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