#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;
}
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) {
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) {
if(!sn || !rq || !list) {
return NULL;
}
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, usr, pw, &pwok);
if(!user) {
return NULL;
}
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;
}
access_mask |= rq->aclreqaccess;
User *user = acllist_getuser(sn, rq, list);
ACLList *acl = acl_evallist(list, user, access_mask,
NULL);
if(acl) {
acl_set_error_status(sn, rq, acl, user);
if(user) {
user->free(user);
}
return REQ_ABORTED;
}
if(user) {
user->free(user);
}
return REQ_PROCEED;
}
ACLList* acl_evallist(
ACLListHandle *list,
User *user,
int access_mask,
ACLList **externacl)
{
if(!list) {
return NULL;
}
if(externacl) {
*externacl =
NULL;
}
ACLListElm *elm = list->listhead;
while(elm) {
ACLList *acl = elm->acl;
if(acl->isextern) {
if(externacl && *externacl ==
NULL) {
*externacl = acl;
}
}
else if(!acl->check(acl, user, access_mask)) {
return acl;
}
elm = elm->next;
}
return NULL;
}
int wsacl_affects_user(WSAce *ace, User *user) {
int check_access =
0;
if(ace->who && user) {
if((ace->flags &
ACL_IDENTIFIER_GROUP) ==
ACL_IDENTIFIER_GROUP) {
if(user->check_group(user, ace->who)) {
check_access =
1;
}
}
else {
if(!strcmp(user->name, ace->who)) {
check_access =
1;
}
}
}
else if((ace->flags &
ACL_OWNER) ==
ACL_OWNER) {
}
else if((ace->flags &
ACL_GROUP) ==
ACL_GROUP) {
}
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;
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) {
allowed_access |= (access_mask & ace->access_mask);
if((allowed_access & access_mask) == access_mask) {
allow =
1;
break;
}
}
else {
if((ace->access_mask & access_mask) !=
0) {
break;
}
}
}
}
return allow;
}
#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,
char *path,
uint32_t access_mask) {
sstr_t 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;
}
}
sstr_t wd = sstr(cwd);
sstr_t pp = sstr(path);
p = sstrcat(
3, wd, sstrn(
"/",
1), pp);
}
else {
p = sstrdup(sstr(path));
}
if(p.ptr[p.length-
1] ==
'/') {
p.ptr[p.length-
1] =
0;
p.length--;
}
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;
}
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;
}
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;
}
}
}
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;
}
p = util_path_remove_last(p);
}
return 1;
}
int solaris_acl_check(
char *path,
struct stat *s,
uint32_t mask,
uid_t uid,
gid_t gid)
{
int nace = acl(path,
ACE_GETACLCNT,
0,
NULL);
if(nace == -
1) {
perror(
"acl: ACE_GETACLCNT");
return 0;
}
ace_t *aces = calloc(nace,
sizeof(
ace_t));
if(acl(path,
ACE_GETACL, nace, aces) ==
1) {
perror(
"acl: ACE_GETACL");
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) {
allowed_access |= (mask & ace.a_access_mask);
if((allowed_access & mask) == mask) {
allow =
1;
break;
}
}
else if(ace.a_type ==
ACE_ACCESS_DENIED_ACE_TYPE) {
if((ace.a_access_mask & mask) !=
0) {
break;
}
}
}
}
free(aces);
return allow;
}
int solaris_acl_affects_user(
ace_t *ace,
uid_t uid,
gid_t gid,
uid_t owner,
gid_t owninggroup)
{
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) {
if((ace->a_flags &
ACE_IDENTIFIER_GROUP) ==
ACE_IDENTIFIER_GROUP) {
if(ace->a_who == gid) {
check_access =
1;
}
}
else {
if(ace->a_who == uid) {
check_access =
1;
}
}
}
return check_access;
}
void fs_acl_finish() {
}
#endif
#ifdef OSX
int fs_acl_check(SysACL *acl, User *user,
char *path,
uint32_t access_mask) {
return 1;
}
void fs_acl_finish() {
}
#endif
#ifdef BSD
int fs_acl_check(SysACL *acl, User *user,
char *path,
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,
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;
}
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;
}
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;
}
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