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