Wed, 27 Nov 2024 23:00:07 +0100
add TODO to use a future ucx feature
/* * 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