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