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