src/server/daemon/acl.c

Wed, 15 Mar 2023 19:46:02 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 15 Mar 2023 19:46:02 +0100
changeset 471
9aa5ae3258f5
parent 470
467ed0f559af
permissions
-rw-r--r--

minimal support for ldap groups

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2013 Olaf Wintermann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "../util/util.h"
#include "../util/pool.h"
#include "../util/pblock.h"
#include "../safs/auth.h"
#include "log.h"
#include "acl.h"

#define AUTH_TYPE_BASIC "basic"

void acllist_createhandle(Session *sn, Request *rq) {
    ACLListHandle *handle = pool_malloc(sn->pool, sizeof(ACLListHandle));
    handle->defaultauthdb = NULL;
    handle->listhead = NULL;
    handle->listtail = NULL;
    rq->acllist = handle;
}

/*
 * append or prepend an ACL
 */
void acllist_add(Session *sn, Request *rq, ACLList *acl, int append) {
    if(!rq->acllist) {
        acllist_createhandle(sn, rq);
    }
    ACLListHandle *list = rq->acllist;

    if(!list->defaultauthdb && acl->authdb) {
        list->defaultauthdb = acl->authdb;
    }

    ACLListElm *elm = pool_malloc(sn->pool, sizeof(ACLListElm));
    elm->acl = acl;
    elm->next = NULL;
    if(list->listhead == NULL) {
        list->listhead = elm;
        list->listtail = elm;
    } else {
        if(append) {
            list->listtail->next = elm;
            list->listtail = elm;
        } else {
            elm->next = list->listhead;
            list->listhead = elm;
        }
    }
}

void acllist_append(Session *sn, Request *rq, ACLList *acl) {
    acllist_add(sn, rq, acl, 1);
}

void acllist_prepend(Session *sn, Request *rq, ACLList *acl) {
    acllist_add(sn, rq, acl, 0);
}

uint32_t acl_oflag2mask(int oflags) {
    /* TODO:
     * maybe there is a plattform where O_RDWR is not O_RDONLY | O_WRONLY
     */
    uint32_t access_mask = 0;
    if((oflags & O_RDONLY) == O_RDONLY) {
        access_mask |= ACL_READ_DATA;
    }
    if((oflags & O_WRONLY) == O_WRONLY) {
        access_mask |= ACL_WRITE_DATA;
    }
    return access_mask;
}

User* acllist_getuser(Session *sn, Request *rq, ACLListHandle *list) {
    // TODO: cache result #50
    if(!sn || !rq || !list) {
        return NULL;
    }

    // get user
    User *user = NULL;
    if(list->defaultauthdb) {
        char *usr;
        char *pw;
        if(!basicauth_getuser(sn, rq, &usr, &pw)) {
            int pwok;
            user = authdb_get_and_verify(list->defaultauthdb, sn, rq, usr, pw, &pwok);
            if(!user) {
                // wrong user or wrong password
                return NULL;
            }
            // ok - user is authenticated
            pblock_kvinsert(
                    pb_key_auth_user,
                    user->name,
                    strlen(user->name),
                    rq->vars);
            pblock_kvinsert(
                    pb_key_auth_type,
                    AUTH_TYPE_BASIC,
                    sizeof(AUTH_TYPE_BASIC)-1,
                    rq->vars);
        }
    }

    return user;
}

void acl_set_error_status(Session *sn, Request *rq, ACLList *acl, User *user) {
    if(sn == NULL || rq == NULL) {
        return;
    }

    if(!user) {
        char *value = NULL;
        if(acl->authprompt) {
            size_t realmlen = strlen(acl->authprompt);
            size_t len = realmlen + 16;
            value = pool_malloc(sn->pool, len);
            if(value) {
                snprintf(
                        value,
                        len,
                        "Basic realm=\"%s\"",
                        acl->authprompt);
            }
        }
        if(!value) {
            value = "Basic realm=\"login\"";
        }
        pblock_nvinsert("www-authenticate", value, rq->srvhdrs);
        protocol_status(sn, rq, PROTOCOL_UNAUTHORIZED, NULL);
    } else {
        protocol_status(sn, rq, PROTOCOL_FORBIDDEN, NULL);
    }
}

