#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <membership.h>
#include "chmod_acl.h"
extern void usage(void);
#ifdef __APPLE__
static struct {
acl_perm_t perm;
char *name;
int flags;
#define ACL_PERM_DIR (1<<0)
#define ACL_PERM_FILE (1<<1)
} acl_perms[] = {
{ACL_READ_DATA, "read", ACL_PERM_FILE},
{ACL_LIST_DIRECTORY, "list", ACL_PERM_DIR},
{ACL_WRITE_DATA, "write", ACL_PERM_FILE},
{ACL_ADD_FILE, "add_file", ACL_PERM_DIR},
{ACL_EXECUTE, "execute", ACL_PERM_FILE},
{ACL_SEARCH, "search", ACL_PERM_DIR},
{ACL_DELETE, "delete", ACL_PERM_FILE | ACL_PERM_DIR},
{ACL_APPEND_DATA, "append", ACL_PERM_FILE},
{ACL_ADD_SUBDIRECTORY, "add_subdirectory", ACL_PERM_DIR},
{ACL_DELETE_CHILD, "delete_child", ACL_PERM_DIR},
{ACL_READ_ATTRIBUTES, "readattr", ACL_PERM_FILE | ACL_PERM_DIR},
{ACL_WRITE_ATTRIBUTES, "writeattr", ACL_PERM_FILE | ACL_PERM_DIR},
{ACL_READ_EXTATTRIBUTES, "readextattr", ACL_PERM_FILE | ACL_PERM_DIR},
{ACL_WRITE_EXTATTRIBUTES, "writeextattr", ACL_PERM_FILE | ACL_PERM_DIR},
{ACL_READ_SECURITY, "readsecurity", ACL_PERM_FILE | ACL_PERM_DIR},
{ACL_WRITE_SECURITY, "writesecurity", ACL_PERM_FILE | ACL_PERM_DIR},
{ACL_CHANGE_OWNER, "chown", ACL_PERM_FILE | ACL_PERM_DIR},
{0, NULL, 0}
};
static struct {
acl_flag_t flag;
char *name;
int flags;
} acl_flags[] = {
{ACL_ENTRY_INHERITED, "inherited", ACL_PERM_FILE | ACL_PERM_DIR},
{ACL_ENTRY_FILE_INHERIT, "file_inherit", ACL_PERM_DIR},
{ACL_ENTRY_DIRECTORY_INHERIT, "directory_inherit", ACL_PERM_DIR},
{ACL_ENTRY_LIMIT_INHERIT, "limit_inherit", ACL_PERM_FILE | ACL_PERM_DIR},
{ACL_ENTRY_ONLY_INHERIT, "only_inherit", ACL_PERM_DIR},
{0, NULL, 0}
};
#define NAME_USER (1)
#define NAME_GROUP (2)
#define NAME_EITHER (NAME_USER | NAME_GROUP)
uuid_t *
name_to_uuid(char *tok, int nametype) {
uuid_t *entryg = NULL;
size_t len = strlen(tok);
if ((entryg = (uuid_t *) calloc(1, sizeof(uuid_t))) == NULL) {
errx(1, "Unable to allocate a uuid");
}
if ((nametype & NAME_USER) && mbr_identifier_to_uuid(ID_TYPE_USERNAME, tok, len, *entryg) == 0) {
return entryg;
}
if ((nametype & NAME_GROUP) && mbr_identifier_to_uuid(ID_TYPE_GROUPNAME, tok, len, *entryg) == 0) {
return entryg;
}
errx(1, "Unable to translate '%s' to a UUID", tok);
}
int
parse_entry(char *entrybuf, acl_entry_t newent) {
char *tok;
char *pebuf;
uuid_t *entryg;
acl_tag_t tag;
acl_permset_t perms;
acl_flagset_t flags;
unsigned permcount = 0;
unsigned pindex = 0;
char *delimiter = " ";
int nametype = NAME_EITHER;
acl_get_permset(newent, &perms);
acl_get_flagset_np(newent, &flags);
pebuf = entrybuf;
if (0 == strncmp(entrybuf, "user:", 5)) {
nametype = NAME_USER;
pebuf += 5;
} else if (0 == strncmp(entrybuf, "group:", 6)) {
nametype = NAME_GROUP;
pebuf += 6;
}
if (strchr(pebuf, ':'))
delimiter = ":";
tok = strsep(&pebuf, delimiter);
if ((tok == NULL) || *tok == '\0') {
errx(1, "Invalid entry format -- expected user or group name");
}
entryg = name_to_uuid(tok, nametype);
tok = strsep(&pebuf, ": ");
if ((tok == NULL) || *tok == '\0') {
errx(1, "Invalid entry format -- expected allow or deny");
}
if (!strcmp(tok, "allow")) {
tag = ACL_EXTENDED_ALLOW;
} else if (!strcmp(tok, "deny")) {
tag = ACL_EXTENDED_DENY;
} else {
errx(1, "Unknown tag type '%s'", tok);
}
for (; (tok = strsep(&pebuf, ",")) != NULL;) {
if (*tok != '\0') {
for (pindex = 0; acl_perms[pindex].name != NULL; pindex++) {
if (!strcmp(acl_perms[pindex].name, tok)) {
acl_add_perm(perms, acl_perms[pindex].perm);
permcount++;
goto found;
}
}
for (pindex = 0; acl_flags[pindex].name != NULL; pindex++) {
if (!strcmp(acl_flags[pindex].name, tok)) {
acl_add_flag_np(flags, acl_flags[pindex].flag);
permcount++;
goto found;
}
}
errx(1,"Invalid permission type '%s'", tok);
found:
continue;
}
}
if (0 == permcount) {
errx(1, "No permissions specified");
}
acl_set_tag_type(newent, tag);
acl_set_qualifier(newent, entryg);
acl_set_permset(newent, perms);
acl_set_flagset_np(newent, flags);
free(entryg);
return(0);
}
acl_t
parse_acl_entries(const char *input) {
acl_t acl_input;
acl_entry_t newent;
char *inbuf;
char *oinbuf;
char **bufp, *entryv[ACL_MAX_ENTRIES];
#if 0
#else
inbuf = malloc(MAX_ACL_TEXT_SIZE);
if (inbuf == NULL)
err(1, "malloc() failed");
strncpy(inbuf, input, MAX_ACL_TEXT_SIZE);
inbuf[MAX_ACL_TEXT_SIZE - 1] = '\0';
if ((acl_input = acl_init(1)) == NULL)
err(1, "acl_init() failed");
oinbuf = inbuf;
for (bufp = entryv; (*bufp = strsep(&oinbuf, "\n")) != NULL;)
if (**bufp != '\0') {
if (0 != acl_create_entry(&acl_input, &newent))
err(1, "acl_create_entry() failed");
if (0 != parse_entry(*bufp, newent)) {
errx(1, "Failed parsing entry '%s'", *bufp);
}
if (++bufp >= &entryv[ACL_MAX_ENTRIES - 1]) {
errx(1, "Too many entries");
}
}
free(inbuf);
return acl_input;
#endif
}
unsigned
get_inheritance_level(acl_entry_t entry) {
return 1;
}
int
score_acl_entry(acl_entry_t entry) {
acl_tag_t tag;
acl_flagset_t flags;
acl_permset_t perms;
int score = 0;
if (entry == NULL)
return (MINIMUM_TIER);
if (acl_get_tag_type(entry, &tag) != 0) {
err(1, "Malformed ACL entry, no tag present");
}
if (acl_get_flagset_np(entry, &flags) != 0){
err(1, "Unable to obtain flagset");
}
if (acl_get_permset(entry, &perms) != 0)
err(1, "Malformed ACL entry, no permset present");
switch(tag) {
case ACL_EXTENDED_ALLOW:
break;
case ACL_EXTENDED_DENY:
score++;
break;
default:
errx(1, "Unknown tag type %d present in ACL entry", tag);
}
if (acl_get_flag_np(flags, ACL_ENTRY_INHERITED))
score += get_inheritance_level(entry) * INHERITANCE_TIER;
return score;
}
int
compare_acl_qualifiers(uuid_t *qa, uuid_t *qb) {
return bcmp(qa, qb, sizeof(uuid_t));
}
int
compare_acl_permsets(acl_permset_t aperms, acl_permset_t bperms)
{
int i;
for (i = 0; acl_perms[i].name != NULL; i++) {
if (acl_get_perm_np(aperms, acl_perms[i].perm) !=
acl_get_perm_np(bperms, acl_perms[i].perm))
return MATCH_NONE;
}
return MATCH_EXACT;
}
static int
compare_acl_flagsets(acl_flagset_t aflags, acl_flagset_t bflags)
{
int i;
for (i = 0; acl_flags[i].name != NULL; i++) {
if (acl_get_flag_np(aflags, acl_flags[i].flag) !=
acl_get_flag_np(bflags, acl_flags[i].flag))
return MATCH_NONE;
}
return MATCH_EXACT;
}
int
compare_acl_entries(acl_entry_t a, acl_entry_t b)
{
acl_tag_t atag, btag;
acl_permset_t aperms, bperms;
acl_flagset_t aflags, bflags;
int pcmp = 0, fcmp = 0;
void *aqual, *bqual;
aqual = acl_get_qualifier(a);
bqual = acl_get_qualifier(b);
int compare = compare_acl_qualifiers(aqual, bqual);
acl_free(aqual);
acl_free(bqual);
if (compare != 0)
return MATCH_NONE;
if (0 != acl_get_tag_type(a, &atag))
err(1, "No tag type present in entry");
if (0!= acl_get_tag_type(b, &btag))
err(1, "No tag type present in entry");
if (atag != btag)
return MATCH_NONE;
if ((acl_get_permset(a, &aperms) != 0) ||
(acl_get_flagset_np(a, &aflags) != 0) ||
(acl_get_permset(b, &bperms) != 0) ||
(acl_get_flagset_np(b, &bflags) != 0))
err(1, "error fetching permissions");
pcmp = compare_acl_permsets(aperms, bperms);
fcmp = compare_acl_flagsets(aflags, bflags);
if ((pcmp == MATCH_NONE) || (fcmp == MATCH_NONE))
return(MATCH_PARTIAL);
else
return(MATCH_EXACT);
}
unsigned int
is_canonical(acl_t acl) {
unsigned aindex;
acl_entry_t entry;
int score = 0, next_score = 0;
if (0 != acl_get_entry(acl, ACL_FIRST_ENTRY, &entry))
return 1;
score = score_acl_entry(entry);
for (aindex = 0; acl_get_entry(acl, ACL_NEXT_ENTRY, &entry) == 0;
aindex++) {
if (score < (next_score = score_acl_entry(entry)))
return 0;
score = next_score;
}
return 1;
}
unsigned int
find_canonical_position(acl_t acl, acl_entry_t modifier) {
acl_entry_t entry;
int mscore = 0;
unsigned mpos = 0;
if (0 != acl_get_entry(acl, ACL_FIRST_ENTRY, &entry))
return 0;
mscore = score_acl_entry(modifier);
while (mscore < score_acl_entry(entry)) {
mpos++;
if (0 != acl_get_entry(acl, ACL_NEXT_ENTRY, &entry))
break;
}
return mpos;
}
int canonicalize_acl_entries(acl_t acl);
int
find_matching_entry (acl_t acl, acl_entry_t modifier, acl_entry_t *rentryp,
unsigned match_inherited) {
acl_entry_t entry = NULL;
unsigned aindex;
int cmp, fcmp = MATCH_NONE;
for (aindex = 0;
acl_get_entry(acl, entry == NULL ? ACL_FIRST_ENTRY :
ACL_NEXT_ENTRY, &entry) == 0;
aindex++) {
cmp = compare_acl_entries(entry, modifier);
if ((cmp == MATCH_EXACT) || (cmp == MATCH_PARTIAL)) {
if (match_inherited) {
acl_flagset_t eflags, mflags;
if (0 != acl_get_flagset_np(modifier, &mflags))
err(1, "Unable to get flagset");
if (0 != acl_get_flagset_np(entry, &eflags))
err(1, "Unable to get flagset");
if (compare_acl_flagsets(mflags, eflags) == MATCH_EXACT) {
*rentryp = entry;
fcmp = cmp;
}
}
else {
*rentryp = entry;
fcmp = cmp;
}
}
if (fcmp == MATCH_EXACT)
break;
}
return fcmp;
}
int
subtract_from_entry(acl_entry_t rentry, acl_entry_t modifier, int* valid_perms)
{
acl_permset_t rperms, mperms;
acl_flagset_t rflags, mflags;
if (valid_perms)
*valid_perms = 0;
int i;
if ((acl_get_permset(rentry, &rperms) != 0) ||
(acl_get_flagset_np(rentry, &rflags) != 0) ||
(acl_get_permset(modifier, &mperms) != 0) ||
(acl_get_flagset_np(modifier, &mflags) != 0))
err(1, "error computing ACL modification");
for (i = 0; acl_perms[i].name != NULL; i++) {
if (acl_get_perm_np(mperms, acl_perms[i].perm))
acl_delete_perm(rperms, acl_perms[i].perm);
else if (valid_perms && acl_get_perm_np(rperms, acl_perms[i].perm))
(*valid_perms)++;
}
for (i = 0; acl_flags[i].name != NULL; i++) {
if (acl_get_flag_np(mflags, acl_flags[i].flag))
acl_delete_flag_np(rflags, acl_flags[i].flag);
}
acl_set_permset(rentry, rperms);
acl_set_flagset_np(rentry, rflags);
return 0;
}
static int
merge_entry_perms(acl_entry_t rentry, acl_entry_t modifier)
{
acl_permset_t rperms, mperms;
acl_flagset_t rflags, mflags;
int i;
if ((acl_get_permset(rentry, &rperms) != 0) ||
(acl_get_flagset_np(rentry, &rflags) != 0) ||
(acl_get_permset(modifier, &mperms) != 0) ||
(acl_get_flagset_np(modifier, &mflags) != 0))
err(1, "error computing ACL modification");
for (i = 0; acl_perms[i].name != NULL; i++) {
if (acl_get_perm_np(mperms, acl_perms[i].perm))
acl_add_perm(rperms, acl_perms[i].perm);
}
for (i = 0; acl_flags[i].name != NULL; i++) {
if (acl_get_flag_np(mflags, acl_flags[i].flag))
acl_add_flag_np(rflags, acl_flags[i].flag);
}
acl_set_permset(rentry, rperms);
acl_set_flagset_np(rentry, rflags);
return 0;
}
int
modify_acl(acl_t *oaclp, acl_entry_t modifier, unsigned int optflags,
int position, int inheritance_level,
unsigned flag_new_acl, const char* path) {
unsigned cpos = 0;
acl_entry_t newent = NULL;
int dmatch = 0;
acl_entry_t rentry = NULL;
unsigned retval = 0;
acl_t oacl = *oaclp;
if (modifier && (optflags & ACL_INHERIT_FLAG)) {
acl_flagset_t mflags;
acl_get_flagset_np(modifier, &mflags);
acl_add_flag_np(mflags, ACL_ENTRY_INHERITED);
acl_set_flagset_np(modifier, mflags);
}
if (optflags & ACL_SET_FLAG) {
if (position != -1) {
if (0 != acl_create_entry_np(&oacl, &newent, position))
err(1, "acl_create_entry() failed");
acl_copy_entry(newent, modifier);
} else {
dmatch = find_matching_entry(oacl, modifier, &rentry, 1);
if (dmatch != MATCH_NONE) {
if (dmatch == MATCH_EXACT)
goto ma_exit;
if (dmatch == MATCH_PARTIAL) {
merge_entry_perms(rentry, modifier);
goto ma_exit;
}
}
cpos = find_canonical_position(oacl, modifier);
if (0!= acl_create_entry_np(&oacl, &newent, cpos))
err(1, "acl_create_entry() failed");
acl_copy_entry(newent, modifier);
}
} else if (optflags & ACL_DELETE_FLAG) {
if (flag_new_acl) {
warnx("No ACL present '%s'", path);
retval = 1;
} else if (position != -1 ) {
if (0 != acl_get_entry(oacl, position, &rentry)) {
warnx("Invalid entry number '%s'", path);
retval = 1;
} else {
acl_delete_entry(oacl, rentry);
}
} else {
unsigned match_found = 0, aindex;
for (aindex = 0;
acl_get_entry(oacl, rentry == NULL ?
ACL_FIRST_ENTRY :
ACL_NEXT_ENTRY, &rentry) == 0;
aindex++) {
unsigned cmp;
cmp = compare_acl_entries(rentry, modifier);
if ((cmp == MATCH_EXACT) ||
(cmp == MATCH_PARTIAL)) {
match_found++;
if (cmp == MATCH_EXACT)
acl_delete_entry(oacl, rentry);
else {
int valid_perms;
subtract_from_entry(rentry, modifier, &valid_perms);
if (valid_perms == 0)
acl_delete_entry(oacl, rentry);
}
}
}
if (0 == match_found) {
warnx("Entry not found when attempting delete '%s'",path);
retval = 1;
}
}
} else if (optflags & ACL_REWRITE_FLAG) {
acl_entry_t rentry;
if (-1 == position) {
usage();
}
if (0 == flag_new_acl) {
if (0 != acl_get_entry(oacl, position,
&rentry))
err(1, "Invalid entry number '%s'", path);
if (0 != acl_delete_entry(oacl, rentry))
err(1, "Unable to delete entry '%s'", path);
}
if (0!= acl_create_entry_np(&oacl, &newent, position))
err(1, "acl_create_entry() failed");
acl_copy_entry(newent, modifier);
}
ma_exit:
*oaclp = oacl;
return retval;
}
int
modify_file_acl(unsigned int optflags, const char *path, acl_t modifier, int position, int inheritance_level, int follow) {
acl_t oacl = NULL;
unsigned aindex = 0, flag_new_acl = 0;
acl_entry_t newent = NULL;
acl_entry_t entry = NULL;
unsigned retval = 0;
extern int fflag;
if (path == NULL)
usage();
if (optflags & ACL_CLEAR_FLAG) {
filesec_t fsec = filesec_init();
if (fsec == NULL) {
err(1, "filesec_init() failed");
}
if (filesec_set_property(fsec, FILESEC_ACL, _FILESEC_REMOVE_ACL) != 0) {
err(1, "filesec_set_property() failed");
}
if (follow) {
if (chmodx_np(path, fsec) != 0) {
if (!fflag) {
warn("Failed to clear ACL on file %s", path);
}
retval = 1;
}
} else {
int fd = open(path, O_SYMLINK);
if (fd != -1) {
if (fchmodx_np(fd, fsec) != 0) {
if (!fflag) {
warn("Failed to clear ACL on file %s", path);
}
retval = 1;
}
close(fd);
} else {
if (!fflag) {
warn("Failed to open file %s", path);
}
retval = 1;
}
}
filesec_free(fsec);
return (retval);
}
if (optflags & ACL_FROM_STDIN) {
oacl = acl_dup(modifier);
} else {
if (follow) {
oacl = acl_get_file(path, ACL_TYPE_EXTENDED);
} else {
int fd = open(path, O_SYMLINK);
if (fd != -1) {
oacl = acl_get_fd_np(fd, ACL_TYPE_EXTENDED);
close(fd);
}
}
if ((oacl == NULL) ||
(acl_get_entry(oacl,ACL_FIRST_ENTRY, &newent) != 0)) {
if ((oacl = acl_init(1)) == NULL)
err(1, "acl_init() failed");
flag_new_acl = 1;
position = 0;
}
if ((0 == flag_new_acl) && (optflags & (ACL_REMOVE_INHERIT_FLAG |
ACL_REMOVE_INHERITED_ENTRIES))) {
acl_t facl = NULL;
if ((facl = acl_init(1)) == NULL)
err(1, "acl_init() failed");
for (aindex = 0;
acl_get_entry(oacl,
(entry == NULL ? ACL_FIRST_ENTRY :
ACL_NEXT_ENTRY), &entry) == 0;
aindex++) {
acl_flagset_t eflags;
acl_entry_t fent = NULL;
if (acl_get_flagset_np(entry, &eflags) != 0) {
err(1, "Unable to obtain flagset");
}
if (acl_get_flag_np(eflags, ACL_ENTRY_INHERITED)) {
if (optflags & ACL_REMOVE_INHERIT_FLAG) {
acl_delete_flag_np(eflags, ACL_ENTRY_INHERITED);
acl_set_flagset_np(entry, eflags);
acl_create_entry(&facl, &fent);
acl_copy_entry(fent, entry);
}
}
else {
acl_create_entry(&facl, &fent);
acl_copy_entry(fent, entry);
}
}
if (oacl)
acl_free(oacl);
oacl = facl;
} else if (optflags & ACL_TO_STDOUT) {
ssize_t len;
char *text = acl_to_text(oacl, &len);
puts(text);
acl_free(text);
} else if (optflags & ACL_CHECK_CANONICITY) {
if (flag_new_acl) {
warnx("No ACL currently associated with file '%s'", path);
}
retval = is_canonical(oacl);
} else if ((optflags & ACL_SET_FLAG) && (position == -1) &&
(!is_canonical(oacl))) {
warnx("The specified file '%s' does not have an ACL in canonical order, please specify a position with +a# ", path);
retval = 1;
} else if (((optflags & ACL_DELETE_FLAG) && (position != -1))
|| (optflags & ACL_CHECK_CANONICITY)) {
retval = modify_acl(&oacl, NULL, optflags, position,
inheritance_level, flag_new_acl, path);
} else if ((optflags & (ACL_REMOVE_INHERIT_FLAG|ACL_REMOVE_INHERITED_ENTRIES)) && flag_new_acl) {
warnx("No ACL currently associated with file '%s'", path);
retval = 1;
} else {
if (!modifier) {
errx(1, "Internal error: modifier should not be NULL");
}
for (aindex = 0;
acl_get_entry(modifier,
(entry == NULL ? ACL_FIRST_ENTRY :
ACL_NEXT_ENTRY), &entry) == 0;
aindex++) {
retval += modify_acl(&oacl, entry, optflags,
position, inheritance_level,
flag_new_acl, path);
}
}
}
if (!(optflags & (ACL_TO_STDOUT|ACL_CHECK_CANONICITY))) {
int status = -1;
if (follow) {
status = acl_set_file(path, ACL_TYPE_EXTENDED, oacl);
} else {
int fd = open(path, O_SYMLINK);
if (fd != -1) {
status = acl_set_fd_np(fd, oacl,
ACL_TYPE_EXTENDED);
close(fd);
}
}
if (status != 0) {
if (!fflag)
warn("Failed to set ACL on file '%s'", path);
retval = 1;
}
}
if (oacl)
acl_free(oacl);
return retval;
}
#endif