#ifdef __FreeBSD__ # define _WITH_GETLINE #else # define _POSIX_C_SOURCE 200809L # define _XOPEN_SOURCE 700 #endif #include <assert.h> #include <errno.h> #include <grp.h> #include <locale.h> #include <pwd.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/acl.h> #include <unistd.h> // Needed to access non-portable functions on Linux #ifdef __SYS_ACL_H # include <acl/libacl.h> #endif typedef enum { CMD_MODIFY_ACTION_EDIT, CMD_MODIFY_ACTION_DELETE, CMD_MODIFY_ACTION_LAST, } CmdModifyAction; static void print_function_result (bool success, const char* function){ if (success) { printf ("\033[1;32m%s\033[0m: OK\n", function); } else { printf ("\033[1;31m%s\033[0m: %s\n", function, strerror (errno)); } } static bool check_error_int (int rval, const char* function) { if (rval < 0){ print_function_result (false, function); return true; } else { print_function_result (true, function); return false; } } static bool check_error_ptr (void* rval, const char* function) { if (rval == NULL) { print_function_result (false, function); return true; } else { print_function_result (true, function); return false; } } static const char* read_input (const char* prompt) { static char* buffer_ptr = NULL; static size_t buffer_len = 0; clearerr (stdin); fputs (prompt, stdout); fflush (stdout); ssize_t str_len = getline (&buffer_ptr, &buffer_len, stdin); if (str_len < 0) { free (buffer_ptr); buffer_ptr = NULL; buffer_len = 0; return ""; } buffer_ptr[str_len - 1] = '\0'; return buffer_ptr; } static acl_type_t read_acl_type (void) { #define READ_ACL_TYPE_INVALID (-1) static_assert (ACL_TYPE_ACCESS != READ_ACL_TYPE_INVALID, "ACL_TYPE_ACCESS == READ_ACL_TYPE_INVALID"); static_assert (ACL_TYPE_DEFAULT != READ_ACL_TYPE_INVALID, "ACL_TYPE_DEFAULT == READ_ACL_TYPE_INVALID"); #ifdef ACL_TYPE_NFS4 static_assert (ACL_TYPE_NFS4 != READ_ACL_TYPE_INVALID, "ACL_TYPE_NFS4 == READ_ACL_TYPE_INVALID"); const char* input = read_input ("Mode [1=access 2=default 3=nfsv4]: "); #else const char* input = read_input ("Mode [1=access 2=default]: "); #endif switch (input[0]) { case '1': return ACL_TYPE_ACCESS; case '2': return ACL_TYPE_DEFAULT; #ifdef ACL_TYPE_NFS4 case '3': return ACL_TYPE_NFS4; #endif default: return READ_ACL_TYPE_INVALID; } return READ_ACL_TYPE_INVALID; } static void entry_display (acl_entry_t entry, unsigned int count) { acl_tag_t tag; acl_permset_t permset; void* qualifier_ptr; uid_t* uid_ptr; gid_t* gid_ptr; if (check_error_int (acl_get_tag_type (entry, &tag), "acl_get_tag_type")) { return; } if (tag == ACL_USER || tag == ACL_GROUP) { if (check_error_ptr (qualifier_ptr = acl_get_qualifier (entry), "acl_get_qualifier")) { return; } } if (check_error_int (acl_get_permset (entry, &permset), "acl_get_permset")) { return; } printf ("%u: ", count); switch (tag) { case ACL_USER: case ACL_USER_OBJ: putchar ('u'); break; case ACL_GROUP: case ACL_GROUP_OBJ: putchar ('g'); break; case ACL_OTHER: putchar ('o'); break; case ACL_MASK: putchar ('m'); } putchar (':'); if (tag == ACL_USER) { struct passwd* pwd; uid_ptr = qualifier_ptr; errno = 0; if ((pwd = getpwuid (*uid_ptr)) == NULL) { printf ("\033[1;31m%lu\033[0m", (unsigned long)*uid_ptr); } else { printf ("%s(%lu)", pwd->pw_name, (unsigned long)*uid_ptr); } } else if (tag == ACL_GROUP) { struct group* grp; gid_ptr = qualifier_ptr; errno = 0; if ((grp = getgrgid (*gid_ptr)) == NULL) { printf ("\033[1;31m%lu\033[0m", (unsigned long)*gid_ptr); } else { printf ("%s(%lu)", grp->gr_name, (unsigned long)*gid_ptr); } } putchar (':'); #ifdef _SYS_ACL_H_ // TrustedBSD and FreeBSD putchar (acl_get_perm_np (permset, ACL_READ) ? 'r' : '-'); putchar (acl_get_perm_np (permset, ACL_WRITE) ? 'w' : '-'); putchar (acl_get_perm_np (permset, ACL_EXECUTE) ? 'x' : '-'); #elif defined(__SYS_ACL_H) // Linux and possibly Hurd or other GNU systems putchar (acl_get_perm (permset, ACL_READ) ? 'r' : '-'); putchar (acl_get_perm (permset, ACL_WRITE) ? 'w' : '-'); putchar (acl_get_perm (permset, ACL_EXECUTE) ? 'x' : '-'); #else # error "Sorry, your operating system is not supported." #endif } static void entry_edit(acl_entry_t* entry){ acl_tag_t tag; const char* input; input = read_input ("Tag [u g uo go o m]: "); switch (input[0]) { case 'u': switch (input[1]) { case '\0': tag = ACL_USER; break; case 'o': tag = ACL_USER_OBJ; break; default: return; } break; case 'g': switch (input[1]){ case '\0': tag = ACL_GROUP; break; case 'o': tag = ACL_GROUP_OBJ; break; default: return; } break; case 'o': tag = ACL_OTHER; break; case 'm': tag = ACL_MASK; break; default: return; } if (check_error_int (acl_set_tag_type (*entry, tag), "acl_set_tag_type")) { return; } if (tag == ACL_USER) { input = read_input ("User [user name or #uid]: "); uid_t uid; if (input[0] == '#') { uid = (uid_t)atol (&input[1]); } else { struct passwd* pwd; errno = 0; if (check_error_ptr (pwd = getpwnam (input), "getpwnam")) { return; } uid = pwd->pw_uid; } if (check_error_int (acl_set_qualifier (*entry, &uid), "acl_set_qualifier")) { return; } } else if (tag == ACL_GROUP) { input = read_input ("Group [group name or #gid]: "); gid_t gid; if (input[0] == '#') { gid = (gid_t)atol (&input[1]); } else { struct group* grp; errno = 0; if (check_error_ptr (grp = getgrnam (input), "getgrnam")) { return; } gid = grp->gr_gid; } if (check_error_int (acl_set_qualifier (*entry, &gid), "acl_set_qualifier")) { return; } } input = read_input ("Permission: "); size_t len = strlen (input); acl_permset_t permset; if (check_error_int (acl_get_permset (*entry, &permset), "acl_get_permset")) { return; } if (check_error_int (acl_clear_perms (permset), "acl_clear_perms")) { return; } for (size_t i = 0; i < len; i++) { switch (input[i]) { case 'r': if (check_error_int (acl_add_perm (permset, ACL_READ), "acl_add_perm")) { return; } break; case 'w': if (check_error_int (acl_add_perm (permset, ACL_WRITE), "acl_add_perm")) { return; } break; case 'x': if (check_error_int (acl_add_perm (permset, ACL_EXECUTE), "acl_add_perm")) { return; } } } } static void cmd_add (acl_t* alist) { acl_entry_t entry; if (check_error_int (acl_create_entry (alist, &entry), "acl_create_entry")) { return; } entry_edit (&entry); } static void cmd_modify (const acl_t* alist, CmdModifyAction action) { acl_entry_t entry; int rval = acl_get_entry (*alist, ACL_FIRST_ENTRY, &entry); unsigned int count = 0; do { count++; switch (rval) { case 1: check_error_int (rval, "acl_get_entry"); break; case 0: return; case -1: check_error_int (rval, "acl_get_entry"); return; } entry_display (entry, count); putchar(' '); switch (action) { case CMD_MODIFY_ACTION_DELETE: { const char* input = read_input ("Delete [y/n/q]? "); switch (input[0]) { case 'q': return; case 'y': check_error_int (acl_delete_entry (*alist, entry), "acl_delete_entry"); } } break; case CMD_MODIFY_ACTION_EDIT: { const char* input = read_input ("Edit [y/n/q]? "); switch (input[0]) { case 'q': return; case 'y': entry_edit (&entry); } } break; default: puts ("This should never happen"); return; } } while ((rval = acl_get_entry (*alist, ACL_NEXT_ENTRY, &entry)) == 1); } static void cmd_read (acl_t* alist){ acl_t alist_new; char* name = strdup (read_input ("File name= ")); acl_type_t type = read_acl_type (); if (type == READ_ACL_TYPE_INVALID) { free (name); return; } if (!check_error_ptr (alist_new = acl_get_file (name, type), "acl_get_file")) { check_error_int (acl_free (*alist), "acl_free"); *alist = alist_new; } free (name); } static void cmd_write(const acl_t* alist){ char* name = strdup (read_input ("File name= ")); acl_type_t type = read_acl_type (); if (type == READ_ACL_TYPE_INVALID) { free (name); return; } check_error_int (acl_set_file (name, type, *alist), "acl_set_file"); free (name); } static void cmd_zzz (void){ const char* name = read_input ("File name= "); check_error_int (acl_delete_def_file (name), "acl_delete_def_file"); } int main (int argc, char* argv[]) { setlocale (LC_ALL, ""); acl_t alist; if (check_error_ptr (alist = acl_init (4), "acl_init")) { return 1; } while (true) { const char* input = read_input ("\033[1;33macl>\033[0m "); if (feof (stdin)) { goto loop_end; } switch (input[0]) { case 'a': cmd_add (&alist); break; case 'c': check_error_int (acl_calc_mask (&alist), "acl_calc_mask"); break; case 'd': cmd_modify (&alist, CMD_MODIFY_ACTION_DELETE); break; case 'e': cmd_modify (&alist, CMD_MODIFY_ACTION_EDIT); break; case 'i': { acl_t alist_new; int count; input = read_input ("Count= "); count = atoi (input); if (!check_error_ptr (alist_new = acl_init (count), "acl_init")) { check_error_int (acl_free (alist), "acl_free"); alist = alist_new; } } break; case 'p': { char* atext; if (!check_error_ptr (atext = acl_to_text (alist, NULL), "acl_to_text")) { fputs (atext, stdout); if (atext[strlen (atext) - 1] != '\n') { putchar ('\n'); } check_error_int (acl_free (atext), "acl_free"); } } break; case 'q': goto loop_end; case 'r': cmd_read (&alist); break; case 'v': if (acl_valid (alist) == 0) { puts ("Current ACL structure is \033[1;32mvalid\033[0m."); } else { puts ("Current ACL structure is \033[1;31minvalid\033[0m."); } break; case 'w': cmd_write (&alist); break; case 'z': cmd_zzz (); break; default: fputs ( " a Add an ACL entry\n" " c Automatically generate the mask value\n" " d Delete ACL entries\n" " e Edit ACL entries\n" " i Clear and initialize an empty ACL structure\n" " p Print current ACL structure\n" " q Quit this program\n" " r Read ACL structure from file\n" " v Verify current ACL\n" " w Write ACL structure to file\n" " z Delete default ACL entries from file\n" , stdout); } } loop_end: check_error_int (acl_free (alist), "acl_free"); return 0; }