int acl_evaluate(Session *sn, Request *rq, int access_mask) {
    ACLListHandle *list = rq->acllist;
    if(!list) {
        return REQ_PROCEED;
    }

    // we combine access_mask with the required access rights
    access_mask |= rq->aclreqaccess;

    // get user
    User *user = acllist_getuser(sn, rq, list);

    // evalutate all ACLs
    ACLList *acl = acl_evallist(list, user, access_mask, NULL);
    if(acl) {
        acl_set_error_status(sn, rq, acl, user);
        return REQ_ABORTED;
    }

    return REQ_PROCEED;
}

ACLList* acl_evallist(
        ACLListHandle *list,
        User *user,
        int access_mask,
        ACLList **externacl)
{
    if(!list) {
        return NULL;
    }
    if(externacl) {
        *externacl = NULL;
    }

    // evaluate each acl until one denies access
    ACLListElm *elm = list->listhead;
    while(elm) {
        ACLList *acl = elm->acl;
        if(acl->isextern) {
            // set externacl to the first external acl
            if(externacl && *externacl == NULL) {
                *externacl = acl;
            }
        } else if(!acl->check(acl, user, access_mask)) {
            // the acl denies access
            return acl;
        }
        elm = elm->next;
    }

    // ok - all acls allowed access

    return NULL;
}

int wsacl_affects_user(WSAce *ace, User *user) {
    int check_access = 0;

    /*
     * an ace can affect
     *   a named user or group (ace->who is set)
     *   the owner of the resource (ACL_OWNER is set)
     *   the owning group of the resource (ACL_GROUP is set)
     *   everyone (ACL_EVERYONE is set)
     *
     * Only one of this conditions should be true. The behavior on
     * illegal flag combination is undefined. We assume that the acls
     * are created correctly by the configuration loader.
     */

    if(ace->who && user) {
        // this ace is defined for a named user or group
        if((ace->flags & ACL_IDENTIFIER_GROUP) == ACL_IDENTIFIER_GROUP) {
            if(user->check_group(user, ace->who)) {
                // the user is in the group
                check_access = 1;
            }
        } else {
            if(!strcmp(user->name, ace->who)) {
                check_access = 1;
            }
        }
    } else if((ace->flags & ACL_OWNER) == ACL_OWNER) {
        // TODO
    } else if((ace->flags & ACL_GROUP) == ACL_GROUP) {
        // TODO
    } else if((ace->flags & ACL_EVERYONE) == ACL_EVERYONE) {
        check_access = 1;
    }

    return check_access;
}

int wsacl_check(WSAcl *acl, User *user, int access_mask) {
    int allow = 0;
    uint32_t allowed_access = 0;
    // check each access control entry
    for(int i=0;i<acl->acenum;i++) {
        WSAce *ace = acl->ace[i];
        if(wsacl_affects_user(ace, user)) {
            if(ace->type == ACL_TYPE_ALLOWED) {
                // add all new access rights
                allowed_access |= (access_mask & ace->access_mask);
                // check if we have all requested rights
                if((allowed_access & access_mask) == access_mask) {
                    allow = 1;
                    break;
                }
            } else {
                // ACL_TYPE_DENIED

                if((ace->access_mask & access_mask) != 0) {
                    // access denied
                    break;
                }
            }
        }
    }

    // TODO: events

    return allow; // allow is 0, if no ace set it to 1
}


/* filesystem acl functions */

#if defined (__SVR4) && defined (__sun)

#include <sys/acl.h>

int solaris_acl_check(
        char *path,
        struct stat *s,
        uint32_t mask,
        uid_t uid,
        gid_t gid);
int solaris_acl_affects_user(
        ace_t *ace,
        uid_t uid,
        gid_t gid,
        uid_t owner,
        gid_t owninggroup);

