1 /*
2 File: do_set.c
3 (Linux Access Control List Management)
4
5 Copyright (C) 1999, 2000
6 Andreas Gruenbacher, <andreas.gruenbacher@gmail.com>
7
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Lesser General Public
10 License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with this library; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 */
22
23 #include "config.h"
24 #include <stdio.h>
25 #include <errno.h>
26 #include <sys/acl.h>
27 #include <acl/libacl.h>
28
29 #include <stdlib.h>
30 #include <string.h>
31 #include <getopt.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
35 #include <dirent.h>
36 #include <ftw.h>
37 #include "misc.h"
38 #include "sequence.h"
39 #include "do_set.h"
40 #include "parse.h"
41 #include "walk_tree.h"
42
43
44 extern const char *progname;
45 extern int opt_recalculate;
46 extern int opt_test;
47 extern int print_options;
48
49 acl_entry_t
50 find_entry(
51 acl_t acl,
52 acl_tag_t type,
53 id_t id)
54 {
55 acl_entry_t ent;
56 acl_tag_t e_type;
57 id_t *e_id_p;
58
59 if (acl_get_entry(acl, ACL_FIRST_ENTRY, &ent) != 1)
60 return NULL;
61
62 for(;;) {
63 acl_get_tag_type(ent, &e_type);
64 if (type == e_type) {
65 if (id != ACL_UNDEFINED_ID) {
66 e_id_p = acl_get_qualifier(ent);
67 if (e_id_p == NULL)
68 return NULL;
69 if (*e_id_p == id) {
70 acl_free(e_id_p);
71 return ent;
72 }
73 acl_free(e_id_p);
74 } else {
75 return ent;
76 }
77 }
78 if (acl_get_entry(acl, ACL_NEXT_ENTRY, &ent) != 1)
79 return NULL;
80 }
81 }
82
83 int
84 has_execute_perms(
85 acl_t acl)
86 {
87 acl_entry_t ent;
88
89 if (acl_get_entry(acl, ACL_FIRST_ENTRY, &ent) != 1)
90 return 0;
91
92 for(;;) {
93 acl_permset_t permset;
94
95 acl_get_permset(ent, &permset);
96 if (acl_get_perm(permset, ACL_EXECUTE) != 0)
97 return 1;
98
99 if (acl_get_entry(acl, ACL_NEXT_ENTRY, &ent) != 1)
100 return 0;
101 }
102 }
103
104 int
105 clone_entry(
106 acl_t from_acl,
107 acl_tag_t from_type,
108 acl_t *to_acl,
109 acl_tag_t to_type)
110 {
111 acl_entry_t from_entry, to_entry;
112 from_entry = find_entry(from_acl, from_type, ACL_UNDEFINED_ID);
113 if (from_entry) {
114 if (acl_create_entry(to_acl, &to_entry) != 0)
115 return -1;
116 acl_copy_entry(to_entry, from_entry);
117 acl_set_tag_type(to_entry, to_type);
118 return 0;
119 } else {
120 return 1;
121 }
122 }
123
124
125 void
126 print_test(
127 FILE *file,
128 const char *path_p,
129 const struct stat *st,
130 const acl_t acl,
131 const acl_t default_acl)
132 {
133 char *acl_text, *default_acl_text;
134
135 acl_text = acl_to_any_text(acl, NULL, ',', TEXT_ABBREVIATE);
136 default_acl_text =
137 acl_to_any_text(default_acl, "d:", ',', TEXT_ABBREVIATE);
138 fprintf(file, "%s: %s,%s\n", path_p,
139 acl_text ? acl_text : "*",
140 default_acl_text ? default_acl_text : "*");
141 acl_free(acl_text);
142 acl_free(default_acl_text);
143 }
144
145
146 static void
147 set_perm(
148 acl_entry_t ent,
149 mode_t perm)
150 {
151 acl_permset_t set;
152
153 acl_get_permset(ent, &set);
154 if (perm & CMD_PERM_READ)
155 acl_add_perm(set, ACL_READ);
156 else
157 acl_delete_perm(set, ACL_READ);
158 if (perm & CMD_PERM_WRITE)
159 acl_add_perm(set, ACL_WRITE);
160 else
161 acl_delete_perm(set, ACL_WRITE);
162 if (perm & CMD_PERM_EXECUTE)
163 acl_add_perm(set, ACL_EXECUTE);
164 else
165 acl_delete_perm(set, ACL_EXECUTE);
166 }
167
168
169 static int
170 retrieve_acl(
171 const char *path_p,
172 acl_type_t type,
173 const struct stat *st,
174 acl_t *old_acl,
175 acl_t *acl)
176 {
177 if (*acl)
178 return 0;
179 *acl = NULL;
180 if (type == ACL_TYPE_ACCESS || S_ISDIR(st->st_mode)) {
181 *old_acl = acl_get_file(path_p, type);
182 if (*old_acl == NULL && (errno == ENOSYS || errno == ENOTSUP)) {
183 if (type == ACL_TYPE_DEFAULT)
184 *old_acl = acl_init(0);
185 else
186 *old_acl = acl_from_mode(st->st_mode);
187 }
188 } else
189 *old_acl = acl_init(0);
190 if (*old_acl == NULL)
191 return -1;
192 *acl = acl_dup(*old_acl);
193 if (*acl == NULL)
194 return -1;
195 return 0;
196 }
197
198
199 static int
200 remove_extended_entries(
201 acl_t acl)
202 {
203 acl_entry_t ent, group_obj;
204 acl_permset_t mask_permset, group_obj_permset;
205 acl_tag_t tag;
206 int error;
207
208 /*
209 * Removing the ACL_MASK entry from the ACL results in
210 * increased permissions for the owning group if the
211 * ACL_GROUP_OBJ entry contains permissions not contained
212 * in the ACL_MASK entry. We remove these permissions from
213 * the ACL_GROUP_OBJ entry to avoid that.
214 *
215 * After removing the ACL, the file owner and the owning group
216 * therefore have the same permissions as before.
217 */
218
219 ent = find_entry(acl, ACL_MASK, ACL_UNDEFINED_ID);
220 group_obj = find_entry(acl, ACL_GROUP_OBJ, ACL_UNDEFINED_ID);
221 if (ent && group_obj) {
222 if (!acl_get_permset(ent, &mask_permset) &&
223 !acl_get_permset(group_obj, &group_obj_permset)) {
224 if (!acl_get_perm(mask_permset, ACL_READ))
225 acl_delete_perm(group_obj_permset, ACL_READ);
226 if (!acl_get_perm(mask_permset, ACL_WRITE))
227 acl_delete_perm(group_obj_permset, ACL_WRITE);
228 if (!acl_get_perm(mask_permset, ACL_EXECUTE))
229 acl_delete_perm(group_obj_permset, ACL_EXECUTE);
230 }
231 }
232
233 error = acl_get_entry(acl, ACL_FIRST_ENTRY, &ent);
234 while (error == 1) {
235 acl_get_tag_type(ent, &tag);
236 switch(tag) {
237 case ACL_USER:
238 case ACL_GROUP:
239 case ACL_MASK:
240 acl_delete_entry(acl, ent);
241 break;
242 default:
243 break;
244 }
245
246 error = acl_get_entry(acl, ACL_NEXT_ENTRY, &ent);
247 }
248 if (error < 0)
249 return -1;
250 return 0;
251 }
252
253
254 #define RETRIEVE_ACL(type) do { \
255 error = retrieve_acl(path_p, type, st, old_xacl, xacl); \
256 if (error) \
257 goto fail; \
258 } while(0)
259
260 int
261 do_set(
262 const char *path_p,
263 const struct stat *st,
264 int walk_flags,
265 void *arg)
266 {
267 struct do_set_args *args = arg;
268 acl_t old_acl = NULL, old_default_acl = NULL;
269 acl_t acl = NULL, default_acl = NULL;
270 acl_t *xacl, *old_xacl;
271 acl_entry_t ent;
272 cmd_t cmd;
273 int which_entry;
274 int errors = 0, error;
275 char *acl_text;
276 int acl_modified = 0, default_acl_modified = 0;
277 int acl_mask_provided = 0, default_acl_mask_provided = 0;
278
279 if (walk_flags & WALK_TREE_FAILED) {
280 fprintf(stderr, "%s: %s: %s\n", progname, path_p, strerror(errno));
281 return 1;
282 }
283
284 /*
285 * Symlinks can never have ACLs, so when doing a physical walk, we
286 * skip symlinks altogether, and when doing a half-logical walk, we
287 * skip all non-toplevel symlinks.
288 */
289 if ((walk_flags & WALK_TREE_SYMLINK) &&
290 ((walk_flags & WALK_TREE_PHYSICAL) ||
291 !(walk_flags & (WALK_TREE_TOPLEVEL | WALK_TREE_LOGICAL))))
292 return 0;
293
294 /* Execute the commands in seq (read ACLs on demand) */
295 error = seq_get_cmd(args->seq, SEQ_FIRST_CMD, &cmd);
296 if (error == 0)
297 return 0;
298 while (error == 1) {
299 mode_t perm = cmd->c_perm;
300
301 if (cmd->c_type == ACL_TYPE_ACCESS) {
302 xacl = &acl;
303 old_xacl = &old_acl;
304 acl_modified = 1;
305 if (cmd->c_tag == ACL_MASK)
306 acl_mask_provided = 1;
307 } else {
308 xacl = &default_acl;
309 old_xacl = &old_default_acl;
310 default_acl_modified = 1;
311 if (cmd->c_tag == ACL_MASK)
312 default_acl_mask_provided = 1;
313 }
314
315 RETRIEVE_ACL(cmd->c_type);
316
317 /* Check for `X', and replace with `x' as appropriate. */
318 if (perm & CMD_PERM_COND_EXECUTE) {
319 perm &= ~CMD_PERM_COND_EXECUTE;
320 if (S_ISDIR(st->st_mode) || has_execute_perms(*xacl))
321 perm |= CMD_PERM_EXECUTE;
322 }
323
324 switch(cmd->c_cmd) {
325 case CMD_ENTRY_REPLACE:
326 ent = find_entry(*xacl, cmd->c_tag, cmd->c_id);
327 if (!ent) {
328 if (acl_create_entry(xacl, &ent) != 0)
329 goto fail;
330 acl_set_tag_type(ent, cmd->c_tag);
331 if (cmd->c_id != ACL_UNDEFINED_ID)
332 acl_set_qualifier(ent,
333 &cmd->c_id);
334 }
335 set_perm(ent, perm);
336 break;
337
338 case CMD_REMOVE_ENTRY:
339 ent = find_entry(*xacl, cmd->c_tag, cmd->c_id);
340 if (ent)
341 acl_delete_entry(*xacl, ent);
342 else
343 /* ignore */;
344 break;
345
346 case CMD_REMOVE_EXTENDED_ACL:
347 remove_extended_entries(acl);
348 break;
349
350 case CMD_REMOVE_ACL:
351 acl_free(*xacl);
352 *xacl = acl_init(5);
353 if (!*xacl)
354 goto fail;
355 break;
356
357 default:
358 errno = EINVAL;
359 goto fail;
360 }
361
362 error = seq_get_cmd(args->seq, SEQ_NEXT_CMD, &cmd);
363 }
364
365 if (error < 0)
366 goto fail;
367
368 /* Try to fill in missing entries */
369 if (default_acl && acl_entries(default_acl) != 0) {
370 xacl = &acl;
371 old_xacl = &old_acl;
372
373 if (!find_entry(default_acl, ACL_USER_OBJ, ACL_UNDEFINED_ID)) {
374 if (!acl)
375 RETRIEVE_ACL(ACL_TYPE_ACCESS);
376 clone_entry(acl, ACL_USER_OBJ,
377 &default_acl, ACL_USER_OBJ);
378 }
379 if (!find_entry(default_acl, ACL_GROUP_OBJ, ACL_UNDEFINED_ID)) {
380 if (!acl)
381 RETRIEVE_ACL(ACL_TYPE_ACCESS);
382 clone_entry(acl, ACL_GROUP_OBJ,
383 &default_acl, ACL_GROUP_OBJ);
384 }
385 if (!find_entry(default_acl, ACL_OTHER, ACL_UNDEFINED_ID)) {
386 if (!acl)
387 RETRIEVE_ACL(ACL_TYPE_ACCESS);
388 clone_entry(acl, ACL_OTHER,
389 &default_acl, ACL_OTHER);
390 }
391 }
392
393 /* update mask entries and check if ACLs are valid */
394 if (acl && acl_modified) {
395 if (acl_equiv_mode(acl, NULL) != 0) {
396 if (!acl_mask_provided &&
397 !find_entry(acl, ACL_MASK, ACL_UNDEFINED_ID))
398 clone_entry(acl, ACL_GROUP_OBJ,
399 &acl, ACL_MASK);
400 if (opt_recalculate != -1 &&
401 (!acl_mask_provided || opt_recalculate == 1))
402 acl_calc_mask(&acl);
403 }
404
405 error = acl_check(acl, &which_entry);
406 if (error < 0)
407 goto fail;
408 if (error > 0) {
409 acl_text = acl_to_any_text(acl, NULL, ',', 0);
410 fprintf(stderr, _("%s: %s: Malformed access ACL "
411 "`%s': %s at entry %d\n"), progname, path_p,
412 acl_text, acl_error(error), which_entry+1);
413 acl_free(acl_text);
414 errors++;
415 goto cleanup;
416 }
417 }
418
419 if (default_acl && acl_entries(default_acl) != 0 &&
420 default_acl_modified) {
421 if (acl_equiv_mode(default_acl, NULL) != 0) {
422 if (!default_acl_mask_provided &&
423 !find_entry(default_acl,ACL_MASK,ACL_UNDEFINED_ID))
424 clone_entry(default_acl, ACL_GROUP_OBJ,
425 &default_acl, ACL_MASK);
426 if (opt_recalculate != -1 &&
427 (!default_acl_mask_provided ||
428 opt_recalculate == 1))
429 acl_calc_mask(&default_acl);
430 }
431
432 error = acl_check(default_acl, &which_entry);
433 if (error < 0)
434 goto fail;
435 if (error > 0) {
436 acl_text = acl_to_any_text(default_acl, NULL, ',', 0);
437 fprintf(stderr, _("%s: %s: Malformed default ACL "
438 "`%s': %s at entry %d\n"),
439 progname, path_p, acl_text,
440 acl_error(error), which_entry+1);
441 acl_free(acl_text);
442 errors++;
443 goto cleanup;
444 }
445 }
446
447 /* Only directores can have default ACLs */
448 if (default_acl && !S_ISDIR(st->st_mode) && (walk_flags & WALK_TREE_RECURSIVE)) {
449 /* In recursive mode, ignore default ACLs for files */
450 acl_free(default_acl);
451 default_acl = NULL;
452 }
453
454 /* check which ACLs have changed */
455 if (acl && old_acl && acl_cmp(old_acl, acl) == 0) {
456 acl_free(acl);
457 acl = NULL;
458 }
459 if ((default_acl && old_default_acl &&
460 acl_cmp(old_default_acl, default_acl) == 0)) {
461 acl_free(default_acl);
462 default_acl = NULL;
463 }
464
465 /* update the file system */
466 if (opt_test) {
467 print_test(stdout, path_p, st,
468 acl, default_acl);
469 goto cleanup;
470 }
471 if (acl) {
472 int equiv_mode;
473 mode_t mode = 0;
474
475 equiv_mode = acl_equiv_mode(acl, &mode);
476
477 if (acl_set_file(path_p, ACL_TYPE_ACCESS, acl) != 0) {
478 if (errno == ENOSYS || errno == ENOTSUP) {
479 if (equiv_mode != 0)
480 goto fail;
481 else {
482 struct stat st;
483
484 if (stat(path_p, &st) != 0)
485 goto fail;
486 mode |= st.st_mode & 07000;
487 if (chmod(path_p, mode) != 0)
488 goto fail;
489 }
490 } else
491 goto fail;
492 }
493 args->mode = mode;
494 }
495 if (default_acl) {
496 if (S_ISDIR(st->st_mode)) {
497 if (acl_entries(default_acl) == 0) {
498 if (acl_delete_def_file(path_p) != 0 &&
499 errno != ENOSYS && errno != ENOTSUP)
500 goto fail;
501 } else {
502 if (acl_set_file(path_p, ACL_TYPE_DEFAULT,
503 default_acl) != 0)
504 goto fail;
505 }
506 } else {
507 if (acl_entries(default_acl) != 0) {
508 fprintf(stderr, _("%s: %s: Only directories "
509 "can have default ACLs\n"),
510 progname, path_p);
511 errors++;
512 goto cleanup;
513 }
514 }
515 }
516
517 error = 0;
518
519 cleanup:
520 if (acl)
521 acl_free(acl);
522 if (old_acl)
523 acl_free(old_acl);
524 if (default_acl)
525 acl_free(default_acl);
526 if (old_default_acl)
527 acl_free(old_default_acl);
528 return errors;
529
530 fail:
531 fprintf(stderr, "%s: %s: %s\n", progname, path_p, strerror(errno));
532 errors++;
533 goto cleanup;
534 }
535