/* * Copyright (c) 2004, 2010, 2011 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "aclvar.h" /* * NOTE: the copy_int/copy_ext functions are duplicated here, one version of each for * each of native and portable endianity. A more elegant solution might be called for * if the functions become much more complicated. */ /* * acl_t -> external representation, portable endianity */ 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; /* validate arguments, compute required size */ 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); /* special case for _FILESEC_REMOVE_ACL */ if (acl == (acl_t)_FILESEC_REMOVE_ACL) { ext->fsec_entrycount = OSSwapHostToBigInt32(KAUTH_FILESEC_NOACL); return(reqsize); } /* export the header */ ext->fsec_entrycount = OSSwapHostToBigInt32(acl->a_entries); ext->fsec_flags = OSSwapHostToBigInt32(acl->a_flags); /* copy ACEs */ for (i = 0; i < acl->a_entries; i++) { /* ACE contents are almost identical */ 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); } /* * acl_t -> external representation, native system endianity */ 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; /* validate arguments, compute required size */ 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; /* special case for _FILESEC_REMOVE_ACL */ if (acl == (acl_t)_FILESEC_REMOVE_ACL) { ext->fsec_entrycount = KAUTH_FILESEC_NOACL; return(reqsize); } /* export the header */ ext->fsec_entrycount = acl->a_entries; ext->fsec_flags = acl->a_flags; /* copy ACEs */ for (i = 0; i < acl->a_entries; i++) { /* ACE contents are almost identical */ 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); } /* * external representation, portable system endianity -> acl_t * * Unlike acl_copy_ext, we can't mung the buffer as it doesn't belong to us. */ 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) { /* copy useful header fields */ ap->a_flags = OSSwapBigToHostInt32(ext->fsec_flags); ap->a_entries = OSSwapBigToHostInt32(ext->fsec_entrycount); /* copy ACEs */ for (i = 0; i < ap->a_entries; i++) { /* ACE contents are literally identical */ 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); } /* * external representation, native system endianity -> acl_t */ 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) { /* copy useful header fields */ ap->a_flags = ext->fsec_flags; ap->a_entries = ext->fsec_entrycount; /* copy ACEs */ for (i = 0; i < ap->a_entries; i++) { /* ACE contents are literally identical */ 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_LIST_DIRECTORY, "list", ACL_TYPE_DIR}, {ACL_WRITE_DATA, "write", ACL_TYPE_FILE}, // {ACL_ADD_FILE, "add_file", ACL_TYPE_DIR}, {ACL_EXECUTE, "execute", ACL_TYPE_FILE}, // {ACL_SEARCH, "search", ACL_TYPE_DIR}, {ACL_DELETE, "delete", ACL_TYPE_FILE | ACL_TYPE_DIR}, {ACL_APPEND_DATA, "append", ACL_TYPE_FILE}, // {ACL_ADD_SUBDIRECTORY, "add_subdirectory", ACL_TYPE_DIR}, {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}, {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} }; /* * reallocing snprintf with offset */ 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); //warn("reallocf failure"); 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: ; //warn("Unable to translate qualifier on ACL\n"); } } 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; /* global acl flags * format: !#acl [] */ if ((entry = strsep(&buf, "\n")) != NULL && *entry) { /* field 1: !#acl */ field = strsep(&entry, " "); if (*field && strncmp(field, "!#acl", strlen("!#acl"))) { error = EINVAL; goto exit; } /* field 2: * currently only accepts 1 */ field = strsep(&entry, " "); errno = 0; if (!*field || strtol(field, NULL, 0) != 1) { error = EINVAL; goto exit; } /* field 3: * optional */ 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) { /* couldn't find flag */ error = EINVAL; goto exit; } } } } else { error = EINVAL; goto exit; } /* parse each acl line * format: : * []: * []: * []: * [,] * [:[,]] * * only one of the user/group identifies is required * the first one found is used */ while ((entry = strsep(&buf, "\n")) && *entry) { need_tag = 1; ug_tag = -1; /* field 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; } /* field 2: */ if ((field = strsep(&entry, ":")) != NULL && *field) { uuid_parse(field, *uu); need_tag = 0; } /* field 3: */ 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; } /* field 4: */ 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; } /* sanity check: nothing set as qualifier */ if (need_tag) { error = EINVAL; goto exit; } /* field 5: */ 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) { /* couldn't find perm */ error = EINVAL; goto exit; } } /* field 6: (can be empty) */ 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) { /* couldn't find perm */ 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; } } } } buf[(*len_p)++] = '\n'; buf[(*len_p)] = 0; return buf; err_nomem: if (buf != NULL) free(buf); errno = ENOMEM; return NULL; } ssize_t acl_size(acl_t acl) { /* special case for _FILESEC_REMOVE_ACL */ if (acl == (acl_t)_FILESEC_REMOVE_ACL) return KAUTH_FILESEC_SIZE(0); _ACL_VALIDATE_ACL(acl); return(KAUTH_FILESEC_SIZE(acl->a_entries)); }