int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask) {
    cxmutstr p;
    if(path[0] != '/') {
        size_t n = 128;
        char *cwd = malloc(n);
        while(!getcwd(cwd, n)) {
            if(errno == ERANGE) {
                n *= 2;
                cwd = realloc(cwd, n);
            } else {
                free(cwd);
                return 0;
            }
        }
        cxmutstr wd = cx_str(cwd);
        cxmutstr pp = cx_str((char*)path);

        p = cx_strcat(3, wd, cx_strn("/", 1), pp);
    } else {
        p = cx_strdup(cx_str((char*)path));
    }
    if(p.ptr[p.length-1] == '/') {
        p.ptr[p.length-1] = 0;
        p.length--;
    }

    // get uid/gid
    struct passwd pw;
    if(user) {
        char *pwbuf = malloc(DEF_PWBUF);
        if(pwbuf == NULL) {
            free(p.ptr);
            return 0;
        }
        if(!util_getpwnam(user->name, &pw, pwbuf, DEF_PWBUF)) {
            free(pwbuf);
            free(p.ptr);
            return 0;
        }
        free(pwbuf);
        acl->user_uid = pw.pw_uid;
        acl->user_gid = pw.pw_gid;
    } else {
        acl->user_uid = -1;
        acl->user_gid = -1;
    }

    // translate access_mask
    uint32_t mask = 0;
    if((access_mask & ACL_READ_DATA) == ACL_READ_DATA) {
        mask |= ACE_READ_DATA;
    }
    if((access_mask & ACL_WRITE_DATA) == ACL_WRITE_DATA) {
        mask |= ACE_WRITE_DATA;
    }
    if((access_mask & ACL_ADD_FILE) == ACL_ADD_FILE) {
        mask |= ACE_ADD_FILE;
    }
    if((access_mask & ACL_READ_XATTR) == ACL_READ_XATTR) {
        mask |= ACE_READ_NAMED_ATTRS;
    }
    if((access_mask & ACL_WRITE_XATTR) == ACL_WRITE_XATTR) {
        mask |= ACE_WRITE_NAMED_ATTRS;
    }
    if((access_mask & ACL_EXECUTE) == ACL_EXECUTE) {
        mask |= ACE_EXECUTE;
    }
    if((access_mask & ACL_DELETE) == ACL_DELETE) {
        mask |= ACE_DELETE_CHILD;
    }
    if((access_mask & ACL_READ_ATTRIBUTES) == ACL_READ_ATTRIBUTES) {
        mask |= ACE_READ_ATTRIBUTES;
    }
    if((access_mask & ACL_WRITE_ATTRIBUTES) == ACL_WRITE_ATTRIBUTES) {
        mask |= ACE_WRITE_ATTRIBUTES;
    }
    if((access_mask & ACL_LIST) == ACL_LIST) {
        mask |= ACE_LIST_DIRECTORY;
    }
    if((access_mask & ACL_READ_ACL) == ACL_READ_ACL) {
        mask |= ACE_READ_ACL;
    }
    if((access_mask & ACL_WRITE_ACL) == ACL_WRITE_ACL) {
        mask |= ACE_WRITE_ACL;
    }
    if((access_mask & ACL_WRITE_OWNER) == ACL_WRITE_OWNER) {
        mask |= ACE_WRITE_OWNER;
    }
    if((access_mask & ACL_SYNCHRONIZE) == ACL_SYNCHRONIZE) {
        mask |= ACE_SYNCHRONIZE;
    }

    /*
     * If the vfs wants to create new files, path does not name an existing
     * file. In this case, we check if the user has the ACE_ADD_FILE
     * permission for the parent directory
     */
    struct stat s;
    if(stat(p.ptr, &s)) {
        if(errno != ENOENT) {
            perror("fs_acl_check: stat");
            free(p.ptr);
            return 0;
        } else {
            mask = ACE_ADD_FILE;
            p = util_path_remove_last(p);
            if(stat(p.ptr, &s)) {
                free(p.ptr);
                return 0;
            }
        }
    }

    /*
     * perform a acl check for the path and each parent directory
     * we don't check the file system root
     *
     * after the first check, we check only search permission for the
     * directories
     */
    if(!solaris_acl_check(p.ptr, &s, mask, pw.pw_uid, pw.pw_gid)) {
        free(p.ptr);
        return 0;
    }

    p = util_path_remove_last(p);
    mask = ACE_LIST_DIRECTORY;
    while(p.length > 1) {
        if(stat(p.ptr, &s)) {
            free(p.ptr);
            return 0;
        }
        if(!solaris_acl_check(p.ptr, &s, mask, pw.pw_uid, pw.pw_gid)) {
            free(p.ptr);
            return 0;
        }

        // cut the last file name from the path
        p = util_path_remove_last(p);
    }


    return 1;
}

int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) {
    // TODO:
    return 1;
}

