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