#include <sys/appleapiopts.h>
#include <sys/types.h>
#include <sys/acl.h>
#include <sys/fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <membership.h>
#include <membershipPriv.h>
#include <pwd.h>
#include <grp.h>
#include <libkern/OSByteOrder.h>
#include "aclvar.h"
ssize_t
acl_copy_ext(void *buf, acl_t acl, ssize_t size)
{
struct kauth_filesec *ext = (struct kauth_filesec *)buf;
ssize_t reqsize;
int i;
reqsize = acl_size(acl);
if (reqsize < 0)
return(-1);
if (reqsize > size) {
errno = ERANGE;
return(-1);
}
bzero(ext, reqsize);
ext->fsec_magic = OSSwapHostToBigInt32(KAUTH_FILESEC_MAGIC);
if (acl == (acl_t)_FILESEC_REMOVE_ACL) {
ext->fsec_entrycount = OSSwapHostToBigInt32(KAUTH_FILESEC_NOACL);
return(reqsize);
}
ext->fsec_entrycount = OSSwapHostToBigInt32(acl->a_entries);
ext->fsec_flags = OSSwapHostToBigInt32(acl->a_flags);
for (i = 0; i < acl->a_entries; i++) {
ext->fsec_ace[i].ace_applicable = acl->a_ace[i].ae_applicable;
ext->fsec_ace[i].ace_flags =
OSSwapHostToBigInt32((acl->a_ace[i].ae_tag & KAUTH_ACE_KINDMASK) | (acl->a_ace[i].ae_flags & ~KAUTH_ACE_KINDMASK));
ext->fsec_ace[i].ace_rights = OSSwapHostToBigInt32(acl->a_ace[i].ae_perms);
}
return(reqsize);
}
ssize_t
acl_copy_ext_native(void *buf, acl_t acl, ssize_t size)
{
struct kauth_filesec *ext = (struct kauth_filesec *)buf;
ssize_t reqsize;
int i;
reqsize = acl_size(acl);
if (reqsize < 0)
return(-1);
if (reqsize > size) {
errno = ERANGE;
return(-1);
}
bzero(ext, reqsize);
ext->fsec_magic = KAUTH_FILESEC_MAGIC;
if (acl == (acl_t)_FILESEC_REMOVE_ACL) {
ext->fsec_entrycount = KAUTH_FILESEC_NOACL;
return(reqsize);
}
ext->fsec_entrycount = acl->a_entries;
ext->fsec_flags = acl->a_flags;
for (i = 0; i < acl->a_entries; i++) {
ext->fsec_ace[i].ace_applicable = acl->a_ace[i].ae_applicable;
ext->fsec_ace[i].ace_flags =
(acl->a_ace[i].ae_tag & KAUTH_ACE_KINDMASK) |
(acl->a_ace[i].ae_flags & ~KAUTH_ACE_KINDMASK);
ext->fsec_ace[i].ace_rights = acl->a_ace[i].ae_perms;
}
return(reqsize);
}
acl_t
acl_copy_int(const void *buf)
{
struct kauth_filesec *ext = (struct kauth_filesec *)buf;
acl_t ap;
int i;
if (ext->fsec_magic != OSSwapHostToBigInt32(KAUTH_FILESEC_MAGIC)) {
errno = EINVAL;
return(NULL);
}
if ((ap = acl_init(OSSwapBigToHostInt32(ext->fsec_entrycount))) != NULL) {
ap->a_flags = OSSwapBigToHostInt32(ext->fsec_flags);
ap->a_entries = OSSwapBigToHostInt32(ext->fsec_entrycount);
for (i = 0; i < ap->a_entries; i++) {
ap->a_ace[i].ae_magic = _ACL_ENTRY_MAGIC;
ap->a_ace[i].ae_applicable = ext->fsec_ace[i].ace_applicable;
ap->a_ace[i].ae_flags = OSSwapBigToHostInt32(ext->fsec_ace[i].ace_flags) & ~KAUTH_ACE_KINDMASK;
ap->a_ace[i].ae_tag = OSSwapBigToHostInt32(ext->fsec_ace[i].ace_flags) & KAUTH_ACE_KINDMASK;
ap->a_ace[i].ae_perms = OSSwapBigToHostInt32(ext->fsec_ace[i].ace_rights);
}
}
return(ap);
}
acl_t
acl_copy_int_native(const void *buf)
{
struct kauth_filesec *ext = (struct kauth_filesec *)buf;
acl_t ap;
int i;
if (ext->fsec_magic != KAUTH_FILESEC_MAGIC) {
errno = EINVAL;
return(NULL);
}
if ((ap = acl_init(ext->fsec_entrycount)) != NULL) {
ap->a_flags = ext->fsec_flags;
ap->a_entries = ext->fsec_entrycount;
for (i = 0; i < ap->a_entries; i++) {
ap->a_ace[i].ae_magic = _ACL_ENTRY_MAGIC;
ap->a_ace[i].ae_applicable = ext->fsec_ace[i].ace_applicable;
ap->a_ace[i].ae_flags = ext->fsec_ace[i].ace_flags & ~KAUTH_ACE_KINDMASK;
ap->a_ace[i].ae_tag = ext->fsec_ace[i].ace_flags & KAUTH_ACE_KINDMASK;
ap->a_ace[i].ae_perms = ext->fsec_ace[i].ace_rights;
}
}
return(ap);
}
#define ACL_TYPE_DIR (1<<0)
#define ACL_TYPE_FILE (1<<1)
#define ACL_TYPE_ACL (1<<2)
static struct {
acl_perm_t perm;
char *name;
int type;
} acl_perms[] = {
{ACL_READ_DATA, "read", ACL_TYPE_FILE},
{ACL_WRITE_DATA, "write", ACL_TYPE_FILE},
{ACL_EXECUTE, "execute", ACL_TYPE_FILE},
{ACL_DELETE, "delete", ACL_TYPE_FILE | ACL_TYPE_DIR},
{ACL_APPEND_DATA, "append", ACL_TYPE_FILE},
{ACL_DELETE_CHILD, "delete_child", ACL_TYPE_DIR},
{ACL_READ_ATTRIBUTES, "readattr", ACL_TYPE_FILE | ACL_TYPE_DIR},
{ACL_WRITE_ATTRIBUTES, "writeattr", ACL_TYPE_FILE | ACL_TYPE_DIR},
{ACL_READ_EXTATTRIBUTES, "readextattr", ACL_TYPE_FILE | ACL_TYPE_DIR},
{ACL_WRITE_EXTATTRIBUTES, "writeextattr", ACL_TYPE_FILE | ACL_TYPE_DIR},
{ACL_READ_SECURITY, "readsecurity", ACL_TYPE_FILE | ACL_TYPE_DIR},
{ACL_WRITE_SECURITY, "writesecurity", ACL_TYPE_FILE | ACL_TYPE_DIR},
{ACL_CHANGE_OWNER, "chown", ACL_TYPE_FILE | ACL_TYPE_DIR},
{ACL_SYNCHRONIZE, "synchronize", ACL_TYPE_FILE | ACL_TYPE_DIR},
{0, NULL, 0}
};
static struct {
acl_flag_t flag;
char *name;
int type;
} acl_flags[] = {
{ACL_ENTRY_INHERITED, "inherited", ACL_TYPE_FILE | ACL_TYPE_DIR},
{ACL_FLAG_DEFER_INHERIT, "defer_inherit", ACL_TYPE_ACL},
{ACL_ENTRY_FILE_INHERIT, "file_inherit", ACL_TYPE_DIR},
{ACL_ENTRY_DIRECTORY_INHERIT, "directory_inherit", ACL_TYPE_DIR},
{ACL_ENTRY_LIMIT_INHERIT, "limit_inherit", ACL_TYPE_FILE | ACL_TYPE_DIR},
{ACL_ENTRY_ONLY_INHERIT, "only_inherit", ACL_TYPE_DIR},
{ACL_FLAG_NO_INHERIT, "no_inherit", ACL_TYPE_ACL},
{0, NULL, 0}
};
static int
raosnprintf(char **buf, size_t *size, ssize_t *offset, char *fmt, ...)
{
va_list ap;
int ret;
do
{
if (*offset < *size)
{
va_start(ap, fmt);
ret = vsnprintf(*buf + *offset, *size - *offset, fmt, ap);
va_end(ap);
if (ret < (*size - *offset))
{
*offset += ret;
return ret;
}
}
*buf = reallocf(*buf, (*size *= 2));
} while (*buf);
return 0;
}
static char *
uuid_to_name(uuid_t *uu, uid_t *id, int *isgid)
{
struct group *tgrp = NULL;
struct passwd *tpass = NULL;
if (0 == mbr_uuid_to_id(*uu, id, isgid))
{
switch (*isgid)
{
case ID_TYPE_UID:
if (!(tpass = getpwuid(*id)))
goto errout;
return strdup(tpass->pw_name);
break;
case ID_TYPE_GID:
if (!(tgrp = getgrgid((gid_t) *id)))
goto errout;
return strdup(tgrp->gr_name);
break;
default:
errout: ; }
}
return NULL;
}
acl_t
acl_from_text(const char *buf_p)
{
int i, error = 0, need_tag, ug_tag;
char *buf, *orig_buf;
char *entry, *field, *sub;
uuid_t *uu = NULL;
struct passwd *tpass = NULL;
struct group *tgrp = NULL;
acl_entry_t acl_entry;
acl_flagset_t flags = NULL;
acl_permset_t perms = NULL;
acl_tag_t tag;
acl_t acl_ret;
if (buf_p == NULL)
{
errno = EINVAL;
return NULL;
}
if ((buf = strdup(buf_p)) == NULL)
return NULL;
if ((acl_ret = acl_init(1)) == NULL)
return NULL;
orig_buf = buf;
if ((entry = strsep(&buf, "\n")) != NULL && *entry)
{
field = strsep(&entry, " ");
if (*field && strncmp(field, "!#acl", strlen("!#acl")))
{
error = EINVAL;
goto exit;
}
field = strsep(&entry, " ");
errno = 0;
if (!*field || strtol(field, NULL, 0) != 1)
{
error = EINVAL;
goto exit;
}
if((field = strsep(&entry, " ")) != NULL && *field)
{
acl_get_flagset_np(acl_ret, &flags);
while ((sub = strsep(&field, ",")) && *sub)
{
for (i = 0; acl_flags[i].name != NULL; ++i)
{
if (acl_flags[i].type & ACL_TYPE_ACL
&& !strcmp(acl_flags[i].name, sub))
{
acl_add_flag_np(flags, acl_flags[i].flag);
break;
}
}
if (acl_flags[i].name == NULL)
{
error = EINVAL;
goto exit;
}
}
}
} else {
error = EINVAL;
goto exit;
}
while ((entry = strsep(&buf, "\n")) && *entry)
{
need_tag = 1;
ug_tag = -1;
field = strsep(&entry, ":");
if(uu)
bzero(uu, sizeof(uuid_t));
else if((uu = calloc(1, sizeof(uuid_t))) == NULL)
{
error = errno;
goto exit;
}
if(acl_create_entry(&acl_ret, &acl_entry))
{
error = errno;
goto exit;
}
if (-1 == acl_get_flagset_np(acl_entry, &flags)
|| -1 == acl_get_permset(acl_entry, &perms))
{
error = errno;
goto exit;
}
switch(*field)
{
case 'u':
if(!strcmp(field, "user"))
ug_tag = ID_TYPE_UID;
break;
case 'g':
if(!strcmp(field, "group"))
ug_tag = ID_TYPE_GID;
break;
default:
error = EINVAL;
goto exit;
}
if ((field = strsep(&entry, ":")) != NULL && *field)
{
uuid_parse(field, *uu);
need_tag = 0;
}
if ((field = strsep(&entry, ":")) != NULL && *field && need_tag)
{
switch(ug_tag)
{
case ID_TYPE_UID:
if((tpass = getpwnam(field)) != NULL)
if (mbr_uid_to_uuid(tpass->pw_uid, *uu) != 0)
{
error = EINVAL;
goto exit;
}
break;
case ID_TYPE_GID:
if ((tgrp = getgrnam(field)) != NULL)
if (mbr_gid_to_uuid(tgrp->gr_gid, *uu) != 0)
{
error = EINVAL;
goto exit;
}
break;
default:
error = EINVAL;
goto exit;
}
need_tag = 0;
}
if ((field = strsep(&entry, ":")) != NULL && *field && need_tag)
{
uid_t id;
error = 0;
if((id = strtol(field, NULL, 10)) == 0 && error)
{
error = EINVAL;
goto exit;
}
switch(ug_tag)
{
case ID_TYPE_UID:
if((tpass = getpwuid((uid_t)id)) != NULL)
if (mbr_uid_to_uuid(tpass->pw_uid, *uu) != 0)
{
error = EINVAL;
goto exit;
}
break;
case ID_TYPE_GID:
if ((tgrp = getgrgid((gid_t)id)) != NULL)
if (mbr_gid_to_uuid(tgrp->gr_gid, *uu) != 0)
{
error = EINVAL;
goto exit;
}
break;
}
need_tag = 0;
}
if (need_tag)
{
error = EINVAL;
goto exit;
}
if((field = strsep(&entry, ":")) == NULL || !*field)
{
error = EINVAL;
goto exit;
}
for (tag = 0; (sub = strsep(&field, ",")) && *sub;)
{
if (!tag)
{
if (!strcmp(sub, "allow"))
tag = ACL_EXTENDED_ALLOW;
else if (!strcmp(sub, "deny"))
tag = ACL_EXTENDED_DENY;
else {
error = EINVAL;
goto exit;
}
continue;
}
for (i = 0; acl_flags[i].name != NULL; ++i)
{
if (acl_flags[i].type & (ACL_TYPE_FILE | ACL_TYPE_DIR)
&& !strcmp(acl_flags[i].name, sub))
{
acl_add_flag_np(flags, acl_flags[i].flag);
break;
}
}
if (acl_flags[i].name == NULL)
{
error = EINVAL;
goto exit;
}
}
if((field = strsep(&entry, ":")) != NULL && *field)
{
while ((sub = strsep(&field, ",")) && *sub)
{
for (i = 0; acl_perms[i].name != NULL; i++)
{
if (acl_perms[i].type & (ACL_TYPE_FILE | ACL_TYPE_DIR)
&& !strcmp(acl_perms[i].name, sub))
{
acl_add_perm(perms, acl_perms[i].perm);
break;
}
}
if (acl_perms[i].name == NULL)
{
error = EINVAL;
goto exit;
}
}
}
acl_set_tag_type(acl_entry, tag);
acl_set_qualifier(acl_entry, *uu);
}
exit:
if(uu)
free(uu);
free(orig_buf);
if (error)
{
acl_free(acl_ret);
acl_ret = NULL;
errno = error;
}
return acl_ret;
}
char *
acl_to_text(acl_t acl, ssize_t *len_p)
{
acl_tag_t tag;
acl_entry_t entry = NULL;
acl_flagset_t flags;
acl_permset_t perms;
uid_t id;
int i, first;
int isgid;
size_t bufsize = 1024;
char *buf = NULL;
if (!_ACL_VALID_ACL(acl)) {
errno = EINVAL;
return NULL;
}
if (len_p == NULL)
if ((len_p = alloca(sizeof(ssize_t))) == NULL)
goto err_nomem;
*len_p = 0;
if ((buf = malloc(bufsize)) == NULL)
goto err_nomem;
if (!raosnprintf(&buf, &bufsize, len_p, "!#acl %d", 1))
goto err_nomem;
if (acl_get_flagset_np(acl, &flags) == 0)
{
for (i = 0, first = 0; acl_flags[i].name != NULL; ++i)
{
if (acl_flags[i].type & ACL_TYPE_ACL
&& acl_get_flag_np(flags, acl_flags[i].flag) != 0)
{
if(!raosnprintf(&buf, &bufsize, len_p, "%s%s",
first++ ? "," : " ", acl_flags[i].name))
goto err_nomem;
}
}
}
for (;acl_get_entry(acl,
entry == NULL ? ACL_FIRST_ENTRY : ACL_NEXT_ENTRY, &entry) == 0;)
{
int valid;
uuid_t *uu;
char *str, uu_str[37];
if (((uu = (uuid_t *) acl_get_qualifier(entry)) == NULL)
|| (acl_get_tag_type(entry, &tag) != 0)
|| (acl_get_flagset_np(entry, &flags) != 0)
|| (acl_get_permset(entry, &perms) != 0)) {
if (uu != NULL) acl_free(uu);
continue;
}
uuid_unparse_upper(*uu, uu_str);
if ((str = uuid_to_name(uu, &id, &isgid)) != NULL) {
valid = raosnprintf(&buf, &bufsize, len_p, "\n%s:%s:%s:%d:%s",
isgid ? "group" : "user",
uu_str,
str,
id,
(tag == ACL_EXTENDED_ALLOW) ? "allow" : "deny");
} else {
valid = raosnprintf(&buf, &bufsize, len_p, "\nuser:%s:::%s",
uu_str,
(tag == ACL_EXTENDED_ALLOW) ? "allow" : "deny");
}
free(str);
acl_free(uu);
if (!valid)
goto err_nomem;
for (i = 0; acl_flags[i].name != NULL; ++i)
{
if (acl_flags[i].type & (ACL_TYPE_DIR | ACL_TYPE_FILE))
{
if(acl_get_flag_np(flags, acl_flags[i].flag) != 0)
{
if(!raosnprintf(&buf, &bufsize, len_p, ",%s",
acl_flags[i].name))
goto err_nomem;
}
}
}
for (i = 0, first = 0; acl_perms[i].name != NULL; ++i)
{
if (acl_perms[i].type & (ACL_TYPE_DIR | ACL_TYPE_FILE))
{
if(acl_get_perm_np(perms, acl_perms[i].perm) != 0)
{
if(!raosnprintf(&buf, &bufsize, len_p, "%s%s",
first++ ? "," : ":", acl_perms[i].name))
goto err_nomem;
}
}
}
}
if(!raosnprintf(&buf, &bufsize, len_p, "\n")) goto err_nomem;
return buf;
err_nomem:
if (buf != NULL)
free(buf);
errno = ENOMEM;
return NULL;
}
ssize_t
acl_size(acl_t acl)
{
if (acl == (acl_t)_FILESEC_REMOVE_ACL)
return KAUTH_FILESEC_SIZE(0);
_ACL_VALIDATE_ACL(acl);
return(KAUTH_FILESEC_SIZE(acl->a_entries));
}