int solaris_acl_check(
        char *path,
        struct stat *s,
        uint32_t mask,
        uid_t uid,
        gid_t gid)
{
    //printf("solaris_acl_check %s\n", path);

    int nace = acl(path, ACE_GETACLCNT, 0, NULL);
    if(nace == -1) {
        perror("acl: ACE_GETACLCNT");
        // TODO: log error
        return 0;
    }
    ace_t *aces = calloc(nace, sizeof(ace_t));
    if(acl(path, ACE_GETACL, nace, aces) == 1) {
        perror("acl: ACE_GETACL");
        // TODO: log error
        free(aces);
        return 0;
    }

    int allow = 0;
    uint32_t allowed_access = 0;
    for(int i=0;i<nace;i++) {
        ace_t ace = aces[i];
        if(solaris_acl_affects_user(&ace, uid, gid, s->st_uid, s->st_gid)) {
            if(ace.a_type == ACE_ACCESS_ALLOWED_ACE_TYPE) {
                // add all new access rights
                allowed_access |= (mask & ace.a_access_mask);
                // check if we have all requested rights
                if((allowed_access & mask) == mask) {
                    allow = 1;
                    break;
                }
            } else if(ace.a_type == ACE_ACCESS_DENIED_ACE_TYPE) {
                // ACL_TYPE_DENIED

                if((ace.a_access_mask & mask) != 0) {
                    // access denied
                    break;
                }
            }
        }
    }

    free(aces);

    //printf("return %d\n", allow);
    return allow;
}

int solaris_acl_affects_user(
        ace_t *ace,
        uid_t uid,
        gid_t gid,
        uid_t owner,
        gid_t owninggroup)
{
    /*
     * mostly the same as wsacl_affects_user
     */

    int check_access = 0;

    if((ace->a_flags & ACE_OWNER) == ACE_OWNER) {
        if(uid == owner) {
            check_access = 1;
        }
    } else if((ace->a_flags & ACE_GROUP) == ACE_GROUP) {
        if(gid == owninggroup) {
            check_access = 1;
        }
    } else if((ace->a_flags & ACE_EVERYONE) == ACE_EVERYONE) {
       check_access = 1;
    } else if(ace->a_who != -1 && uid != 0) {
        // this ace is defined for a named user or group
        if((ace->a_flags & ACE_IDENTIFIER_GROUP) == ACE_IDENTIFIER_GROUP) {
            // TODO: check all groups
            if(ace->a_who == gid) {
                // the user is in the group
                check_access = 1;
            }
        } else {
            if(ace->a_who == uid) {
                check_access = 1;
            }
        }
    }

    return check_access;
}

void fs_acl_finish() {

}

#endif

/*
 * generic code for all non acl unices
 * TODO: don't use OSX in the preprocessor directive
 */
#ifdef OSX

int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask) {
    return 1;
}

int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) {
    return 1;
}

void fs_acl_finish() {

}

#endif

#if defined(BSD) && !defined(OSX)

int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask) {
    return 1;
}

int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) {
    return 1;
}

void fs_acl_finish() {

}

#endif


#ifdef LINUX

#include <sys/fsuid.h>

int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask) {
    struct passwd *ws_pw = conf_getglobals()->Vuserpw;
    if(!ws_pw) {
        log_ereport(LOG_FAILURE, "fs_acl_check: unknown webserver uid/gid");
        return 1;
    }

    // get uid/gid
    struct passwd pw;
    if(user) {
        char *pwbuf = malloc(DEF_PWBUF);
        if(pwbuf == NULL) {
            return 0;
        }
        if(!util_getpwnam(user->name, &pw, pwbuf, DEF_PWBUF)) {
            free(pwbuf);
            return 0;
        }
        free(pwbuf);
        acl->user_uid = pw.pw_uid;
        acl->user_gid = pw.pw_gid;
    } else {
        acl->user_uid = 0;
        acl->user_gid = 0;
    }

    // set fs uid/gid
    if(acl->user_uid != 0) {
        if(setfsuid(pw.pw_uid)) {
            log_ereport(
                    LOG_FAILURE,
                    "Cannot set fsuid to uid: %u", pw.pw_uid);
        }
        if(setfsgid(pw.pw_gid)) {
            log_ereport(
                    LOG_FAILURE,
                    "Cannot set fsgid to gid: %u", pw.pw_gid);
        }
    }


    return 1;
}

int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) {
    // TODO
    return 1;
}

void fs_acl_finish() {
    struct passwd *pw = conf_getglobals()->Vuserpw;
    if(!pw) {
        log_ereport(
                LOG_FAILURE,
                "global configuration broken (Vuserpw is null)");
        return;
    }
    if(setfsuid(pw->pw_uid)) {
        log_ereport(
                LOG_FAILURE,
                "Cannot set fsuid back to server uid: %u", pw->pw_uid);
    }
    if(setfsgid(pw->pw_gid)) {
        log_ereport(
                LOG_FAILURE,
                "Cannot set fsgid back to server gid: %u", pw->pw_gid);
    }
}

#endif

mercurial