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