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