]> gitweb.michael.orlitzky.com - apply-default-acl.git/blob - src/apply-default-acl.c
Fix the bug from the latest test case.
[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 #define _GNU_SOURCE
11
12 #include <errno.h>
13 #include <ftw.h> /* nftw() et al. */
14 #include <getopt.h>
15 #include <libgen.h> /* dirname() */
16 #include <limits.h> /* PATH_MAX */
17 #include <stdbool.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23
24 /* ACLs */
25 #include <acl/libacl.h> /* acl_get_perm, not portable */
26 #include <sys/types.h>
27 #include <sys/acl.h>
28
29 /* Most of the libacl functions return 1 for success, 0 for failure,
30 and -1 on error */
31 #define ACL_ERROR -1
32 #define ACL_FAILURE 0
33 #define ACL_SUCCESS 1
34
35
36
37 /**
38 * @brief Get the mode bits from the given path.
39 *
40 * @param path
41 * The path (file or directory) whose mode we want.
42 *
43 * @return A mode_t (st_mode) structure containing the mode bits.
44 * See sys/stat.h for details.
45 */
46 mode_t get_mode(const char* path) {
47 if (path == NULL) {
48 errno = ENOENT;
49 return -1;
50 }
51
52 struct stat s;
53 int result = stat(path, &s);
54
55 if (result == 0) {
56 return s.st_mode;
57 }
58 else {
59 /* errno will be set already by stat() */
60 return result;
61 }
62 }
63
64
65
66 /**
67 * @brief Determine whether or not the given path is a regular file.
68 *
69 * @param path
70 * The path to test.
71 *
72 * @return true if @c path is a regular file, false otherwise.
73 */
74 bool is_regular_file(const char* path) {
75 if (path == NULL) {
76 return false;
77 }
78
79 struct stat s;
80 int result = stat(path, &s);
81 if (result == 0) {
82 return S_ISREG(s.st_mode);
83 }
84 else {
85 return false;
86 }
87 }
88
89
90
91 /**
92 * @brief Determine whether or not the given path is a directory.
93 *
94 * @param path
95 * The path to test.
96 *
97 * @return true if @c path is a directory, false otherwise.
98 */
99 bool is_directory(const char* path) {
100 if (path == NULL) {
101 return false;
102 }
103
104 struct stat s;
105 int result = stat(path, &s);
106 if (result == 0) {
107 return S_ISDIR(s.st_mode);
108 }
109 else {
110 return false;
111 }
112 }
113
114
115
116 /**
117 * @brief Update (or create) an entry in an @b minimal ACL.
118 *
119 * This function will not work if @c aclp contains extended
120 * entries. This is fine for our purposes, since we call @c wipe_acls
121 * on each path before applying the default to it.
122 *
123 * The assumption that there are no extended entries makes things much
124 * simpler. For example, we only have to update the @c ACL_USER_OBJ,
125 * @c ACL_GROUP_OBJ, and @c ACL_OTHER entries -- all others can simply
126 * be created anew. This means we don't have to fool around comparing
127 * named-user/group entries.
128 *
129 * @param aclp
130 * A pointer to the acl_t structure whose entry we want to modify.
131 *
132 * @param entry
133 * The new entry. If @c entry contains a user/group/other entry, we
134 * update the existing one. Otherwise we create a new entry.
135 *
136 * @return If there is an unexpected library error, @c ACL_ERROR is
137 * returned. Otherwise, @c ACL_SUCCESS.
138 *
139 */
140 int acl_set_entry(acl_t* aclp,
141 acl_entry_t entry) {
142
143 acl_tag_t entry_tag;
144 int gt_result = acl_get_tag_type(entry, &entry_tag);
145 if (gt_result == ACL_ERROR) {
146 perror("acl_set_entry (acl_get_tag_type)");
147 return ACL_ERROR;
148 }
149
150 acl_permset_t entry_permset;
151 int ps_result = acl_get_permset(entry, &entry_permset);
152 if (ps_result == ACL_ERROR) {
153 perror("acl_set_entry (acl_get_permset)");
154 return ACL_ERROR;
155 }
156
157 acl_entry_t existing_entry;
158 /* Loop through the given ACL looking for matching entries. */
159 int result = acl_get_entry(*aclp, ACL_FIRST_ENTRY, &existing_entry);
160
161 while (result == ACL_SUCCESS) {
162 acl_tag_t existing_tag = ACL_UNDEFINED_TAG;
163 int tag_result = acl_get_tag_type(existing_entry, &existing_tag);
164
165 if (tag_result == ACL_ERROR) {
166 perror("set_acl_tag_permset (acl_get_tag_type)");
167 return ACL_ERROR;
168 }
169
170 if (existing_tag == entry_tag) {
171 if (entry_tag == ACL_USER_OBJ ||
172 entry_tag == ACL_GROUP_OBJ ||
173 entry_tag == ACL_OTHER) {
174 /* Only update for these three since all other tags will have
175 been wiped. These three are guaranteed to exist, so if we
176 match one of them, we're allowed to return ACL_SUCCESS
177 below and bypass the rest of the function. */
178 acl_permset_t existing_permset;
179 int gep_result = acl_get_permset(existing_entry, &existing_permset);
180 if (gep_result == ACL_ERROR) {
181 perror("acl_set_entry (acl_get_permset)");
182 return ACL_ERROR;
183 }
184
185 int s_result = acl_set_permset(existing_entry, entry_permset);
186 if (s_result == ACL_ERROR) {
187 perror("acl_set_entry (acl_set_permset)");
188 return ACL_ERROR;
189 }
190
191 return ACL_SUCCESS;
192 }
193
194 }
195
196 result = acl_get_entry(*aclp, ACL_NEXT_ENTRY, &existing_entry);
197 }
198
199 /* This catches both the initial acl_get_entry and the ones at the
200 end of the loop. */
201 if (result == ACL_ERROR) {
202 perror("acl_set_entry (acl_get_entry)");
203 return ACL_ERROR;
204 }
205
206 /* If we've made it this far, we need to add a new entry to the
207 ACL. */
208 acl_entry_t new_entry;
209
210 /* We allocate memory here that we should release! */
211 int c_result = acl_create_entry(aclp, &new_entry);
212 if (c_result == ACL_ERROR) {
213 perror("acl_set_entry (acl_create_entry)");
214 return ACL_ERROR;
215 }
216
217 int st_result = acl_set_tag_type(new_entry, entry_tag);
218 if (st_result == ACL_ERROR) {
219 perror("acl_set_entry (acl_set_tag_type)");
220 return ACL_ERROR;
221 }
222
223 int s_result = acl_set_permset(new_entry, entry_permset);
224 if (s_result == ACL_ERROR) {
225 perror("acl_set_entry (acl_set_permset)");
226 return ACL_ERROR;
227 }
228
229 if (entry_tag == ACL_USER || entry_tag == ACL_GROUP) {
230 /* We need to set the qualifier too. */
231 void* entry_qual = acl_get_qualifier(entry);
232 if (entry_qual == (void*)NULL) {
233 perror("acl_set_entry (acl_get_qualifier)");
234 return ACL_ERROR;
235 }
236
237 int sq_result = acl_set_qualifier(new_entry, entry_qual);
238 if (sq_result == ACL_ERROR) {
239 perror("acl_set_entry (acl_set_qualifier)");
240 return ACL_ERROR;
241 }
242 }
243
244 return ACL_SUCCESS;
245 }
246
247
248
249 /**
250 * @brief Determine the number of entries in the given ACL.
251 *
252 * @param acl
253 * A pointer to an @c acl_t structure.
254 *
255 * @return Either the non-negative number of entries in @c acl, or
256 * @c ACL_ERROR on error.
257 */
258 int acl_entry_count(acl_t* acl) {
259
260 acl_entry_t entry;
261 int entry_count = 0;
262 int result = acl_get_entry(*acl, ACL_FIRST_ENTRY, &entry);
263
264 while (result == ACL_SUCCESS) {
265 entry_count++;
266 result = acl_get_entry(*acl, ACL_NEXT_ENTRY, &entry);
267 }
268
269 if (result == ACL_ERROR) {
270 perror("acl_entry_count (acl_get_entry)");
271 return ACL_ERROR;
272 }
273
274 return entry_count;
275 }
276
277
278
279 /**
280 * @brief Determine whether or not the given ACL is minimal.
281 *
282 * An ACL is minimal if it has fewer than four entries.
283 *
284 * @param acl
285 * A pointer to an acl_t structure.
286 *
287 * @return
288 * - @c ACL_SUCCESS - @c acl is minimal
289 * - @c ACL_FAILURE - @c acl is not minimal
290 * - @c ACL_ERROR - Unexpected library error
291 */
292 int acl_is_minimal(acl_t* acl) {
293
294 int ec = acl_entry_count(acl);
295
296 if (ec == ACL_ERROR) {
297 perror("acl_is_minimal (acl_entry_count)");
298 return ACL_ERROR;
299 }
300
301 if (ec < 4) {
302 return ACL_SUCCESS;
303 }
304 else {
305 return ACL_FAILURE;
306 }
307 }
308
309
310
311 /**
312 * @brief Determine whether the given path has an ACL whose mask
313 * denies execute.
314 *
315 * @param path
316 * The path to check.
317 *
318 * @return
319 * - @c ACL_SUCCESS - @c path has a mask which denies execute.
320 * - @c ACL_FAILURE - The ACL for @c path does not deny execute,
321 * or @c path has no extended ACL at all.
322 * - @c ACL_ERROR - Unexpected library error.
323 */
324 int acl_execute_masked(const char* path) {
325
326 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
327
328 if (acl == (acl_t)NULL) {
329 perror("acl_execute_masked (acl_get_file)");
330 return ACL_ERROR;
331 }
332
333 /* Our return value. */
334 int result = ACL_FAILURE;
335
336 acl_entry_t entry;
337 int ge_result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
338
339 while (ge_result == ACL_SUCCESS) {
340 acl_tag_t tag = ACL_UNDEFINED_TAG;
341 int tag_result = acl_get_tag_type(entry, &tag);
342
343 if (tag_result == ACL_ERROR) {
344 perror("acl_execute_masked (acl_get_tag_type)");
345 result = ACL_ERROR;
346 goto cleanup;
347 }
348
349 if (tag == ACL_MASK) {
350 /* This is the mask entry, get its permissions, and see if
351 execute is specified. */
352 acl_permset_t permset;
353
354 int ps_result = acl_get_permset(entry, &permset);
355 if (ps_result == ACL_ERROR) {
356 perror("acl_execute_masked (acl_get_permset)");
357 result = ACL_ERROR;
358 goto cleanup;
359 }
360
361 int gp_result = acl_get_perm(permset, ACL_EXECUTE);
362 if (gp_result == ACL_ERROR) {
363 perror("acl_execute_masked (acl_get_perm)");
364 result = ACL_ERROR;
365 goto cleanup;
366 }
367
368 if (gp_result == ACL_FAILURE) {
369 /* No execute bit set in the mask; execute not allowed. */
370 return ACL_SUCCESS;
371 }
372 }
373
374 ge_result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
375 }
376
377 cleanup:
378 acl_free(acl);
379 return result;
380 }
381
382
383
384 /**
385 * @brief Determine whether @c path is executable (by anyone) or a
386 * directory.
387 *
388 * This is used as part of the heuristic to determine whether or not
389 * we should mask the execute bit when inheriting an ACL. If @c path
390 * is a directory, the answer is a clear-cut yes. This behavior is
391 * modeled after the capital 'X' perms of setfacl.
392 *
393 * If @c path is a file, we check the @a effective permissions,
394 * contrary to what setfacl does.
395 *
396 * @param path
397 * The path to check.
398 *
399 * @return
400 * - @c ACL_SUCCESS - @c path is a directory, or someone has effective
401 execute permissions.
402 * - @c ACL_FAILURE - @c path is a regular file and nobody can execute
403 it.
404 * - @c ACL_ERROR - Unexpected library error.
405 */
406 int any_can_execute_or_dir(const char* path) {
407
408 if (is_directory(path)) {
409 /* That was easy... */
410 return ACL_SUCCESS;
411 }
412
413 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
414
415 if (acl == (acl_t)NULL) {
416 perror("any_can_execute_or_dir (acl_get_file)");
417 return ACL_ERROR;
418 }
419
420 /* Our return value. */
421 int result = ACL_FAILURE;
422
423 if (acl_is_minimal(&acl)) {
424 mode_t mode = get_mode(path);
425 if (mode & (S_IXUSR | S_IXOTH | S_IXGRP)) {
426 result = ACL_SUCCESS;
427 goto cleanup;
428 }
429 else {
430 result = ACL_FAILURE;
431 goto cleanup;
432 }
433 }
434
435 acl_entry_t entry;
436 int ge_result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
437
438 while (ge_result == ACL_SUCCESS) {
439 /* The first thing we do is check to see if this is a mask
440 entry. If it is, we skip it entirely. */
441 acl_tag_t tag = ACL_UNDEFINED_TAG;
442 int tag_result = acl_get_tag_type(entry, &tag);
443
444 if (tag_result == ACL_ERROR) {
445 perror("any_can_execute_or_dir (acl_get_tag_type)");
446 result = ACL_ERROR;
447 goto cleanup;
448 }
449
450 if (tag == ACL_MASK) {
451 ge_result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
452 continue;
453 }
454
455 /* Ok, so it's not a mask entry. Check the execute perms. */
456 acl_permset_t permset;
457
458 int ps_result = acl_get_permset(entry, &permset);
459 if (ps_result == ACL_ERROR) {
460 perror("any_can_execute_or_dir (acl_get_permset)");
461 result = ACL_ERROR;
462 goto cleanup;
463 }
464
465 int gp_result = acl_get_perm(permset, ACL_EXECUTE);
466 if (gp_result == ACL_ERROR) {
467 perror("any_can_execute_or_dir (acl_get_perm)");
468 result = ACL_ERROR;
469 goto cleanup;
470 }
471
472 if (gp_result == ACL_SUCCESS) {
473 /* Only return ACL_SUCCESS if this execute bit is not masked. */
474 if (acl_execute_masked(path) != ACL_SUCCESS) {
475 result = ACL_SUCCESS;
476 goto cleanup;
477 }
478 }
479
480 ge_result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
481 }
482
483 if (ge_result == ACL_ERROR) {
484 perror("any_can_execute_or_dir (acl_get_entry)");
485 result = ACL_ERROR;
486 goto cleanup;
487 }
488
489 cleanup:
490 acl_free(acl);
491 return result;
492 }
493
494
495
496 /**
497 * @brief Inherit the default ACL from @c parent to @c path.
498 *
499 * The @c parent parameter does not necessarily need to be the parent
500 * of @c path, although that will usually be the case. This overwrites
501 * any existing default ACL on @c path.
502 *
503 * @param parent
504 * The parent directory whose ACL we want to inherit.
505 *
506 * @param path
507 * The target directory whose ACL we wish to overwrite (or create).
508 *
509 * @return
510 * - @c ACL_SUCCESS - The default ACL was inherited successfully.
511 * - @c ACL_FAILURE - Either @c parent or @c path is not a directory.
512 * - @c ACL_ERROR - Unexpected library error.
513 */
514 int inherit_default_acl(const char* path, const char* parent) {
515
516 /* Our return value. */
517 int result = ACL_SUCCESS;
518
519 if (path == NULL) {
520 errno = ENOENT;
521 return ACL_ERROR;
522 }
523
524 if (!is_directory(path) || !is_directory(parent)) {
525 return ACL_FAILURE;
526 }
527
528 acl_t parent_acl = acl_get_file(parent, ACL_TYPE_DEFAULT);
529 if (parent_acl == (acl_t)NULL) {
530 perror("inherit_default_acl (acl_get_file)");
531 return ACL_ERROR;
532 }
533
534 acl_t path_acl = acl_dup(parent_acl);
535
536 if (path_acl == (acl_t)NULL) {
537 perror("inherit_default_acl (acl_dup)");
538 acl_free(parent_acl);
539 return ACL_ERROR;
540 }
541
542 int sf_result = acl_set_file(path, ACL_TYPE_DEFAULT, path_acl);
543 if (sf_result == -1) {
544 perror("inherit_default_acl (acl_set_file)");
545 result = ACL_ERROR;
546 goto cleanup;
547 }
548
549 cleanup:
550 acl_free(path_acl);
551 return result;
552 }
553
554
555
556 /**
557 * @brief Remove @c ACL_USER, @c ACL_GROUP, and @c ACL_MASK entries
558 * from the given path.
559 *
560 * @param path
561 * The path whose ACLs we want to wipe.
562 *
563 * @return
564 * - @c ACL_SUCCESS - The ACLs were wiped successfully, or none
565 * existed in the first place.
566 * - @c ACL_ERROR - Unexpected library error.
567 */
568 int wipe_acls(const char* path) {
569
570 if (path == NULL) {
571 errno = ENOENT;
572 return ACL_ERROR;
573 }
574
575 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
576 if (acl == (acl_t)NULL) {
577 perror("wipe_acls (acl_get_file)");
578 return ACL_ERROR;
579 }
580
581 /* Our return value. */
582 int result = ACL_SUCCESS;
583
584 acl_entry_t entry;
585 int ge_result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
586
587 while (ge_result == ACL_SUCCESS) {
588 int d_result = acl_delete_entry(acl, entry);
589 if (d_result == ACL_ERROR) {
590 perror("wipe_acls (acl_delete_entry)");
591 result = ACL_ERROR;
592 goto cleanup;
593 }
594
595 ge_result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
596 }
597
598 /* Catches the first acl_get_entry as well as the ones at the end of
599 the loop. */
600 if (ge_result == ACL_ERROR) {
601 perror("wipe_acls (acl_get_entry)");
602 result = ACL_ERROR;
603 goto cleanup;
604 }
605
606 int sf_result = acl_set_file(path, ACL_TYPE_ACCESS, acl);
607 if (sf_result == ACL_ERROR) {
608 perror("wipe_acls (acl_set_file)");
609 result = ACL_ERROR;
610 goto cleanup;
611 }
612
613 cleanup:
614 acl_free(acl);
615 return result;
616 }
617
618
619
620 /**
621 * @brief Apply parent default ACL to a path.
622 *
623 * This overwrites any existing ACLs on @c path.
624 *
625 * @param path
626 * The path whose ACL we would like to reset to its default.
627 *
628 * @param no_exec_mask
629 * The value (either true or false) of the --no-exec-mask flag.
630 *
631 * @return
632 * - @c ACL_SUCCESS - The parent default ACL was inherited successfully.
633 * - @c ACL_FAILURE - The target path is not a regular file/directory,
634 * or the parent of @c path is not a directory.
635 * - @c ACL_ERROR - Unexpected library error.
636 */
637 int apply_default_acl(const char* path, bool no_exec_mask) {
638
639 if (path == NULL) {
640 errno = ENOENT;
641 return ACL_ERROR;
642 }
643
644 if (!is_regular_file(path) && !is_directory(path)) {
645 return ACL_FAILURE;
646 }
647
648 /* dirname mangles its argument */
649 char path_copy[PATH_MAX];
650 strncpy(path_copy, path, PATH_MAX-1);
651 path_copy[PATH_MAX-1] = 0;
652
653 char* parent = dirname(path_copy);
654 if (!is_directory(parent)) {
655 /* Make sure dirname() did what we think it did. */
656 return ACL_FAILURE;
657 }
658
659 /* Default to not masking the exec bit; i.e. applying the default
660 ACL literally. If --no-exec-mask was not specified, then we try
661 to "guess" whether or not to mask the exec bit. */
662 bool allow_exec = true;
663
664 if (!no_exec_mask) {
665 int ace_result = any_can_execute_or_dir(path);
666
667 if (ace_result == ACL_ERROR) {
668 perror("apply_default_acl (any_can_execute_or_dir)");
669 return ACL_ERROR;
670 }
671
672 allow_exec = (bool)ace_result;
673 }
674
675 acl_t defacl = acl_get_file(parent, ACL_TYPE_DEFAULT);
676
677 if (defacl == (acl_t)NULL) {
678 perror("apply_default_acl (acl_get_file)");
679 return ACL_ERROR;
680 }
681
682 /* Our return value. */
683 int result = ACL_SUCCESS;
684
685 int wipe_result = wipe_acls(path);
686 if (wipe_result == ACL_ERROR) {
687 perror("apply_default_acl (wipe_acls)");
688 result = ACL_ERROR;
689 goto cleanup;
690 }
691
692 /* Do this after wipe_acls(), otherwise we'll overwrite the wiped
693 ACL with this one. */
694 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
695 if (acl == (acl_t)NULL) {
696 perror("apply_default_acl (acl_get_file)");
697 return ACL_ERROR;
698 }
699
700 /* If it's a directory, inherit the parent's default. */
701 int inherit_result = inherit_default_acl(path, parent);
702 if (inherit_result == ACL_ERROR) {
703 perror("apply_default_acl (inherit_acls)");
704 result = ACL_ERROR;
705 goto cleanup;
706 }
707
708 acl_entry_t entry;
709 int ge_result = acl_get_entry(defacl, ACL_FIRST_ENTRY, &entry);
710
711 while (ge_result == ACL_SUCCESS) {
712 acl_tag_t tag = ACL_UNDEFINED_TAG;
713 int tag_result = acl_get_tag_type(entry, &tag);
714
715 if (tag_result == ACL_ERROR) {
716 perror("apply_default_acl (acl_get_tag_type)");
717 result = ACL_ERROR;
718 goto cleanup;
719 }
720
721
722 /* We've got an entry/tag from the default ACL. Get its permset. */
723 acl_permset_t permset;
724 int ps_result = acl_get_permset(entry, &permset);
725 if (ps_result == ACL_ERROR) {
726 perror("apply_default_acl (acl_get_permset)");
727 result = ACL_ERROR;
728 goto cleanup;
729 }
730
731 /* If this is a default mask, fix it up. */
732 if (tag == ACL_MASK ||
733 tag == ACL_USER_OBJ ||
734 tag == ACL_GROUP_OBJ ||
735 tag == ACL_OTHER) {
736
737 if (!allow_exec) {
738 /* The mask doesn't affect acl_user_obj, acl_group_obj (in
739 minimal ACLs) or acl_other entries, so if execute should be
740 masked, we have to do it manually. */
741 int d_result = acl_delete_perm(permset, ACL_EXECUTE);
742 if (d_result == ACL_ERROR) {
743 perror("apply_default_acl (acl_delete_perm)");
744 result = ACL_ERROR;
745 goto cleanup;
746 }
747
748 int sp_result = acl_set_permset(entry, permset);
749 if (sp_result == ACL_ERROR) {
750 perror("apply_default_acl (acl_set_permset)");
751 result = ACL_ERROR;
752 goto cleanup;
753 }
754 }
755 }
756
757 /* Finally, add the permset to the access ACL. */
758 int set_result = acl_set_entry(&acl, entry);
759 if (set_result == ACL_ERROR) {
760 perror("apply_default_acl (acl_set_entry)");
761 result = ACL_ERROR;
762 goto cleanup;
763 }
764
765 ge_result = acl_get_entry(defacl, ACL_NEXT_ENTRY, &entry);
766 }
767
768 /* Catches the first acl_get_entry as well as the ones at the end of
769 the loop. */
770 if (ge_result == ACL_ERROR) {
771 perror("apply_default_acl (acl_get_entry)");
772 result = ACL_ERROR;
773 goto cleanup;
774 }
775
776 int sf_result = acl_set_file(path, ACL_TYPE_ACCESS, acl);
777 if (sf_result == ACL_ERROR) {
778 perror("apply_default_acl (acl_set_file)");
779 result = ACL_ERROR;
780 goto cleanup;
781 }
782
783 cleanup:
784 acl_free(defacl);
785 return result;
786 }
787
788
789
790 /**
791 * @brief Display program usage information.
792 *
793 * @param program_name
794 * The program name to use in the output.
795 *
796 */
797 void usage(char* program_name) {
798 printf("Apply any applicable default ACLs to the given files or "
799 "directories.\n\n");
800 printf("Usage: %s [flags] <target1> [<target2> [ <target3>...]]\n\n",
801 program_name);
802 printf("Flags:\n");
803 printf(" -h, --help Print this help message\n");
804 printf(" -r, --recursive Act on any given directories recursively\n");
805 printf(" -x, --no-exec-mask Apply execute permissions unconditionally\n");
806
807 return;
808 }
809
810
811 /**
812 * @brief Wrapper around @c apply_default_acl() for use with @c nftw().
813 *
814 * For parameter information, see the @c nftw man page.
815 *
816 * @return If the ACL was applied to @c target successfully, we return
817 * @c FTW_CONTINUE to signal to @ nftw() that we should proceed onto
818 * the next file or directory. Otherwise, we return @c FTW_STOP to
819 * signal failure.
820 *
821 */
822 int apply_default_acl_nftw(const char *target,
823 const struct stat *s,
824 int info,
825 struct FTW *ftw) {
826
827 bool app_result = apply_default_acl(target, false);
828 if (app_result) {
829 return FTW_CONTINUE;
830 }
831 else {
832 return FTW_STOP;
833 }
834 }
835
836
837
838 /**
839 * @brief Wrapper around @c apply_default_acl() for use with @c nftw().
840 *
841 * This is identical to @c apply_default_acl_nftw(), except it passes
842 * @c true to @c apply_default_acl() as its no_exec_mask argument.
843 *
844 */
845 int apply_default_acl_nftw_x(const char *target,
846 const struct stat *s,
847 int info,
848 struct FTW *ftw) {
849
850 bool app_result = apply_default_acl(target, true);
851 if (app_result) {
852 return FTW_CONTINUE;
853 }
854 else {
855 return FTW_STOP;
856 }
857 }
858
859
860
861 /**
862 * @brief Recursive version of @c apply_default_acl().
863 *
864 * If @c target is a directory, we use @c nftw() to call @c
865 * apply_default_acl() recursively on all of its children. Otherwise,
866 * we just delegate to @c apply_default_acl().
867 *
868 * We ignore symlinks for consistency with chmod -r.
869 *
870 * @param target
871 * The root (path) of the recursive application.
872 *
873 * @param no_exec_mask
874 * The value (either true or false) of the --no-exec-mask flag.
875 *
876 * @return
877 * If @c target is not a directory, we return the result of
878 * calling @c apply_default_acl() on @c target. Otherwise, we convert
879 * the return value of @c nftw(). If @c nftw() succeeds (returns 0),
880 * then we return @c true. Otherwise, we return @c false.
881 * \n\n
882 * If there is an error, it will be reported via @c perror, but
883 * we still return @c false.
884 */
885 bool apply_default_acl_recursive(const char *target, bool no_exec_mask) {
886
887 if (!is_directory(target)) {
888 return apply_default_acl(target, no_exec_mask);
889 }
890
891 int max_levels = 256;
892 int flags = FTW_PHYS; /* Don't follow links. */
893
894 /* There are two separate functions that could be passed to
895 nftw(). One passes no_exec_mask = true to apply_default_acl(),
896 and the other passes no_exec_mask = false. Since the function we
897 pass to nftw() cannot have parameters, we have to create separate
898 options and make the decision here. */
899 int (*fn)(const char *, const struct stat *, int, struct FTW *) = NULL;
900 fn = no_exec_mask ? apply_default_acl_nftw_x : apply_default_acl_nftw;
901
902 int nftw_result = nftw(target, fn, max_levels, flags);
903
904 if (nftw_result == 0) {
905 /* Success */
906 return true;
907 }
908
909 /* nftw will return -1 on error, or if the supplied function
910 * (apply_default_acl_nftw) returns a non-zero result, nftw will
911 * return that.
912 */
913 if (nftw_result == -1) {
914 perror("apply_default_acl_recursive (nftw)");
915 }
916
917 return false;
918 }
919
920
921
922 /**
923 * @brief Call apply_default_acl (possibly recursively) on each
924 * command-line argument.
925 *
926 * @return Either @c EXIT_FAILURE or @c EXIT_SUCCESS. If everything
927 * goes as expected, we return @c EXIT_SUCCESS. Otherwise, we return
928 * @c EXIT_FAILURE.
929 */
930 int main(int argc, char* argv[]) {
931
932 if (argc < 2) {
933 usage(argv[0]);
934 return EXIT_FAILURE;
935 }
936
937 bool recursive = false;
938 bool no_exec_mask = false;
939
940 struct option long_options[] = {
941 /* These options set a flag. */
942 {"help", no_argument, NULL, 'h'},
943 {"recursive", no_argument, NULL, 'r'},
944 {"no-exec-mask", no_argument, NULL, 'x'},
945 {NULL, 0, NULL, 0}
946 };
947
948 int opt = 0;
949
950 while ((opt = getopt_long(argc, argv, "hrx", long_options, NULL)) != -1) {
951 switch (opt) {
952 case 'h':
953 usage(argv[0]);
954 return EXIT_SUCCESS;
955 case 'r':
956 recursive = true;
957 break;
958 case 'x':
959 no_exec_mask = true;
960 break;
961 default:
962 usage(argv[0]);
963 return EXIT_FAILURE;
964 }
965 }
966
967 int result = EXIT_SUCCESS;
968
969 int arg_index = 1;
970 for (arg_index = optind; arg_index < argc; arg_index++) {
971 const char* target = argv[arg_index];
972 bool reapp_result = false;
973
974 if (recursive) {
975 reapp_result = apply_default_acl_recursive(target, no_exec_mask);
976 }
977 else {
978 /* It's either normal file, or we're not operating recursively. */
979 reapp_result = apply_default_acl(target, no_exec_mask);
980 }
981
982 if (!reapp_result) {
983 result = EXIT_FAILURE;
984 }
985 }
986
987 return result;
988 }