]> gitweb.michael.orlitzky.com - apply-default-acl.git/blob - src/aclq.c
0d09c5c1b4c22c440b8f1e2976888e411904011d
[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 bool mode_has_perm(mode_t mode, int perm) {
36 if (mode & perm) {
37 return true;
38 }
39 else {
40 return false;
41 }
42 }
43
44
45 bool is_regular_file(const char* path) {
46 if (path == NULL) {
47 return false;
48 }
49
50 struct stat s;
51 int result = stat(path, &s);
52 if (result == 0) {
53 return S_ISREG(s.st_mode);
54 }
55 else {
56 return false;
57 }
58 }
59
60 bool is_directory(const char* path) {
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 int has_type_tag_acl(const char* path,
77 acl_type_t type,
78 acl_tag_t desired_tag) {
79 /* Returns one if the given path has a default ACL for the supplied
80 tag, zero if it doesn't, and -1 on error. */
81 acl_t defacl = acl_get_file(path, type);
82
83 if (defacl == (acl_t)NULL) {
84 return 0;
85 }
86
87 acl_entry_t entry;
88 int result = acl_get_entry(defacl, ACL_FIRST_ENTRY, &entry);
89
90 while (result == 1) {
91 acl_tag_t tag = ACL_UNDEFINED_TAG;
92 int tag_result = acl_get_tag_type(entry, &tag);
93
94 if (tag_result == -1) {
95 perror("has_default_tag_acl (acl_get_tag_type)");
96 return -1;
97 }
98 else {
99 if (tag == desired_tag) {
100 return 1;
101 }
102 }
103
104 result = acl_get_entry(defacl, ACL_NEXT_ENTRY, &entry);
105 }
106
107 if (result == -1) {
108 perror("has_default_tag_acl (acl_get_entry)");
109 return -1;
110 }
111
112 return 0;
113 }
114
115 int has_default_tag_acl(const char* path, acl_tag_t desired_tag) {
116 return has_type_tag_acl(path, ACL_TYPE_DEFAULT, desired_tag);
117 }
118
119 int has_access_tag_acl(const char* path, acl_tag_t desired_tag) {
120 return has_type_tag_acl(path, ACL_TYPE_ACCESS, desired_tag);
121 }
122
123 int has_default_user_obj_acl(const char* path) {
124 return has_default_tag_acl(path, ACL_USER_OBJ);
125 }
126
127 int has_default_group_obj_acl(const char* path) {
128 return has_default_tag_acl(path, ACL_GROUP_OBJ);
129 }
130
131 int has_default_other_acl(const char* path) {
132 return has_default_tag_acl(path, ACL_OTHER);
133 }
134
135 int has_default_mask_acl(const char* path) {
136 return has_default_tag_acl(path, ACL_MASK);
137 }
138
139
140 int get_type_tag_entry(const char* path,
141 acl_type_t type,
142 acl_tag_t desired_tag,
143 acl_entry_t* entry) {
144 /* Returns one if successful, zero when the ACL doesn't exist, and
145 -1 on unexpected errors. */
146 acl_t acl = acl_get_file(path, type);
147
148 if (acl == (acl_t)NULL) {
149 /* Follow the acl_foo convention of -1 == error. */
150 return 0;
151 }
152
153 int result = acl_get_entry(acl, ACL_FIRST_ENTRY, entry);
154
155 while (result == 1) {
156 acl_tag_t tag = ACL_UNDEFINED_TAG;
157 int tag_result = acl_get_tag_type(*entry, &tag);
158
159 if (tag_result == -1) {
160 perror("get_type_tag_entry (acl_get_tag_type)");
161 return -1;
162 }
163
164 if (tag == desired_tag) {
165 /* We found the right tag, so return successfully. */
166 return 1;
167 }
168
169 result = acl_get_entry(acl, ACL_NEXT_ENTRY, entry);
170 }
171
172 /* This catches both the initial acl_get_entry and the ones at the
173 end of the loop. */
174 if (result == -1) {
175 perror("get_type_tag_entry (acl_get_entry)");
176 return -1;
177 }
178
179 return 0;
180 }
181
182 int get_default_tag_entry(const char* path,
183 acl_tag_t desired_tag,
184 acl_entry_t* entry) {
185 return get_type_tag_entry(path, ACL_TYPE_DEFAULT, desired_tag, entry);
186 }
187
188 int get_access_tag_entry(const char* path,
189 acl_tag_t desired_tag,
190 acl_entry_t* entry) {
191 return get_type_tag_entry(path, ACL_TYPE_ACCESS, desired_tag, entry);
192 }
193
194
195
196 int get_type_tag_permset(const char* path,
197 acl_type_t type,
198 acl_tag_t desired_tag,
199 acl_permset_t* output_perms) {
200 /* Returns one if successful, zero when the ACL doesn't exist, and
201 -1 on unexpected errors. */
202 acl_t defacl = acl_get_file(path, type);
203
204 if (defacl == (acl_t)NULL) {
205 /* Follow the acl_foo convention of -1 == error. */
206 return 0;
207 }
208
209 acl_entry_t entry;
210 int result = get_type_tag_entry(path, type, desired_tag, &entry);
211
212 if (result == 1) {
213 /* We found the right tag, now get the permset. */
214 int ps_result = acl_get_permset(entry, output_perms);
215 if (ps_result == -1) {
216 perror("get_type_tag_permset (acl_get_permset)");
217 }
218
219 if (ps_result == 0) {
220 return 1;
221 }
222 else {
223 return 0;
224 }
225 }
226 else {
227 return result;
228 }
229 }
230
231 int get_default_tag_permset(const char* path,
232 acl_tag_t desired_tag,
233 acl_permset_t* output_perms) {
234 return get_type_tag_permset(path,
235 ACL_TYPE_DEFAULT,
236 desired_tag,
237 output_perms);
238 }
239
240 int get_access_tag_permset(const char* path,
241 acl_tag_t desired_tag,
242 acl_permset_t* output_perms) {
243 return get_type_tag_permset(path, ACL_TYPE_ACCESS, desired_tag, output_perms);
244 }
245
246 int has_default_tag_perm(const char* path,
247 acl_tag_t tag,
248 acl_perm_t perm) {
249 /* Check path to see if tag has the given perm. Returns one if it
250 does, zero if it doesn't (or there's no ACL), and -1 on unexpected
251 errors. */
252
253 if (!has_default_tag_acl(path, tag)) {
254 return 0;
255 }
256
257 acl_permset_t permset;
258 bool ps_result = get_default_tag_permset(path, tag, &permset);
259
260 if (ps_result != 1) {
261 /* Failure or error. */
262 return ps_result;
263 }
264
265 int p_result = acl_get_perm(permset, perm);
266 if (p_result == -1) {
267 perror("has_default_tag_perm (acl_get_perm)");
268 }
269
270 return p_result;
271 }
272
273 int remove_access_tag_perm(const char* path,
274 acl_tag_t desired_tag,
275 acl_perm_t perm) {
276 /* Attempt to remove perm from tag. Returns one if successful, zero
277 if there was nothing to do, and -1 on errors. */
278 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
279 if (acl == (acl_t)NULL) {
280 /* Error. */
281 return -1;
282 }
283
284 acl_permset_t permset;
285 bool ps_result = get_access_tag_permset(path, desired_tag, &permset);
286
287 if (ps_result != 1) {
288 /* Failure or error. */
289 return ps_result;
290 }
291
292 int d_result = acl_delete_perm(permset, perm);
293 if (d_result == -1) {
294 perror("remove_access_tag_perm (acl_delete_perm)");
295 return -1;
296 }
297
298 /* We've only removed perm from the permset; now we have to replace
299 the permset. */
300 acl_entry_t entry;
301 int result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
302
303 while (result == 1) {
304 acl_tag_t tag = ACL_UNDEFINED_TAG;
305 int tag_result = acl_get_tag_type(entry, &tag);
306
307 if (tag_result == -1) {
308 perror("remove_access_tag_perm (acl_get_tag_type)");
309 return -1;
310 }
311
312 if (tag == desired_tag) {
313 /* We found the right tag. Update the permset. */
314 int s_result = acl_set_permset(entry, permset);
315 if (s_result == -1) {
316 perror("remove_access_tag_perm (acl_set_permset)");
317 return -1;
318 }
319
320 int sf_result = acl_set_file(path, ACL_TYPE_ACCESS, acl);
321 if (sf_result == -1) {
322 perror("remove_access_tag_perm (acl_set_file)");
323 return -1;
324 }
325
326 return 1;
327 }
328
329 result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
330 }
331
332 /* This catches both the initial acl_get_entry and the ones at the
333 end of the loop. */
334 if (result == -1) {
335 perror("remove_access_tag_perm (acl_get_entry)");
336 return -1;
337 }
338
339 return 0;
340 }
341
342 int remove_access_group_obj_execute(const char* path) {
343 return remove_access_tag_perm(path, ACL_GROUP_OBJ, ACL_EXECUTE);
344 }
345
346
347 int has_default_user_obj_read(const char* path) {
348 return has_default_tag_perm(path, ACL_USER_OBJ, ACL_READ);
349 }
350
351 int has_default_user_obj_write(const char* path) {
352 return has_default_tag_perm(path, ACL_USER_OBJ, ACL_WRITE);
353 }
354
355 int has_default_user_obj_execute(const char* path) {
356 return has_default_tag_perm(path, ACL_USER_OBJ, ACL_EXECUTE);
357 }
358
359 int has_default_group_obj_read(const char* path) {
360 return has_default_tag_perm(path, ACL_GROUP_OBJ, ACL_READ);
361 }
362
363 int has_default_group_obj_write(const char* path) {
364 return has_default_tag_perm(path, ACL_GROUP_OBJ, ACL_WRITE);
365 }
366
367 int has_default_group_obj_execute(const char* path) {
368 return has_default_tag_perm(path, ACL_GROUP_OBJ, ACL_EXECUTE);
369 }
370
371 int has_default_other_read(const char* path) {
372 return has_default_tag_perm(path, ACL_OTHER, ACL_READ);
373 }
374
375 int has_default_other_write(const char* path) {
376 return has_default_tag_perm(path, ACL_OTHER, ACL_WRITE);
377 }
378
379 int has_default_other_execute(const char* path) {
380 return has_default_tag_perm(path, ACL_OTHER, ACL_EXECUTE);
381 }
382
383 int has_default_mask_read(const char* path) {
384 return has_default_tag_perm(path, ACL_MASK, ACL_READ);
385 }
386
387 int has_default_mask_write(const char* path) {
388 return has_default_tag_perm(path, ACL_MASK, ACL_WRITE);
389 }
390
391 int has_default_mask_execute(const char* path) {
392 return has_default_tag_perm(path, ACL_MASK, ACL_EXECUTE);
393 }
394
395
396 int reapply_default_acl(const char* path) {
397 /* If this is a normal file or directory (i.e. that has just been
398 created), we proceed to find its parent directory which will have
399 a default ACL.
400
401 Returns one for success, zero for failure (i.e. no ACL), and -1
402 on unexpected errors. */
403 if (path == NULL) {
404 return 0;
405 }
406
407 if (!is_regular_file(path) && !is_directory(path)) {
408 return 0;
409 }
410
411 /* dirname mangles its argument */
412 char path_copy[PATH_MAX];
413 strncpy(path_copy, path, PATH_MAX-1);
414 path_copy[PATH_MAX-1] = 0;
415
416 char* parent = dirname(path_copy);
417 if (!is_directory(parent)) {
418 /* Make sure dirname() did what we think it did. */
419 return 0;
420 }
421
422 /* This is the original mode of path. We will simply add permissions
423 to it, and then later reapply the result via chmod. */
424 mode_t path_mode = get_mode(path);
425
426 if (has_default_mask_acl(parent)) {
427 /* The parent has an extended ACL. Extended ACLs use the mask
428 entry. */
429
430 /* For the group bits, we'll use the ACL's mask instead of the group
431 object bits. If the default ACL had a group entry, it should
432 already have propagated (but might be masked). */
433 if (has_default_mask_read(parent)) {
434 path_mode |= S_IRGRP;
435 }
436 else {
437 path_mode &= ~S_IRGRP;
438 }
439
440 if (has_default_mask_write(parent)) {
441 path_mode |= S_IWGRP;
442 }
443 else {
444 path_mode &= ~S_IWGRP;
445 }
446
447 if (!mode_has_perm(path_mode, S_IXGRP)) {
448 /* The group ACL entry should already have been inherited from the
449 default ACL. If the source was not group executable, we want to
450 modify the destination so that it is not group executable
451 either. In the presence of ACLs, the group permissions come not
452 from the mode bits, but from the group:: ACL entry. So, to do
453 this, we remove the group::x entry. */
454 remove_access_group_obj_execute(path);
455 }
456
457 /* We need to determine whether or not to mask the execute
458 bit. This applies not only to the user/group/other entries, but
459 also to all other named entries. If the original file wasn't
460 executable, then the result probably should not be. To
461 determine whether or not "it was executable", we rely on the
462 user execute bits. Obviously this should be done before we
463 twiddle that bit. */
464 if (has_default_mask_execute(parent)) {
465 if (mode_has_perm(path_mode, S_IXUSR)) {
466 /* This just adds the group execute bit, and doesn't actually
467 grant group execute permissions. */
468 path_mode |= S_IXGRP;
469 }
470 }
471 else {
472 path_mode &= ~S_IXGRP;
473 }
474
475 }
476 else {
477 /* It's a minimal ACL. We'll repeat for the group bits what we
478 already did for the owner/other bits. */
479 if (has_default_group_obj_acl(parent)) {
480 if (has_default_group_obj_read(parent)) {
481 path_mode |= S_IRGRP;
482 }
483 else {
484 path_mode &= ~S_IRGRP;
485 }
486
487
488 if (has_default_group_obj_write(parent)) {
489 path_mode |= S_IWGRP;
490 }
491 else {
492 path_mode &= ~S_IWGRP;
493 }
494
495 /* We don't want to set the execute bit on via the ACL unless it
496 was on originally. */
497 if (!has_default_group_obj_execute(parent)) {
498 path_mode &= ~S_IXGRP;
499 }
500 }
501 }
502
503
504 /* If parent has a default user ACL, apply it. */
505 if (has_default_user_obj_acl(parent)) {
506
507 if (has_default_user_obj_read(parent)) {
508 /* Add user read. */
509 path_mode |= S_IRUSR;
510 }
511 else {
512 /* Remove user read. */
513 path_mode &= ~S_IRUSR;
514 }
515
516
517 if (has_default_user_obj_write(parent)) {
518 /* Add user write. */
519 path_mode |= S_IWUSR;
520 }
521 else {
522 /* Remove user write. */
523 path_mode &= ~S_IWUSR;
524 }
525
526
527 /* We don't want to set the execute bit on via the ACL unless it
528 was on originally. */
529 if (!has_default_user_obj_execute(parent)) {
530 /* Remove user execute. */
531 path_mode &= ~S_IXUSR;
532 }
533 }
534
535
536 /* Do the same thing with the other perms/ACL. */
537 if (has_default_other_acl(parent)) {
538
539 if (has_default_other_read(parent)) {
540 path_mode |= S_IROTH;
541 }
542 else {
543 path_mode &= ~S_IROTH;
544 }
545
546
547 if (has_default_other_write(parent)) {
548 path_mode |= S_IWOTH;
549 }
550 else {
551 path_mode &= ~S_IWOTH;
552 }
553
554
555 /* We don't want to set the execute bit on via the ACL unless it
556 was on originally. */
557 if (!has_default_other_execute(parent)) {
558 path_mode &= ~S_IXOTH;
559 }
560 }
561
562 int chmod_result = chmod(path, path_mode);
563 if (chmod_result == 0) {
564 return 1;
565 }
566 else {
567 return 0;
568 }
569 }
570
571
572 int main(int argc, char* argv[]) {
573 const char* target = argv[1];
574
575 bool result = reapply_default_acl(target);
576
577 if (result) {
578 return EXIT_SUCCESS;
579 }
580 else {
581 return EXIT_FAILURE;
582 }
583 }