]> gitweb.michael.orlitzky.com - apply-default-acl.git/blob - src/apply-default-acl.c
Begin using Doxygen-style comments.
[apply-default-acl.git] / src / apply-default-acl.c
1 /**
2 * @file apply-default-acl.c
3 *
4 * @brief The entire implementation.
5 *
6 */
7
8 /* On Linux, ftw.h needs this special voodoo to work. */
9 #define _XOPEN_SOURCE 500
10
11 #include <errno.h>
12 #include <ftw.h> /* nftw() et al. */
13 #include <getopt.h>
14 #include <libgen.h> /* dirname() */
15 #include <limits.h> /* PATH_MAX */
16 #include <stdbool.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <sys/stat.h>
21 #include <unistd.h>
22
23 /* ACLs */
24 #include <acl/libacl.h> /* acl_get_perm, not portable */
25 #include <sys/types.h>
26 #include <sys/acl.h>
27
28
29 /* Command-line options */
30 static bool no_exec_mask = false;
31
32
33 /**
34 * @brief Get the mode bits from the given path.
35 *
36 * @param path
37 * The path (file or directory) whose mode we want.
38 *
39 * @return A mode_t (st_mode) structure containing the mode bits.
40 * See sys/stat.h for details.
41 */
42 mode_t get_mode(const char* path) {
43 if (path == NULL) {
44 errno = ENOENT;
45 return -1;
46 }
47
48 struct stat s;
49 int result = stat(path, &s);
50
51 if (result == 0) {
52 return s.st_mode;
53 }
54 else {
55 /* errno will be set already by stat() */
56 return result;
57 }
58 }
59
60
61 /**
62 * @brief Determine whether or not the given path is a regular file.
63 *
64 * @param path
65 * The path to test.
66 *
67 * @return true if @c path is a regular file, false otherwise.
68 */
69 bool is_regular_file(const char* path) {
70 if (path == NULL) {
71 return false;
72 }
73
74 struct stat s;
75 int result = stat(path, &s);
76 if (result == 0) {
77 return S_ISREG(s.st_mode);
78 }
79 else {
80 return false;
81 }
82 }
83
84
85 /**
86 * @brief Determine whether or not the given path is a directory.
87 *
88 * @param path
89 * The path to test.
90 *
91 * @return true if @c path is a directory, false otherwise.
92 */
93 bool is_directory(const char* path) {
94 if (path == NULL) {
95 return false;
96 }
97
98 struct stat s;
99 int result = stat(path, &s);
100 if (result == 0) {
101 return S_ISDIR(s.st_mode);
102 }
103 else {
104 return false;
105 }
106 }
107
108
109
110 int acl_set_entry(acl_t* aclp,
111 acl_entry_t entry) {
112 /*
113 * Update or create the given entry.
114 */
115
116 acl_tag_t entry_tag;
117 int gt_result = acl_get_tag_type(entry, &entry_tag);
118 if (gt_result == -1) {
119 perror("acl_set_entry (acl_get_tag_type)");
120 return -1;
121 }
122
123 acl_permset_t entry_permset;
124 int ps_result = acl_get_permset(entry, &entry_permset);
125 if (ps_result == -1) {
126 perror("acl_set_entry (acl_get_permset)");
127 return -1;
128 }
129
130 acl_entry_t existing_entry;
131 /* Loop through the given ACL looking for matching entries. */
132 int result = acl_get_entry(*aclp, ACL_FIRST_ENTRY, &existing_entry);
133
134 while (result == 1) {
135 acl_tag_t existing_tag = ACL_UNDEFINED_TAG;
136 int tag_result = acl_get_tag_type(existing_entry, &existing_tag);
137
138 if (tag_result == -1) {
139 perror("set_acl_tag_permset (acl_get_tag_type)");
140 return -1;
141 }
142
143 if (existing_tag == entry_tag) {
144 if (entry_tag == ACL_USER_OBJ ||
145 entry_tag == ACL_GROUP_OBJ ||
146 entry_tag == ACL_OTHER) {
147 /* Only update for these three since all other tags will have
148 been wiped. */
149 acl_permset_t existing_permset;
150 int gep_result = acl_get_permset(existing_entry, &existing_permset);
151 if (gep_result == -1) {
152 perror("acl_set_entry (acl_get_permset)");
153 return -1;
154 }
155
156 int s_result = acl_set_permset(existing_entry, entry_permset);
157 if (s_result == -1) {
158 perror("acl_set_entry (acl_set_permset)");
159 return -1;
160 }
161
162 return 1;
163 }
164
165 }
166
167 result = acl_get_entry(*aclp, ACL_NEXT_ENTRY, &existing_entry);
168 }
169
170 /* This catches both the initial acl_get_entry and the ones at the
171 end of the loop. */
172 if (result == -1) {
173 perror("acl_set_entry (acl_get_entry)");
174 return -1;
175 }
176
177 /* If we've made it this far, we need to add a new entry to the
178 ACL. */
179 acl_entry_t new_entry;
180
181 /* We allocate memory here that we should release! */
182 int c_result = acl_create_entry(aclp, &new_entry);
183 if (c_result == -1) {
184 perror("acl_set_entry (acl_create_entry)");
185 return -1;
186 }
187
188 int st_result = acl_set_tag_type(new_entry, entry_tag);
189 if (st_result == -1) {
190 perror("acl_set_entry (acl_set_tag_type)");
191 return -1;
192 }
193
194 int s_result = acl_set_permset(new_entry, entry_permset);
195 if (s_result == -1) {
196 perror("acl_set_entry (acl_set_permset)");
197 return -1;
198 }
199
200 if (entry_tag == ACL_USER || entry_tag == ACL_GROUP) {
201 /* We need to set the qualifier too. */
202 void* entry_qual = acl_get_qualifier(entry);
203 if (entry_qual == (void*)NULL) {
204 perror("acl_set_entry (acl_get_qualifier)");
205 return -1;
206 }
207
208 int sq_result = acl_set_qualifier(new_entry, entry_qual);
209 if (sq_result == -1) {
210 perror("acl_set_entry (acl_set_qualifier)");
211 return -1;
212 }
213 }
214
215 return 1;
216 }
217
218
219
220 int acl_entry_count(acl_t* acl) {
221 /*
222 * Return the number of entries in acl, or -1 on error.
223 */
224 acl_entry_t entry;
225 int entry_count = 0;
226 int result = acl_get_entry(*acl, ACL_FIRST_ENTRY, &entry);
227
228 while (result == 1) {
229 entry_count++;
230 result = acl_get_entry(*acl, ACL_NEXT_ENTRY, &entry);
231 }
232
233 if (result == -1) {
234 perror("acl_is_minimal (acl_get_entry)");
235 return -1;
236 }
237
238 return entry_count;
239 }
240
241
242 int acl_is_minimal(acl_t* acl) {
243 /* An ACL is minimal if it has fewer than four entries. Return 0 for
244 * false, 1 for true, and -1 on error.
245 */
246
247 int ec = acl_entry_count(acl);
248 if (ec == -1) {
249 perror("acl_is_minimal (acl_entry_count)");
250 return -1;
251 }
252
253 if (ec < 4) {
254 return 1;
255 }
256 else {
257 return 0;
258 }
259 }
260
261
262 int acl_execute_masked(const char* path) {
263 /* Returns 1 i the given path has an ACL mask which denies
264 execute. Returns 0 if it does not (or if it has no ACL/mask at
265 all), and -1 on error. */
266
267 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
268
269 if (acl == (acl_t)NULL) {
270 return 0;
271 }
272
273 /* Our return value. */
274 int result = 0;
275
276 acl_entry_t entry;
277 int ge_result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
278
279 while (ge_result == 1) {
280 acl_tag_t tag = ACL_UNDEFINED_TAG;
281 int tag_result = acl_get_tag_type(entry, &tag);
282
283 if (tag_result == -1) {
284 perror("acl_execute_masked (acl_get_tag_type)");
285 result = -1;
286 goto cleanup;
287 }
288
289 if (tag == ACL_MASK) {
290 /* This is the mask entry, get its permissions, and see if
291 execute is specified. */
292 acl_permset_t permset;
293
294 int ps_result = acl_get_permset(entry, &permset);
295 if (ps_result == -1) {
296 perror("acl_execute_masked (acl_get_permset)");
297 result = -1;
298 goto cleanup;
299 }
300
301 int gp_result = acl_get_perm(permset, ACL_EXECUTE);
302 if (gp_result == -1) {
303 perror("acl_execute_masked (acl_get_perm)");
304 result = -1;
305 goto cleanup;
306 }
307
308 if (gp_result == 0) {
309 /* No execute bit set in the mask; execute not allowed. */
310 return 1;
311 }
312 }
313
314 ge_result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
315 }
316
317 cleanup:
318 acl_free(acl);
319 return result;
320 }
321
322
323 int any_can_execute_or_dir(const char* path) {
324 /* If the given path is a directory, returns 1. Otherwise, returns 1
325 * if any ACL entry has EFFECTIVE execute access, 0 if none do, and
326 * -1 on error.
327 *
328 * This is meant to somewhat mimic setfacl's handling of the capital
329 * 'X' perm, which allows execute access if the target is a
330 * directory or someone can already execute it. We differ in that we
331 * check the effective execute rather than just the execute bits.
332 */
333
334 if (is_directory(path)) {
335 /* That was easy... */
336 return 1;
337 }
338
339 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
340
341 if (acl == (acl_t)NULL) {
342 return 0;
343 }
344
345 /* Our return value. */
346 int result = 0;
347
348 if (acl_is_minimal(&acl)) {
349 mode_t mode = get_mode(path);
350 if (mode & (S_IXUSR | S_IXOTH | S_IXGRP)) {
351 result = 1;
352 goto cleanup;
353 }
354 else {
355 result = 0;
356 goto cleanup;
357 }
358 }
359
360 acl_entry_t entry;
361 int ge_result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
362
363 while (ge_result == 1) {
364 acl_permset_t permset;
365
366 int ps_result = acl_get_permset(entry, &permset);
367 if (ps_result == -1) {
368 perror("any_can_execute_or_dir (acl_get_permset)");
369 result = -1;
370 goto cleanup;
371 }
372
373 int gp_result = acl_get_perm(permset, ACL_EXECUTE);
374 if (gp_result == -1) {
375 perror("any_can_execute_or_dir (acl_get_perm)");
376 result = -1;
377 goto cleanup;
378 }
379
380 if (gp_result == 1) {
381 /* Only return one if this execute bit is not masked. */
382 if (acl_execute_masked(path) != 1) {
383 result = 1;
384 goto cleanup;
385 }
386 }
387
388 ge_result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
389 }
390
391 if (ge_result == -1) {
392 perror("any_can_execute_or_dir (acl_get_entry)");
393 result = -1;
394 goto cleanup;
395 }
396
397 cleanup:
398 acl_free(acl);
399 return result;
400 }
401
402
403 int inherit_default_acl(const char* path, const char* parent) {
404 /* Inherit the default ACL from parent to path. This overwrites any
405 * existing default ACL. Returns 1 for success, 0 for failure, and
406 * -1 on error.
407 */
408
409 /* Our return value. */
410 int result = 1;
411
412 if (path == NULL) {
413 errno = ENOENT;
414 return -1;
415 }
416
417 if (!is_directory(path) || !is_directory(parent)) {
418 return 0;
419 }
420
421 acl_t parent_acl = acl_get_file(parent, ACL_TYPE_DEFAULT);
422 if (parent_acl == (acl_t)NULL) {
423 return 0;
424 }
425
426 acl_t path_acl = acl_dup(parent_acl);
427
428 if (path_acl == (acl_t)NULL) {
429 perror("inherit_default_acl (acl_dup)");
430 acl_free(parent_acl);
431 return -1;
432 }
433
434 int sf_result = acl_set_file(path, ACL_TYPE_DEFAULT, path_acl);
435 if (sf_result == -1) {
436 perror("inherit_default_acl (acl_set_file)");
437 result = -1;
438 goto cleanup;
439 }
440
441 cleanup:
442 acl_free(path_acl);
443 return result;
444 }
445
446
447 int wipe_acls(const char* path) {
448 /* Remove ACL_USER, ACL_GROUP, and ACL_MASK entries from
449 path. Returns 1 for success, 0 for failure, and -1 on error. */
450
451 if (path == NULL) {
452 errno = ENOENT;
453 return -1;
454 }
455
456 /* Finally, remove individual named/mask entries. */
457 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
458 if (acl == (acl_t)NULL) {
459 perror("wipe_acls (acl_get_file)");
460 return -1;
461 }
462
463 /* Our return value. */
464 int result = 1;
465
466 acl_entry_t entry;
467 int ge_result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
468
469 while (ge_result == 1) {
470 int d_result = acl_delete_entry(acl, entry);
471 if (d_result == -1) {
472 perror("wipe_acls (acl_delete_entry)");
473 result = -1;
474 goto cleanup;
475 }
476
477 ge_result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
478 }
479
480 /* Catches the first acl_get_entry as well as the ones at the end of
481 the loop. */
482 if (ge_result == -1) {
483 perror("wipe_acls (acl_get_entry)");
484 result = -1;
485 goto cleanup;
486 }
487
488 int sf_result = acl_set_file(path, ACL_TYPE_ACCESS, acl);
489 if (sf_result == -1) {
490 perror("wipe_acls (acl_set_file)");
491 result = -1;
492 goto cleanup;
493 }
494
495 cleanup:
496 acl_free(acl);
497 return result;
498 }
499
500
501 int apply_default_acl(const char* path) {
502 /* Really apply the default ACL by looping through it. Returns one
503 * for success, zero for failure (i.e. no ACL), and -1 on unexpected
504 * errors.
505
506 */
507 if (path == NULL) {
508 return 0;
509 }
510
511 if (!is_regular_file(path) && !is_directory(path)) {
512 return 0;
513 }
514
515 /* dirname mangles its argument */
516 char path_copy[PATH_MAX];
517 strncpy(path_copy, path, PATH_MAX-1);
518 path_copy[PATH_MAX-1] = 0;
519
520 char* parent = dirname(path_copy);
521 if (!is_directory(parent)) {
522 /* Make sure dirname() did what we think it did. */
523 return 0;
524 }
525
526 /* Default to not masking the exec bit; i.e. applying the default
527 ACL literally. If --no-exec-mask was not specified, then we try
528 to "guess" whether or not to mask the exec bit. */
529 bool allow_exec = true;
530
531 if (!no_exec_mask) {
532 int ace_result = any_can_execute_or_dir(path);
533
534 if (ace_result == -1) {
535 perror("apply_default_acl (any_can_execute_or_dir)");
536 return -1;
537 }
538 allow_exec = (bool)ace_result;
539 }
540
541 acl_t defacl = acl_get_file(parent, ACL_TYPE_DEFAULT);
542
543 if (defacl == (acl_t)NULL) {
544 perror("apply_default_acl (acl_get_file)");
545 return -1;
546 }
547
548 /* Our return value. */
549 int result = 1;
550
551 int wipe_result = wipe_acls(path);
552 if (wipe_result == -1) {
553 perror("apply_default_acl (wipe_acls)");
554 result = -1;
555 goto cleanup;
556 }
557
558 /* Do this after wipe_acls(), otherwise we'll overwrite the wiped
559 ACL with this one. */
560 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
561 if (acl == (acl_t)NULL) {
562 perror("apply_default_acl (acl_get_file)");
563 return -1;
564 }
565
566 /* If it's a directory, inherit the parent's default. */
567 int inherit_result = inherit_default_acl(path, parent);
568 if (inherit_result == -1) {
569 perror("apply_default_acl (inherit_acls)");
570 result = -1;
571 goto cleanup;
572 }
573
574 acl_entry_t entry;
575 int ge_result = acl_get_entry(defacl, ACL_FIRST_ENTRY, &entry);
576
577 while (ge_result == 1) {
578 acl_tag_t tag = ACL_UNDEFINED_TAG;
579 int tag_result = acl_get_tag_type(entry, &tag);
580
581 if (tag_result == -1) {
582 perror("apply_default_acl (acl_get_tag_type)");
583 result = -1;
584 goto cleanup;
585 }
586
587
588 /* We've got an entry/tag from the default ACL. Get its permset. */
589 acl_permset_t permset;
590 int ps_result = acl_get_permset(entry, &permset);
591 if (ps_result == -1) {
592 perror("apply_default_acl (acl_get_permset)");
593 result = -1;
594 goto cleanup;
595 }
596
597 /* If this is a default mask, fix it up. */
598 if (tag == ACL_MASK ||
599 tag == ACL_USER_OBJ ||
600 tag == ACL_GROUP_OBJ ||
601 tag == ACL_OTHER) {
602
603 if (!allow_exec) {
604 /* The mask doesn't affect acl_user_obj, acl_group_obj (in
605 minimal ACLs) or acl_other entries, so if execute should be
606 masked, we have to do it manually. */
607 int d_result = acl_delete_perm(permset, ACL_EXECUTE);
608 if (d_result == -1) {
609 perror("apply_default_acl (acl_delete_perm)");
610 result = -1;
611 goto cleanup;
612 }
613
614 int sp_result = acl_set_permset(entry, permset);
615 if (sp_result == -1) {
616 perror("apply_default_acl (acl_set_permset)");
617 result = -1;
618 goto cleanup;
619 }
620 }
621 }
622
623 /* Finally, add the permset to the access ACL. */
624 int set_result = acl_set_entry(&acl, entry);
625 if (set_result == -1) {
626 perror("apply_default_acl (acl_set_entry)");
627 result = -1;
628 goto cleanup;
629 }
630
631 ge_result = acl_get_entry(defacl, ACL_NEXT_ENTRY, &entry);
632 }
633
634 /* Catches the first acl_get_entry as well as the ones at the end of
635 the loop. */
636 if (ge_result == -1) {
637 perror("apply_default_acl (acl_get_entry)");
638 result = -1;
639 goto cleanup;
640 }
641
642 int sf_result = acl_set_file(path, ACL_TYPE_ACCESS, acl);
643 if (sf_result == -1) {
644 perror("apply_default_acl (acl_set_file)");
645 result = -1;
646 goto cleanup;
647 }
648
649 cleanup:
650 acl_free(defacl);
651 return result;
652 }
653
654
655 void usage(char* program_name) {
656 /*
657 * Print usage information.
658 */
659 printf("Apply any applicable default ACLs to the given files or "
660 "directories.\n\n");
661 printf("Usage: %s [flags] <target1> [<target2> [ <target3>...]]\n\n",
662 program_name);
663 printf("Flags:\n");
664 printf(" -h, --help Print this help message\n");
665 printf(" -r, --recursive Act on any given directories recursively\n");
666 printf(" -x, --no-exec-mask Apply execute permissions unconditionally\n");
667
668 return;
669 }
670
671
672 int apply_default_acl_nftw(const char *target,
673 const struct stat *s,
674 int info,
675 struct FTW *ftw) {
676 /* A wrapper around the apply_default_acl() function for use with
677 * nftw(). We need to adjust the return value so that nftw() doesn't
678 * think we've failed.
679 */
680 bool reapp_result = apply_default_acl(target);
681 if (reapp_result) {
682 return 0;
683 }
684 else {
685 return 1;
686 }
687 }
688
689
690 bool apply_default_acl_recursive(const char *target) {
691 /* Attempt to apply default ACLs recursively. If target is a
692 * directory, we recurse through its entries. If not, we just
693 * apply the default ACL to target.
694 *
695 * We ignore symlinks for consistency with chmod -r.
696 *
697 */
698 if (!is_directory(target)) {
699 return apply_default_acl(target);
700 }
701
702 int max_levels = 256;
703 int flags = FTW_PHYS; /* Don't follow links. */
704
705 int nftw_result = nftw(target,
706 apply_default_acl_nftw,
707 max_levels,
708 flags);
709
710 if (nftw_result == 0) {
711 /* Success */
712 return true;
713 }
714
715 /* nftw will return -1 on error, or if the supplied function
716 * (apply_default_acl_nftw) returns a non-zero result, nftw will
717 * return that.
718 */
719 if (nftw_result == -1) {
720 perror("apply_default_acl_recursive (nftw)");
721 }
722
723 return false;
724 }
725
726
727 int main(int argc, char* argv[]) {
728 /*
729 * Call apply_default_acl on each command-line argument.
730 */
731 if (argc < 2) {
732 usage(argv[0]);
733 return EXIT_FAILURE;
734 }
735
736 bool recursive = false;
737 /* bool no_exec_mask is declared static/global */
738
739 struct option long_options[] = {
740 /* These options set a flag. */
741 {"help", no_argument, NULL, 'h'},
742 {"recursive", no_argument, NULL, 'r'},
743 {"no-exec-mask", no_argument, NULL, 'x'},
744 {NULL, 0, NULL, 0}
745 };
746
747 int opt = 0;
748
749 while ((opt = getopt_long(argc, argv, "hrx", long_options, NULL)) != -1) {
750 switch (opt) {
751 case 'h':
752 usage(argv[0]);
753 return EXIT_SUCCESS;
754 case 'r':
755 recursive = true;
756 break;
757 case 'x':
758 no_exec_mask = true;
759 break;
760 default:
761 usage(argv[0]);
762 return EXIT_FAILURE;
763 }
764 }
765
766 int result = EXIT_SUCCESS;
767
768 int arg_index = 1;
769 for (arg_index = optind; arg_index < argc; arg_index++) {
770 const char* target = argv[arg_index];
771 bool reapp_result = false;
772
773 if (recursive) {
774 reapp_result = apply_default_acl_recursive(target);
775 }
776 else {
777 /* It's either normal file, or we're not operating recursively. */
778 reapp_result = apply_default_acl(target);
779 }
780
781 if (!reapp_result) {
782 result = EXIT_FAILURE;
783 }
784 }
785
786 return result;
787 }