| /* |
| File: getfacl.c |
| (Linux Access Control List Management) |
| |
| Copyright (C) 1999-2002 |
| Andreas Gruenbacher, <a.gruenbacher@bestbits.at> |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or (at |
| your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this library; if not, write to the Free Software |
| Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
| USA. |
| */ |
| |
| #include "config.h" |
| #include <stdio.h> |
| #include <errno.h> |
| #include <sys/acl.h> |
| #include <acl/libacl.h> |
| |
| #include <limits.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <dirent.h> |
| #include <libgen.h> |
| #include <getopt.h> |
| #include "misc.h" |
| #include "user_group.h" |
| #include "walk_tree.h" |
| |
| #define POSIXLY_CORRECT_STR "POSIXLY_CORRECT" |
| |
| #if !POSIXLY_CORRECT |
| # define CMD_LINE_OPTIONS "aceEsRLPtpndvh" |
| #endif |
| #define POSIXLY_CMD_LINE_OPTIONS "d" |
| |
| struct option long_options[] = { |
| #if !POSIXLY_CORRECT |
| { "access", 0, 0, 'a' }, |
| { "omit-header", 0, 0, 'c' }, |
| { "all-effective", 0, 0, 'e' }, |
| { "no-effective", 0, 0, 'E' }, |
| { "skip-base", 0, 0, 's' }, |
| { "recursive", 0, 0, 'R' }, |
| { "logical", 0, 0, 'L' }, |
| { "physical", 0, 0, 'P' }, |
| { "tabular", 0, 0, 't' }, |
| { "absolute-names", 0, 0, 'p' }, |
| { "numeric", 0, 0, 'n' }, |
| #endif |
| { "default", 0, 0, 'd' }, |
| { "version", 0, 0, 'v' }, |
| { "help", 0, 0, 'h' }, |
| { NULL, 0, 0, 0 } |
| }; |
| |
| const char *progname; |
| const char *cmd_line_options; |
| |
| int walk_flags = WALK_TREE_DEREFERENCE_TOPLEVEL; |
| int opt_print_acl; |
| int opt_print_default_acl; |
| int opt_strip_leading_slash = 1; |
| int opt_comments = 1; /* include comments */ |
| int opt_skip_base; /* skip files that only have the base entries */ |
| int opt_tabular; /* tabular output format (alias `showacl') */ |
| #if POSIXLY_CORRECT |
| const int posixly_correct = 1; /* Posix compatible behavior! */ |
| #else |
| int posixly_correct; /* Posix compatible behavior? */ |
| #endif |
| int had_errors; |
| int absolute_warning; /* Absolute path warning was issued */ |
| int print_options = TEXT_SOME_EFFECTIVE; |
| int opt_numeric; /* don't convert id's to symbolic names */ |
| |
| |
| static const char *xquote(const char *str, const char *quote_chars) |
| { |
| const char *q = quote(str, quote_chars); |
| if (q == NULL) { |
| fprintf(stderr, "%s: %s\n", progname, strerror(errno)); |
| exit(1); |
| } |
| return q; |
| } |
| |
| struct name_list { |
| struct name_list *next; |
| char name[0]; |
| }; |
| |
| void free_list(struct name_list *names) |
| { |
| struct name_list *next; |
| |
| while (names) { |
| next = names->next; |
| free(names); |
| names = next; |
| } |
| } |
| |
| struct name_list *get_list(const struct stat *st, acl_t acl) |
| { |
| struct name_list *first = NULL, *last = NULL; |
| acl_entry_t ent; |
| int ret = 0; |
| |
| if (acl != NULL) |
| ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &ent); |
| if (ret != 1) |
| return NULL; |
| while (ret > 0) { |
| acl_tag_t e_type; |
| const id_t *id_p; |
| const char *name = ""; |
| int len; |
| |
| acl_get_tag_type(ent, &e_type); |
| switch(e_type) { |
| case ACL_USER_OBJ: |
| name = user_name(st->st_uid, opt_numeric); |
| break; |
| |
| case ACL_USER: |
| id_p = acl_get_qualifier(ent); |
| if (id_p != NULL) |
| name = user_name(*id_p, opt_numeric); |
| break; |
| |
| case ACL_GROUP_OBJ: |
| name = group_name(st->st_gid, opt_numeric); |
| break; |
| |
| case ACL_GROUP: |
| id_p = acl_get_qualifier(ent); |
| if (id_p != NULL) |
| name = group_name(*id_p, opt_numeric); |
| break; |
| } |
| name = xquote(name, "\t\n\r"); |
| len = strlen(name); |
| if (last == NULL) { |
| first = last = (struct name_list *) |
| malloc(sizeof(struct name_list) + len + 1); |
| } else { |
| last->next = (struct name_list *) |
| malloc(sizeof(struct name_list) + len + 1); |
| last = last->next; |
| } |
| if (last == NULL) { |
| free_list(first); |
| return NULL; |
| } |
| last->next = NULL; |
| strcpy(last->name, name); |
| |
| ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &ent); |
| } |
| return first; |
| } |
| |
| int max_name_length(struct name_list *names) |
| { |
| int max_len = 0; |
| while (names != NULL) { |
| struct name_list *next = names->next; |
| int len = strlen(names->name); |
| |
| if (len > max_len) |
| max_len = len; |
| names = next; |
| } |
| return max_len; |
| } |
| |
| int names_width; |
| |
| struct acl_perm_def { |
| acl_tag_t tag; |
| char c; |
| }; |
| |
| struct acl_perm_def acl_perm_defs[] = { |
| { ACL_READ, 'r' }, |
| { ACL_WRITE, 'w' }, |
| { ACL_EXECUTE, 'x' }, |
| { 0, 0 } |
| }; |
| |
| #define ACL_PERMS (sizeof(acl_perm_defs) / sizeof(struct acl_perm_def) - 1) |
| |
| void acl_perm_str(acl_entry_t entry, char *str) |
| { |
| acl_permset_t permset; |
| int n; |
| |
| acl_get_permset(entry, &permset); |
| for (n = 0; n < (int) ACL_PERMS; n++) { |
| str[n] = (acl_get_perm(permset, acl_perm_defs[n].tag) ? |
| acl_perm_defs[n].c : '-'); |
| } |
| str[n] = '\0'; |
| } |
| |
| void acl_mask_perm_str(acl_t acl, char *str) |
| { |
| acl_entry_t entry; |
| |
| str[0] = '\0'; |
| if (acl_get_entry(acl, ACL_FIRST_ENTRY, &entry) != 1) |
| return; |
| for(;;) { |
| acl_tag_t tag; |
| |
| acl_get_tag_type(entry, &tag); |
| if (tag == ACL_MASK) { |
| acl_perm_str(entry, str); |
| return; |
| } |
| if (acl_get_entry(acl, ACL_NEXT_ENTRY, &entry) != 1) |
| return; |
| } |
| } |
| |
| void apply_mask(char *perm, const char *mask) |
| { |
| while (*perm) { |
| if (*mask == '-' && *perm >= 'a' && *perm <= 'z') |
| *perm = *perm - 'a' + 'A'; |
| perm++; |
| if (*mask) |
| mask++; |
| } |
| } |
| |
| int show_line(FILE *stream, struct name_list **acl_names, acl_t acl, |
| acl_entry_t *acl_ent, const char *acl_mask, |
| struct name_list **dacl_names, acl_t dacl, |
| acl_entry_t *dacl_ent, const char *dacl_mask) |
| { |
| acl_tag_t tag_type; |
| const char *tag, *name; |
| char acl_perm[ACL_PERMS+1], dacl_perm[ACL_PERMS+1]; |
| |
| if (acl) { |
| acl_get_tag_type(*acl_ent, &tag_type); |
| name = (*acl_names)->name; |
| } else { |
| acl_get_tag_type(*dacl_ent, &tag_type); |
| name = (*dacl_names)->name; |
| } |
| |
| switch(tag_type) { |
| case ACL_USER_OBJ: |
| tag = "USER"; |
| break; |
| case ACL_USER: |
| tag = "user"; |
| break; |
| case ACL_GROUP_OBJ: |
| tag = "GROUP"; |
| break; |
| case ACL_GROUP: |
| tag = "group"; |
| break; |
| case ACL_MASK: |
| tag = "mask"; |
| break; |
| case ACL_OTHER: |
| tag = "other"; |
| break; |
| default: |
| return -1; |
| } |
| |
| memset(acl_perm, ' ', ACL_PERMS); |
| acl_perm[ACL_PERMS] = '\0'; |
| if (acl_ent) { |
| acl_perm_str(*acl_ent, acl_perm); |
| if (tag_type != ACL_USER_OBJ && tag_type != ACL_OTHER && |
| tag_type != ACL_MASK) |
| apply_mask(acl_perm, acl_mask); |
| } |
| memset(dacl_perm, ' ', ACL_PERMS); |
| dacl_perm[ACL_PERMS] = '\0'; |
| if (dacl_ent) { |
| acl_perm_str(*dacl_ent, dacl_perm); |
| if (tag_type != ACL_USER_OBJ && tag_type != ACL_OTHER && |
| tag_type != ACL_MASK) |
| apply_mask(dacl_perm, dacl_mask); |
| } |
| |
| fprintf(stream, "%-5s %*s %*s %*s\n", |
| tag, -names_width, name, |
| -(int)ACL_PERMS, acl_perm, |
| -(int)ACL_PERMS, dacl_perm); |
| |
| if (acl_names) { |
| acl_get_entry(acl, ACL_NEXT_ENTRY, acl_ent); |
| (*acl_names) = (*acl_names)->next; |
| } |
| if (dacl_names) { |
| acl_get_entry(dacl, ACL_NEXT_ENTRY, dacl_ent); |
| (*dacl_names) = (*dacl_names)->next; |
| } |
| return 0; |
| } |
| |
| int do_show(FILE *stream, const char *path_p, const struct stat *st, |
| acl_t acl, acl_t dacl) |
| { |
| struct name_list *acl_names = get_list(st, acl), |
| *first_acl_name = acl_names; |
| struct name_list *dacl_names = get_list(st, dacl), |
| *first_dacl_name = dacl_names; |
| |
| int acl_names_width = max_name_length(acl_names); |
| int dacl_names_width = max_name_length(dacl_names); |
| acl_entry_t acl_ent; |
| acl_entry_t dacl_ent; |
| char acl_mask[ACL_PERMS+1], dacl_mask[ACL_PERMS+1]; |
| int ret; |
| |
| names_width = 8; |
| if (acl_names_width > names_width) |
| names_width = acl_names_width; |
| if (dacl_names_width > names_width) |
| names_width = dacl_names_width; |
| |
| acl_mask[0] = '\0'; |
| if (acl) { |
| acl_mask_perm_str(acl, acl_mask); |
| ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &acl_ent); |
| if (ret == 0) |
| acl = NULL; |
| if (ret < 0) |
| return ret; |
| } |
| dacl_mask[0] = '\0'; |
| if (dacl) { |
| acl_mask_perm_str(dacl, dacl_mask); |
| ret = acl_get_entry(dacl, ACL_FIRST_ENTRY, &dacl_ent); |
| if (ret == 0) |
| dacl = NULL; |
| if (ret < 0) |
| return ret; |
| } |
| fprintf(stream, "# file: %s\n", xquote(path_p, "\n\r")); |
| while (acl_names != NULL || dacl_names != NULL) { |
| acl_tag_t acl_tag, dacl_tag; |
| |
| if (acl) |
| acl_get_tag_type(acl_ent, &acl_tag); |
| if (dacl) |
| acl_get_tag_type(dacl_ent, &dacl_tag); |
| |
| if (acl && (!dacl || acl_tag < dacl_tag)) { |
| show_line(stream, &acl_names, acl, &acl_ent, acl_mask, |
| NULL, NULL, NULL, NULL); |
| continue; |
| } else if (dacl && (!acl || dacl_tag < acl_tag)) { |
| show_line(stream, NULL, NULL, NULL, NULL, |
| &dacl_names, dacl, &dacl_ent, dacl_mask); |
| continue; |
| } else { |
| if (acl_tag == ACL_USER || acl_tag == ACL_GROUP) { |
| id_t *acl_id_p = NULL, *dacl_id_p = NULL; |
| if (acl_ent) |
| acl_id_p = acl_get_qualifier(acl_ent); |
| if (dacl_ent) |
| dacl_id_p = acl_get_qualifier(dacl_ent); |
| |
| if (acl && (!dacl || *acl_id_p < *dacl_id_p)) { |
| show_line(stream, &acl_names, acl, |
| &acl_ent, acl_mask, |
| NULL, NULL, NULL, NULL); |
| continue; |
| } else if (dacl && |
| (!acl || *dacl_id_p < *acl_id_p)) { |
| show_line(stream, NULL, NULL, NULL, |
| NULL, &dacl_names, dacl, |
| &dacl_ent, dacl_mask); |
| continue; |
| } |
| } |
| show_line(stream, &acl_names, acl, &acl_ent, acl_mask, |
| &dacl_names, dacl, &dacl_ent, dacl_mask); |
| } |
| } |
| |
| free_list(first_acl_name); |
| free_list(first_dacl_name); |
| |
| return 0; |
| } |
| |
| /* |
| * Create an ACL from the file permission bits |
| * of the file PATH_P. |
| */ |
| static acl_t |
| acl_get_file_mode(const char *path_p) |
| { |
| struct stat st; |
| |
| if (stat(path_p, &st) != 0) |
| return NULL; |
| return acl_from_mode(st.st_mode); |
| } |
| |
| static const char * |
| flagstr(mode_t mode) |
| { |
| static char str[4]; |
| |
| str[0] = (mode & S_ISUID) ? 's' : '-'; |
| str[1] = (mode & S_ISGID) ? 's' : '-'; |
| str[2] = (mode & S_ISVTX) ? 't' : '-'; |
| str[3] = '\0'; |
| return str; |
| } |
| |
| int do_print(const char *path_p, const struct stat *st, int walk_flags, void *unused) |
| { |
| const char *default_prefix = NULL; |
| acl_t acl = NULL, default_acl = NULL; |
| int error = 0; |
| |
| if (walk_flags & WALK_TREE_FAILED) { |
| fprintf(stderr, "%s: %s: %s\n", progname, xquote(path_p, "\n\r"), |
| strerror(errno)); |
| return 1; |
| } |
| |
| /* |
| * Symlinks can never have ACLs, so when doing a physical walk, we |
| * skip symlinks altogether, and when doing a half-logical walk, we |
| * skip all non-toplevel symlinks. |
| */ |
| if ((walk_flags & WALK_TREE_SYMLINK) && |
| ((walk_flags & WALK_TREE_PHYSICAL) || |
| !(walk_flags & (WALK_TREE_TOPLEVEL | WALK_TREE_LOGICAL)))) |
| return 0; |
| |
| if (opt_print_acl) { |
| acl = acl_get_file(path_p, ACL_TYPE_ACCESS); |
| if (acl == NULL && (errno == ENOSYS || errno == ENOTSUP)) |
| acl = acl_get_file_mode(path_p); |
| if (acl == NULL) |
| goto fail; |
| } |
| |
| if (opt_print_default_acl && S_ISDIR(st->st_mode)) { |
| default_acl = acl_get_file(path_p, ACL_TYPE_DEFAULT); |
| if (default_acl == NULL) { |
| if (errno != ENOSYS && errno != ENOTSUP) |
| goto fail; |
| } else if (acl_entries(default_acl) == 0) { |
| acl_free(default_acl); |
| default_acl = NULL; |
| } |
| } |
| |
| if (opt_skip_base && |
| (!acl || acl_equiv_mode(acl, NULL) == 0) && !default_acl) |
| return 0; |
| |
| if (opt_print_acl && opt_print_default_acl) |
| default_prefix = "default:"; |
| |
| if (opt_strip_leading_slash) { |
| if (*path_p == '/') { |
| if (!absolute_warning) { |
| fprintf(stderr, _("%s: Removing leading " |
| "'/' from absolute path names\n"), |
| progname); |
| absolute_warning = 1; |
| } |
| while (*path_p == '/') |
| path_p++; |
| } else if (*path_p == '.' && *(path_p+1) == '/') |
| while (*++path_p == '/') |
| /* nothing */ ; |
| if (*path_p == '\0') |
| path_p = "."; |
| } |
| |
| if (opt_tabular) { |
| if (do_show(stdout, path_p, st, acl, default_acl) != 0) |
| goto fail; |
| } else { |
| if (opt_comments) { |
| printf("# file: %s\n", xquote(path_p, "\n\r")); |
| printf("# owner: %s\n", |
| xquote(user_name(st->st_uid, opt_numeric), " \t\n\r")); |
| printf("# group: %s\n", |
| xquote(group_name(st->st_gid, opt_numeric), " \t\n\r")); |
| if ((st->st_mode & (S_ISVTX | S_ISUID | S_ISGID)) && !posixly_correct) |
| printf("# flags: %s\n", flagstr(st->st_mode)); |
| } |
| if (acl != NULL) { |
| char *acl_text = acl_to_any_text(acl, NULL, '\n', |
| print_options); |
| if (!acl_text) |
| goto fail; |
| if (puts(acl_text) < 0) { |
| acl_free(acl_text); |
| goto fail; |
| } |
| acl_free(acl_text); |
| } |
| if (default_acl != NULL) { |
| char *acl_text = acl_to_any_text(default_acl, |
| default_prefix, '\n', |
| print_options); |
| if (!acl_text) |
| goto fail; |
| if (puts(acl_text) < 0) { |
| acl_free(acl_text); |
| goto fail; |
| } |
| acl_free(acl_text); |
| } |
| } |
| if (acl || default_acl || opt_comments) |
| printf("\n"); |
| |
| cleanup: |
| if (acl) |
| acl_free(acl); |
| if (default_acl) |
| acl_free(default_acl); |
| return error; |
| |
| fail: |
| fprintf(stderr, "%s: %s: %s\n", progname, xquote(path_p, "\n\r"), |
| strerror(errno)); |
| error = -1; |
| goto cleanup; |
| } |
| |
| |
| void help(void) |
| { |
| printf(_("%s %s -- get file access control lists\n"), |
| progname, VERSION); |
| printf(_("Usage: %s [-%s] file ...\n"), |
| progname, cmd_line_options); |
| #if !POSIXLY_CORRECT |
| if (posixly_correct) { |
| #endif |
| printf(_( |
| " -d, --default display the default access control list\n")); |
| #if !POSIXLY_CORRECT |
| } else { |
| printf(_( |
| " -a, --access display the file access control list only\n" |
| " -d, --default display the default access control list only\n" |
| " -c, --omit-header do not display the comment header\n" |
| " -e, --all-effective print all effective rights\n" |
| " -E, --no-effective print no effective rights\n" |
| " -s, --skip-base skip files that only have the base entries\n" |
| " -R, --recursive recurse into subdirectories\n" |
| " -L, --logical logical walk, follow symbolic links\n" |
| " -P, --physical physical walk, do not follow symbolic links\n" |
| " -t, --tabular use tabular output format\n" |
| " -n, --numeric print numeric user/group identifiers\n" |
| " -p, --absolute-names don't strip leading '/' in pathnames\n")); |
| } |
| #endif |
| printf(_( |
| " -v, --version print version and exit\n" |
| " -h, --help this help text\n")); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int opt; |
| char *line; |
| |
| progname = basename(argv[0]); |
| |
| #if POSIXLY_CORRECT |
| cmd_line_options = POSIXLY_CMD_LINE_OPTIONS; |
| #else |
| if (getenv(POSIXLY_CORRECT_STR)) |
| posixly_correct = 1; |
| if (!posixly_correct) |
| cmd_line_options = CMD_LINE_OPTIONS; |
| else |
| cmd_line_options = POSIXLY_CMD_LINE_OPTIONS; |
| #endif |
| |
| setlocale(LC_CTYPE, ""); |
| setlocale(LC_MESSAGES, ""); |
| bindtextdomain(PACKAGE, LOCALEDIR); |
| textdomain(PACKAGE); |
| |
| /* Align `#effective:' comments to column 40 for tty's */ |
| if (!posixly_correct && isatty(fileno(stdout))) |
| print_options |= TEXT_SMART_INDENT; |
| |
| while ((opt = getopt_long(argc, argv, cmd_line_options, |
| long_options, NULL)) != -1) { |
| switch (opt) { |
| case 'a': /* acl only */ |
| if (posixly_correct) |
| goto synopsis; |
| opt_print_acl = 1; |
| break; |
| |
| case 'd': /* default acl only */ |
| opt_print_default_acl = 1; |
| break; |
| |
| case 'c': /* no comments */ |
| if (posixly_correct) |
| goto synopsis; |
| opt_comments = 0; |
| break; |
| |
| case 'e': /* all #effective comments */ |
| if (posixly_correct) |
| goto synopsis; |
| print_options |= TEXT_ALL_EFFECTIVE; |
| break; |
| |
| case 'E': /* no #effective comments */ |
| if (posixly_correct) |
| goto synopsis; |
| print_options &= ~(TEXT_SOME_EFFECTIVE | |
| TEXT_ALL_EFFECTIVE); |
| break; |
| |
| case 'R': /* recursive */ |
| if (posixly_correct) |
| goto synopsis; |
| walk_flags |= WALK_TREE_RECURSIVE; |
| break; |
| |
| case 'L': /* follow all symlinks */ |
| if (posixly_correct) |
| goto synopsis; |
| walk_flags |= WALK_TREE_LOGICAL | WALK_TREE_DEREFERENCE; |
| walk_flags &= ~WALK_TREE_PHYSICAL; |
| break; |
| |
| case 'P': /* skip all symlinks */ |
| if (posixly_correct) |
| goto synopsis; |
| walk_flags |= WALK_TREE_PHYSICAL; |
| walk_flags &= ~(WALK_TREE_LOGICAL | WALK_TREE_DEREFERENCE | |
| WALK_TREE_DEREFERENCE_TOPLEVEL); |
| break; |
| |
| case 's': /* skip files with only base entries */ |
| if (posixly_correct) |
| goto synopsis; |
| opt_skip_base = 1; |
| break; |
| |
| case 'p': |
| if (posixly_correct) |
| goto synopsis; |
| opt_strip_leading_slash = 0; |
| break; |
| |
| case 't': |
| if (posixly_correct) |
| goto synopsis; |
| opt_tabular = 1; |
| break; |
| |
| case 'n': /* numeric */ |
| opt_numeric = 1; |
| print_options |= TEXT_NUMERIC_IDS; |
| break; |
| |
| case 'v': /* print version */ |
| printf("%s " VERSION "\n", progname); |
| return 0; |
| |
| case 'h': /* help */ |
| help(); |
| return 0; |
| |
| case ':': /* option missing */ |
| case '?': /* unknown option */ |
| default: |
| goto synopsis; |
| } |
| } |
| |
| if (!(opt_print_acl || opt_print_default_acl)) { |
| opt_print_acl = 1; |
| if (!posixly_correct) |
| opt_print_default_acl = 1; |
| } |
| |
| if ((optind == argc) && !posixly_correct) |
| goto synopsis; |
| |
| do { |
| if (optind == argc || |
| strcmp(argv[optind], "-") == 0) { |
| while ((line = next_line(stdin)) != NULL) { |
| if (*line == '\0') |
| continue; |
| |
| had_errors += walk_tree(line, walk_flags, 0, |
| do_print, NULL); |
| } |
| if (!feof(stdin)) { |
| fprintf(stderr, _("%s: Standard input: %s\n"), |
| progname, strerror(errno)); |
| had_errors++; |
| } |
| } else |
| had_errors += walk_tree(argv[optind], walk_flags, 0, |
| do_print, NULL); |
| optind++; |
| } while (optind < argc); |
| |
| return had_errors ? 1 : 0; |
| |
| synopsis: |
| fprintf(stderr, _("Usage: %s [-%s] file ...\n"), |
| progname, cmd_line_options); |
| fprintf(stderr, _("Try `%s --help' for more information.\n"), |
| progname); |
| return 2; |
| } |
| |