]> gitweb.michael.orlitzky.com - apply-default-acl.git/blob - src/aclq.c
a50d33f8249971d6a54c187be65351b7defb4ae5
[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 int c_result = acl_create_entry(aclp, &new_entry);
137 if (c_result == -1) {
138 perror("acl_set_entry (acl_create_entry)");
139 return -1;
140 }
141
142 int st_result = acl_set_tag_type(new_entry, entry_tag);
143 if (st_result == -1) {
144 perror("acl_set_entry (acl_set_tag_type)");
145 return -1;
146 }
147
148 int s_result = acl_set_permset(new_entry, entry_permset);
149 if (s_result == -1) {
150 perror("acl_set_entry (acl_set_permset)");
151 return -1;
152 }
153
154 if (entry_tag == ACL_USER || entry_tag == ACL_GROUP) {
155 /* We need to set the qualifier too. */
156 void* entry_qual = acl_get_qualifier(entry);
157 if (entry_qual == (void*)NULL) {
158 perror("acl_set_entry (acl_get_qualifier)");
159 return -1;
160 }
161
162 int sq_result = acl_set_qualifier(new_entry, entry_qual);
163 if (sq_result == -1) {
164 perror("acl_set_entry (acl_set_qualifier)");
165 return -1;
166 }
167 }
168
169 return 1;
170 }
171
172
173
174 int acl_entry_count(acl_t* acl) {
175 /* Return the number of entries in ACL, or -1 on error. */
176 acl_entry_t entry;
177 int entry_count = 0;
178 int result = acl_get_entry(*acl, ACL_FIRST_ENTRY, &entry);
179
180 while (result == 1) {
181 entry_count++;
182 result = acl_get_entry(*acl, ACL_NEXT_ENTRY, &entry);
183 }
184
185 if (result == -1) {
186 perror("acl_is_minimal (acl_get_entry)");
187 return -1;
188 }
189
190 return entry_count;
191 }
192
193
194 int acl_is_minimal(acl_t* acl) {
195 /* An ACL is minimal if it has fewer than four entries. Return 0 for
196 false, 1 for true, and -1 on error. */
197
198 int ec = acl_entry_count(acl);
199 if (ec == -1) {
200 perror("acl_is_minimal (acl_entry_count)");
201 return -1;
202 }
203
204 if (ec < 4) {
205 return 1;
206 }
207 else {
208 return 0;
209 }
210 }
211
212
213 int any_can_execute(const char* path) {
214 /* Returns 1 if any ACL entry has execute access, 0 if none do, and
215 -1 on error. */
216 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
217
218 if (acl_is_minimal(&acl)) {
219 mode_t mode = get_mode(path);
220 if (mode & (S_IXUSR | S_IXOTH | S_IXGRP)) {
221 return 1;
222 }
223 else {
224 return 0;
225 }
226 }
227
228 if (acl == (acl_t)NULL) {
229 return 0;
230 }
231
232 acl_entry_t entry;
233 int result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
234
235 while (result == 1) {
236 acl_permset_t permset;
237
238 int ps_result = acl_get_permset(entry, &permset);
239 if (ps_result == -1) {
240 perror("any_can_execute (acl_get_permset)");
241 return -1;
242 }
243
244 int gp_result = acl_get_perm(permset, ACL_EXECUTE);
245 if (gp_result == -1) {
246 perror("any_can_execute (acl_get_perm)");
247 return -1;
248 }
249
250 if (gp_result == 1) {
251 return 1;
252 }
253
254 result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
255 }
256
257 if (result == -1) {
258 perror("any_can_execute (acl_get_entry)");
259 return -1;
260 }
261
262 return 0;
263 }
264
265
266 int inherit_default_acl(const char* path, const char* parent) {
267 /* Inherit the default ACL from parent to path. This overwrites any
268 existing default ACL. Returns 1 for success, 0 for failure, and
269 -1 on error. */
270
271 if (path == NULL) {
272 errno = ENOENT;
273 return -1;
274 }
275
276 if (!is_directory(path) || !is_directory(parent)) {
277 return 0;
278 }
279
280 acl_t parent_acl = acl_get_file(parent, ACL_TYPE_DEFAULT);
281 acl_t path_acl = acl_dup(parent_acl);
282
283 if (path_acl == (acl_t)NULL) {
284 perror("inherit_default_acl (acl_dup)");
285 return -1;
286 }
287
288 int sf_result = acl_set_file(path, ACL_TYPE_DEFAULT, path_acl);
289 if (sf_result == -1) {
290 perror("inherit_default_acl (acl_set_file)");
291 return -1;
292 }
293
294 acl_free(path_acl);
295 return 1;
296 }
297
298
299 int wipe_acls(const char* path) {
300 /* Remove ACL_USER, ACL_GROUP, and ACL_MASK entries from
301 path. Returns 1 for success, 0 for failure, and -1 on error. */
302
303 if (path == NULL) {
304 errno = ENOENT;
305 return -1;
306 }
307
308 /* Finally, remove individual named/mask entries. */
309 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
310 if (acl == (acl_t)NULL) {
311 perror("wipe_acls (acl_get_file)");
312 return -1;
313 }
314
315 acl_entry_t entry;
316 int result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
317
318 while (result == 1) {
319 int d_result = acl_delete_entry(acl, entry);
320 if (d_result == -1) {
321 perror("wipe_acls (acl_delete_entry)");
322 return -1;
323 }
324
325 result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
326 }
327
328 /* Catches the first acl_get_entry as well as the ones at the end of
329 the loop. */
330 if (result == -1) {
331 perror("reapply_default_acl_ng (acl_get_entry)");
332 return -1;
333 }
334
335 int sf_result = acl_set_file(path, ACL_TYPE_ACCESS, acl);
336 if (sf_result == -1) {
337 perror("wipe_acls (acl_set_file)");
338 return -1;
339 }
340
341 return 1;
342 }
343
344
345 int reapply_default_acl(const char* path) {
346 /* Really reapply the default ACL by looping through it. Returns one
347 for success, zero for failure (i.e. no ACL), and -1 on unexpected
348 errors. */
349 if (path == NULL) {
350 return 0;
351 }
352
353 if (!is_regular_file(path) && !is_directory(path)) {
354 return 0;
355 }
356
357 /* dirname mangles its argument */
358 char path_copy[PATH_MAX];
359 strncpy(path_copy, path, PATH_MAX-1);
360 path_copy[PATH_MAX-1] = 0;
361
362 char* parent = dirname(path_copy);
363 if (!is_directory(parent)) {
364 /* Make sure dirname() did what we think it did. */
365 return 0;
366 }
367
368 int ace_result = any_can_execute(path);
369 if (ace_result == -1) {
370 perror("reapply_default_acl_ng (any_can_execute)");
371 return -1;
372 }
373
374 bool allow_exec = (bool)ace_result;
375
376 acl_t defacl = acl_get_file(parent, ACL_TYPE_DEFAULT);
377
378 if (defacl == (acl_t)NULL) {
379 perror("reapply_default_acl_ng (acl_get_file)");
380 return -1;
381 }
382
383 int wipe_result = wipe_acls(path);
384 if (wipe_result == -1) {
385 perror("reapply_default_acl_ng (wipe_acls)");
386 return -1;
387 }
388
389 /* Do this after wipe_acls(), otherwise we'll overwrite the wiped
390 ACL with this one. */
391 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
392 if (acl == (acl_t)NULL) {
393 perror("reapply_default_acl_ng (acl_get_file)");
394 return -1;
395 }
396
397 /* If it's a directory, inherit the parent's default. */
398 int inherit_result = inherit_default_acl(path, parent);
399 if (inherit_result == -1) {
400 perror("reapply_default_acl_ng (inherit_acls)");
401 return -1;
402 }
403
404 acl_entry_t entry;
405 int result = acl_get_entry(defacl, ACL_FIRST_ENTRY, &entry);
406
407 while (result == 1) {
408 acl_tag_t tag = ACL_UNDEFINED_TAG;
409 int tag_result = acl_get_tag_type(entry, &tag);
410
411 if (tag_result == -1) {
412 perror("has_default_tag_acl (acl_get_tag_type)");
413 return -1;
414 }
415
416
417 /* We've got an entry/tag from the default ACL. Get its permset. */
418 acl_permset_t permset;
419 int ps_result = acl_get_permset(entry, &permset);
420 if (ps_result == -1) {
421 perror("reapply_default_acl_ng (acl_get_permset)");
422 return -1;
423 }
424
425 /* If this is a default mask, fix it up. */
426 if (tag == ACL_MASK ||
427 tag == ACL_USER_OBJ ||
428 tag == ACL_GROUP_OBJ ||
429 tag == ACL_OTHER) {
430 if (!allow_exec) {
431 /* The mask doesn't affect acl_user_obj, acl_group_obj (in
432 minimal ACLs) or acl_other entries, so if execute should be
433 masked, we have to do it manually. */
434 int d_result = acl_delete_perm(permset, ACL_EXECUTE);
435 if (d_result == -1) {
436 perror("reapply_default_acl_ng (acl_delete_perm)");
437 return -1;
438 }
439
440 int sp_result = acl_set_permset(entry, permset);
441 if (sp_result == -1) {
442 perror("reapply_default_acl_ng (acl_set_permset)");
443 return -1;
444 }
445 }
446 }
447
448 /* Finally, add the permset to the access ACL. */
449 int set_result = acl_set_entry(&acl, entry);
450 if (set_result == -1) {
451 perror("reapply_default_acl_ng (acl_set_entry)");
452 return -1;
453 }
454
455 result = acl_get_entry(defacl, ACL_NEXT_ENTRY, &entry);
456 }
457
458 /* Catches the first acl_get_entry as well as the ones at the end of
459 the loop. */
460 if (result == -1) {
461 perror("reapply_default_acl_ng (acl_get_entry)");
462 return -1;
463 }
464
465 int sf_result = acl_set_file(path, ACL_TYPE_ACCESS, acl);
466 if (sf_result == -1) {
467 perror("reapply_default_acl_ng (acl_set_file)");
468 return -1;
469 }
470
471 return 1;
472 }
473
474
475
476 int main(int argc, char* argv[]) {
477 const char* target = argv[1];
478
479 bool result = reapply_default_acl(target);
480
481 if (result) {
482 return EXIT_SUCCESS;
483 }
484 else {
485 return EXIT_FAILURE;
486 }
487 }