]> gitweb.michael.orlitzky.com - apply-default-acl.git/blob - src/aclq.c
eaeb2ede17e36d33f79925e3a6416f3a605259fa
[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 defacl = acl_get_file(path, type);
147
148 if (defacl == (acl_t)NULL) {
149 /* Follow the acl_foo convention of -1 == error. */
150 return 0;
151 }
152
153 int result = acl_get_entry(defacl, 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_default_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(defacl, 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_default_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_default_tag_entry(path, 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_default_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, ACL_TYPE_DEFAULT, desired_tag, output_perms);
235 }
236
237 int get_access_tag_permset(const char* path,
238 acl_tag_t desired_tag,
239 acl_permset_t* output_perms) {
240 return get_type_tag_permset(path, ACL_TYPE_ACCESS, desired_tag, output_perms);
241 }
242
243 int has_default_tag_perm(const char* path,
244 acl_tag_t tag,
245 acl_perm_t perm) {
246 /* Check path to see if tag has the given perm. Returns one if it
247 does, zero if it doesn't (or there's no ACL), and -1 on unexpected
248 errors. */
249
250 if (!has_default_tag_acl(path, tag)) {
251 return 0;
252 }
253
254 acl_permset_t permset;
255 bool ps_result = get_default_tag_permset(path, tag, &permset);
256
257 if (ps_result != 1) {
258 /* Failure or error. */
259 return ps_result;
260 }
261
262 int p_result = acl_get_perm(permset, perm);
263 if (p_result == -1) {
264 perror("has_default_tag_perm (acl_get_perm)");
265 }
266
267 return p_result;
268 }
269
270 int remove_access_tag_perm(const char* path,
271 acl_tag_t tag,
272 acl_perm_t perm) {
273 /* Attempt to remove perm from tag. Returns one if successful, zero
274 if there was nothing to do, and -1 on errors. */
275 int hata = has_access_tag_acl(path, tag);
276 if (hata != 1) {
277 /* Failure or error. */
278 return hata;
279 }
280
281 acl_permset_t permset;
282 bool ps_result = get_access_tag_permset(path, tag, &permset);
283
284 if (ps_result != 1) {
285 /* Failure or error. */
286 return ps_result;
287 }
288
289 int d_result = acl_delete_perm(permset, perm);
290 if (d_result == -1) {
291 perror("remove_access_tag_perm (acl_delete_perm)");
292 return -1;
293 }
294
295 /* We've only removed perm from the permset; now we have to replace
296 the permset. */
297 acl_entry_t entry;
298 int entry_result = get_access_tag_entry(path, tag, &entry);
299
300 if (entry_result == -1) {
301 perror("remove_access_tag_perm (get_access_tag_entry)");
302 return -1;
303 }
304
305 if (entry_result == 1) {
306 /* Success. */
307 int s_result = acl_set_permset(entry, permset);
308 if (s_result == -1) {
309 perror("remove_access_tag_perm (acl_set_permset)");
310 return -1;
311 }
312
313 return 1;
314 }
315 else {
316 return 0;
317 }
318 }
319
320 int remove_access_group_obj_execute(const char* path) {
321 return remove_access_tag_perm(path, ACL_GROUP_OBJ, ACL_EXECUTE);
322 }
323
324
325 int has_default_user_obj_read(const char* path) {
326 return has_default_tag_perm(path, ACL_USER_OBJ, ACL_READ);
327 }
328
329 int has_default_user_obj_write(const char* path) {
330 return has_default_tag_perm(path, ACL_USER_OBJ, ACL_WRITE);
331 }
332
333 int has_default_user_obj_execute(const char* path) {
334 return has_default_tag_perm(path, ACL_USER_OBJ, ACL_EXECUTE);
335 }
336
337 int has_default_group_obj_read(const char* path) {
338 return has_default_tag_perm(path, ACL_GROUP_OBJ, ACL_READ);
339 }
340
341 int has_default_group_obj_write(const char* path) {
342 return has_default_tag_perm(path, ACL_GROUP_OBJ, ACL_WRITE);
343 }
344
345 int has_default_group_obj_execute(const char* path) {
346 return has_default_tag_perm(path, ACL_GROUP_OBJ, ACL_EXECUTE);
347 }
348
349 int has_default_other_read(const char* path) {
350 return has_default_tag_perm(path, ACL_OTHER, ACL_READ);
351 }
352
353 int has_default_other_write(const char* path) {
354 return has_default_tag_perm(path, ACL_OTHER, ACL_WRITE);
355 }
356
357 int has_default_other_execute(const char* path) {
358 return has_default_tag_perm(path, ACL_OTHER, ACL_EXECUTE);
359 }
360
361 int has_default_mask_read(const char* path) {
362 return has_default_tag_perm(path, ACL_MASK, ACL_READ);
363 }
364
365 int has_default_mask_write(const char* path) {
366 return has_default_tag_perm(path, ACL_MASK, ACL_WRITE);
367 }
368
369 int has_default_mask_execute(const char* path) {
370 return has_default_tag_perm(path, ACL_MASK, ACL_EXECUTE);
371 }
372
373
374 int reapply_default_acl(const char* path) {
375 /* If this is a normal file or directory (i.e. that has just been
376 created), we proceed to find its parent directory which will have
377 a default ACL.
378
379 Returns one for success, zero for failure (i.e. no ACL), and -1
380 on unexpected errors. */
381 if (path == NULL) {
382 return 0;
383 }
384
385 if (!is_regular_file(path) && !is_directory(path)) {
386 return 0;
387 }
388
389 /* dirname mangles its argument */
390 char path_copy[PATH_MAX];
391 strncpy(path_copy, path, PATH_MAX-1);
392 path_copy[PATH_MAX-1] = 0;
393
394 char* parent = dirname(path_copy);
395 if (!is_directory(parent)) {
396 /* Make sure dirname() did what we think it did. */
397 return 0;
398 }
399
400 /* This is the original mode of path. We will simply add permissions
401 to it, and then later reapply the result via chmod. */
402 mode_t path_mode = get_mode(path);
403
404 if (has_default_mask_acl(parent)) {
405 /* The parent has an extended ACL. Extended ACLs use the mask
406 entry. */
407
408 /* For the group bits, we'll use the ACL's mask instead of the group
409 object bits. If the default ACL had a group entry, it should
410 already have propagated (but might be masked). */
411 if (has_default_mask_read(parent)) {
412 path_mode |= S_IRGRP;
413 }
414 else {
415 path_mode &= ~S_IRGRP;
416 }
417
418 if (has_default_mask_write(parent)) {
419 path_mode |= S_IWGRP;
420 }
421 else {
422 path_mode &= ~S_IWGRP;
423 }
424
425 if (!mode_has_perm(path_mode, S_IXGRP)) {
426 /* The group ACL entry should already have been inherited from the
427 default ACL. If the source was not group executable, we want to
428 modify the destination so that it is not group executable
429 either. In the presence of ACLs, the group permissions come not
430 from the mode bits, but from the group:: ACL entry. So, to do
431 this, we remove the group::x entry. */
432 remove_access_group_obj_execute(path);
433 }
434
435 /* We need to determine whether or not to mask the execute
436 bit. This applies not only to the user/group/other entries, but
437 also to all other named entries. If the original file wasn't
438 executable, then the result probably should not be. To
439 determine whether or not "it was executable", we rely on the
440 user execute bits. Obviously this should be done before we
441 twiddle that bit. */
442 if (has_default_mask_execute(parent)) {
443 if (mode_has_perm(path_mode, S_IXUSR)) {
444 /* This just adds the group execute bit, and doesn't actually
445 grant group execute permissions. */
446 path_mode |= S_IXGRP;
447 }
448 }
449 else {
450 path_mode &= ~S_IXGRP;
451 }
452
453 }
454 else {
455 /* It's a minimal ACL. We'll repeat for the group bits what we
456 already did for the owner/other bits. */
457 if (has_default_group_obj_acl(parent)) {
458 if (has_default_group_obj_read(parent)) {
459 path_mode |= S_IRGRP;
460 }
461 else {
462 path_mode &= ~S_IRGRP;
463 }
464
465
466 if (has_default_group_obj_write(parent)) {
467 path_mode |= S_IWGRP;
468 }
469 else {
470 path_mode &= ~S_IWGRP;
471 }
472
473 /* We don't want to set the execute bit on via the ACL unless it
474 was on originally. */
475 if (!has_default_group_obj_execute(parent)) {
476 path_mode &= ~S_IXGRP;
477 }
478 }
479 }
480
481
482 /* If parent has a default user ACL, apply it. */
483 if (has_default_user_obj_acl(parent)) {
484
485 if (has_default_user_obj_read(parent)) {
486 /* Add user read. */
487 path_mode |= S_IRUSR;
488 }
489 else {
490 /* Remove user read. */
491 path_mode &= ~S_IRUSR;
492 }
493
494
495 if (has_default_user_obj_write(parent)) {
496 /* Add user write. */
497 path_mode |= S_IWUSR;
498 }
499 else {
500 /* Remove user write. */
501 path_mode &= ~S_IWUSR;
502 }
503
504
505 /* We don't want to set the execute bit on via the ACL unless it
506 was on originally. */
507 if (!has_default_user_obj_execute(parent)) {
508 /* Remove user execute. */
509 path_mode &= ~S_IXUSR;
510 }
511 }
512
513
514 /* Do the same thing with the other perms/ACL. */
515 if (has_default_other_acl(parent)) {
516
517 if (has_default_other_read(parent)) {
518 path_mode |= S_IROTH;
519 }
520 else {
521 path_mode &= ~S_IROTH;
522 }
523
524
525 if (has_default_other_write(parent)) {
526 path_mode |= S_IWOTH;
527 }
528 else {
529 path_mode &= ~S_IWOTH;
530 }
531
532
533 /* We don't want to set the execute bit on via the ACL unless it
534 was on originally. */
535 if (!has_default_other_execute(parent)) {
536 path_mode &= ~S_IXOTH;
537 }
538 }
539
540 int chmod_result = chmod(path, path_mode);
541 if (chmod_result == 0) {
542 return 1;
543 }
544 else {
545 return 0;
546 }
547 }
548
549
550 int main(int argc, char* argv[]) {
551 const char* target = argv[1];
552
553 bool result = reapply_default_acl(target);
554
555 if (result) {
556 return EXIT_SUCCESS;
557 }
558 else {
559 return EXIT_FAILURE;
560 }
561 }