]> gitweb.michael.orlitzky.com - apply-default-acl.git/blob - src/apply-default-acl.c
Add a short -r flag example in the README.
[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 any_can_execute(const char* path) {
235 /* Returns 1 if any ACL entry has execute access, 0 if none do, and
236 * -1 on error.
237 */
238 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
239
240 if (acl == (acl_t)NULL) {
241 return 0;
242 }
243
244 /* Our return value. */
245 int result = 0;
246
247 if (acl_is_minimal(&acl)) {
248 mode_t mode = get_mode(path);
249 if (mode & (S_IXUSR | S_IXOTH | S_IXGRP)) {
250 result = 1;
251 goto cleanup;
252 }
253 else {
254 result = 0;
255 goto cleanup;
256 }
257 }
258
259 acl_entry_t entry;
260 int ge_result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
261
262 while (ge_result == 1) {
263 acl_permset_t permset;
264
265 int ps_result = acl_get_permset(entry, &permset);
266 if (ps_result == -1) {
267 perror("any_can_execute (acl_get_permset)");
268 result = -1;
269 goto cleanup;
270 }
271
272 int gp_result = acl_get_perm(permset, ACL_EXECUTE);
273 if (gp_result == -1) {
274 perror("any_can_execute (acl_get_perm)");
275 result = -1;
276 goto cleanup;
277 }
278
279 if (gp_result == 1) {
280 result = 1;
281 goto cleanup;
282 }
283
284 ge_result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
285 }
286
287 if (ge_result == -1) {
288 perror("any_can_execute (acl_get_entry)");
289 result = -1;
290 goto cleanup;
291 }
292
293 cleanup:
294 acl_free(acl);
295 return result;
296 }
297
298
299 int inherit_default_acl(const char* path, const char* parent) {
300 /* Inherit the default ACL from parent to path. This overwrites any
301 * existing default ACL. Returns 1 for success, 0 for failure, and
302 * -1 on error.
303 */
304
305 /* Our return value. */
306 int result = 1;
307
308 if (path == NULL) {
309 errno = ENOENT;
310 return -1;
311 }
312
313 if (!is_directory(path) || !is_directory(parent)) {
314 return 0;
315 }
316
317 acl_t parent_acl = acl_get_file(parent, ACL_TYPE_DEFAULT);
318 if (parent_acl == (acl_t)NULL) {
319 return 0;
320 }
321
322 acl_t path_acl = acl_dup(parent_acl);
323
324 if (path_acl == (acl_t)NULL) {
325 perror("inherit_default_acl (acl_dup)");
326 acl_free(parent_acl);
327 return -1;
328 }
329
330 int sf_result = acl_set_file(path, ACL_TYPE_DEFAULT, path_acl);
331 if (sf_result == -1) {
332 perror("inherit_default_acl (acl_set_file)");
333 result = -1;
334 goto cleanup;
335 }
336
337 cleanup:
338 acl_free(path_acl);
339 return result;
340 }
341
342
343 int wipe_acls(const char* path) {
344 /* Remove ACL_USER, ACL_GROUP, and ACL_MASK entries from
345 path. Returns 1 for success, 0 for failure, and -1 on error. */
346
347 if (path == NULL) {
348 errno = ENOENT;
349 return -1;
350 }
351
352 /* Finally, remove individual named/mask entries. */
353 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
354 if (acl == (acl_t)NULL) {
355 perror("wipe_acls (acl_get_file)");
356 return -1;
357 }
358
359 /* Our return value. */
360 int result = 1;
361
362 acl_entry_t entry;
363 int ge_result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
364
365 while (ge_result == 1) {
366 int d_result = acl_delete_entry(acl, entry);
367 if (d_result == -1) {
368 perror("wipe_acls (acl_delete_entry)");
369 result = -1;
370 goto cleanup;
371 }
372
373 ge_result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
374 }
375
376 /* Catches the first acl_get_entry as well as the ones at the end of
377 the loop. */
378 if (ge_result == -1) {
379 perror("wipe_acls (acl_get_entry)");
380 result = -1;
381 goto cleanup;
382 }
383
384 int sf_result = acl_set_file(path, ACL_TYPE_ACCESS, acl);
385 if (sf_result == -1) {
386 perror("wipe_acls (acl_set_file)");
387 result = -1;
388 goto cleanup;
389 }
390
391 cleanup:
392 acl_free(acl);
393 return result;
394 }
395
396
397 int apply_default_acl(const char* path) {
398 /* Really apply the default ACL by looping through it. Returns one
399 * for success, zero for failure (i.e. no ACL), and -1 on unexpected
400 * errors.
401 */
402 if (path == NULL) {
403 return 0;
404 }
405
406 if (!is_regular_file(path) && !is_directory(path)) {
407 return 0;
408 }
409
410 /* dirname mangles its argument */
411 char path_copy[PATH_MAX];
412 strncpy(path_copy, path, PATH_MAX-1);
413 path_copy[PATH_MAX-1] = 0;
414
415 char* parent = dirname(path_copy);
416 if (!is_directory(parent)) {
417 /* Make sure dirname() did what we think it did. */
418 return 0;
419 }
420
421 int ace_result = any_can_execute(path);
422 if (ace_result == -1) {
423 perror("apply_default_acl (any_can_execute)");
424 return -1;
425 }
426
427 bool allow_exec = (bool)ace_result;
428
429 acl_t defacl = acl_get_file(parent, ACL_TYPE_DEFAULT);
430
431 if (defacl == (acl_t)NULL) {
432 perror("apply_default_acl (acl_get_file)");
433 return -1;
434 }
435
436 /* Our return value. */
437 int result = 1;
438
439 int wipe_result = wipe_acls(path);
440 if (wipe_result == -1) {
441 perror("apply_default_acl (wipe_acls)");
442 result = -1;
443 goto cleanup;
444 }
445
446 /* Do this after wipe_acls(), otherwise we'll overwrite the wiped
447 ACL with this one. */
448 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
449 if (acl == (acl_t)NULL) {
450 perror("apply_default_acl (acl_get_file)");
451 return -1;
452 }
453
454 /* If it's a directory, inherit the parent's default. */
455 int inherit_result = inherit_default_acl(path, parent);
456 if (inherit_result == -1) {
457 perror("apply_default_acl (inherit_acls)");
458 result = -1;
459 goto cleanup;
460 }
461
462 acl_entry_t entry;
463 int ge_result = acl_get_entry(defacl, ACL_FIRST_ENTRY, &entry);
464
465 while (ge_result == 1) {
466 acl_tag_t tag = ACL_UNDEFINED_TAG;
467 int tag_result = acl_get_tag_type(entry, &tag);
468
469 if (tag_result == -1) {
470 perror("has_default_tag_acl (acl_get_tag_type)");
471 result = -1;
472 goto cleanup;
473 }
474
475
476 /* We've got an entry/tag from the default ACL. Get its permset. */
477 acl_permset_t permset;
478 int ps_result = acl_get_permset(entry, &permset);
479 if (ps_result == -1) {
480 perror("apply_default_acl (acl_get_permset)");
481 result = -1;
482 goto cleanup;
483 }
484
485 /* If this is a default mask, fix it up. */
486 if (tag == ACL_MASK ||
487 tag == ACL_USER_OBJ ||
488 tag == ACL_GROUP_OBJ ||
489 tag == ACL_OTHER) {
490 if (!allow_exec) {
491 /* The mask doesn't affect acl_user_obj, acl_group_obj (in
492 minimal ACLs) or acl_other entries, so if execute should be
493 masked, we have to do it manually. */
494 int d_result = acl_delete_perm(permset, ACL_EXECUTE);
495 if (d_result == -1) {
496 perror("apply_default_acl (acl_delete_perm)");
497 result = -1;
498 goto cleanup;
499 }
500
501 int sp_result = acl_set_permset(entry, permset);
502 if (sp_result == -1) {
503 perror("apply_default_acl (acl_set_permset)");
504 result = -1;
505 goto cleanup;
506 }
507 }
508 }
509
510 /* Finally, add the permset to the access ACL. */
511 int set_result = acl_set_entry(&acl, entry);
512 if (set_result == -1) {
513 perror("apply_default_acl (acl_set_entry)");
514 result = -1;
515 goto cleanup;
516 }
517
518 ge_result = acl_get_entry(defacl, ACL_NEXT_ENTRY, &entry);
519 }
520
521 /* Catches the first acl_get_entry as well as the ones at the end of
522 the loop. */
523 if (ge_result == -1) {
524 perror("apply_default_acl (acl_get_entry)");
525 result = -1;
526 goto cleanup;
527 }
528
529 int sf_result = acl_set_file(path, ACL_TYPE_ACCESS, acl);
530 if (sf_result == -1) {
531 perror("apply_default_acl (acl_set_file)");
532 result = -1;
533 goto cleanup;
534 }
535
536 cleanup:
537 acl_free(defacl);
538 return result;
539 }
540
541
542 void usage(char* program_name) {
543 /*
544 * Print usage information.
545 */
546 printf("Apply any applicable default ACLs to the given files or "
547 "directories.\n\n");
548 printf("Usage: %s [flags] <target1> [<target2> [ <target3>...]]\n\n",
549 program_name);
550 printf("Flags:\n");
551 printf(" -h, --help Print this help message\n");
552 printf(" -r, --recursive Act on any given directories recursively\n");
553 }
554
555
556 int apply_default_acl_nftw(const char *target,
557 const struct stat *s,
558 int info,
559 struct FTW *ftw) {
560 /* A wrapper around the apply_default_acl() function for use with
561 * nftw(). We need to adjust the return value so that nftw() doesn't
562 * think we've failed.
563 */
564 bool reapp_result = apply_default_acl(target);
565 if (reapp_result) {
566 return 0;
567 }
568 else {
569 return 1;
570 }
571 }
572
573
574 bool apply_default_acl_recursive(const char *target) {
575 /* Attempt to apply default ACLs recursively. If target is a
576 * directory, we recurse through its entries. If not, we just
577 * apply the default ACL to target.
578 *
579 * We ignore symlinks for consistency with chmod -r.
580 *
581 */
582 if (!is_directory(target)) {
583 return apply_default_acl(target);
584 }
585
586 int max_levels = 256;
587 int flags = FTW_PHYS; /* Don't follow links. */
588
589 int nftw_result = nftw(target,
590 apply_default_acl_nftw,
591 max_levels,
592 flags);
593
594 if (nftw_result == 0) {
595 /* Success */
596 return true;
597 }
598
599 /* nftw will return -1 on error, or if the supplied function
600 * (apply_default_acl_nftw) returns a non-zero result, nftw will
601 * return that.
602 */
603 if (nftw_result == -1) {
604 perror("apply_default_acl_recursive (nftw)");
605 }
606
607 return false;
608 }
609
610
611 int main(int argc, char* argv[]) {
612 /*
613 * Call apply_default_acl on each command-line argument.
614 */
615 if (argc < 2) {
616 usage(argv[0]);
617 return EXIT_FAILURE;
618 }
619
620
621 bool recursive = false;
622
623 struct option long_options[] = {
624 /* These options set a flag. */
625 {"help", no_argument, NULL, 'h'},
626 {"recursive", no_argument, NULL, 'r'},
627 {NULL, 0, NULL, 0}
628 };
629
630 int opt = 0;
631
632 while ((opt = getopt_long(argc, argv, "hr", long_options, NULL)) != -1) {
633 switch (opt) {
634 case 'h':
635 usage(argv[0]);
636 return EXIT_SUCCESS;
637 case 'r':
638 recursive = true;
639 break;
640 default:
641 usage(argv[0]);
642 return EXIT_FAILURE;
643 }
644 }
645
646 int result = EXIT_SUCCESS;
647
648 int arg_index = 1;
649 for (arg_index = optind; arg_index < argc; arg_index++) {
650 const char* target = argv[arg_index];
651 bool reapp_result = false;
652
653 if (recursive) {
654 reapp_result = apply_default_acl_recursive(target);
655 }
656 else {
657 /* It's either normal file, or we're not operating recursively. */
658 reapp_result = apply_default_acl(target);
659 }
660
661 if (!reapp_result) {
662 result = EXIT_FAILURE;
663 }
664 }
665
666 return result;
667 }