]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - bin/setfacl/setfacl.c
libevent: Import libevent 2.1.12
[FreeBSD/FreeBSD.git] / bin / setfacl / setfacl.c
1 /*-
2  * Copyright (c) 2001 Chris D. Faulhaber
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26
27 #include <sys/cdefs.h>
28 #include <sys/param.h>
29 #include <sys/acl.h>
30 #include <sys/queue.h>
31
32 #include <err.h>
33 #include <errno.h>
34 #include <fts.h>
35 #include <stdbool.h>
36 #include <stdint.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41
42 #include "setfacl.h"
43
44 /* file operations */
45 #define OP_MERGE_ACL            0x00    /* merge acl's (-mM) */
46 #define OP_REMOVE_DEF           0x01    /* remove default acl's (-k) */
47 #define OP_REMOVE_EXT           0x02    /* remove extended acl's (-b) */
48 #define OP_REMOVE_ACL           0x03    /* remove acl's (-xX) */
49 #define OP_REMOVE_BY_NUMBER     0x04    /* remove acl's (-xX) by acl entry number */
50 #define OP_ADD_ACL              0x05    /* add acls entries at a given position */
51
52 /* TAILQ entry for acl operations */
53 struct sf_entry {
54         uint    op;
55         acl_t   acl;
56         uint    entry_number;
57         TAILQ_ENTRY(sf_entry) next;
58 };
59 static TAILQ_HEAD(, sf_entry) entrylist;
60
61 bool have_mask;
62 bool have_stdin;
63 bool n_flag;
64 static bool h_flag;
65 static bool H_flag;
66 static bool L_flag;
67 static bool R_flag;
68 static bool need_mask;
69 static acl_type_t acl_type = ACL_TYPE_ACCESS;
70
71 static int      handle_file(FTS *ftsp, FTSENT *file);
72 static acl_t    clear_inheritance_flags(acl_t acl);
73 static char     **stdin_files(void);
74 static void     usage(void);
75
76 static void
77 usage(void)
78 {
79
80         fprintf(stderr, "usage: setfacl [-R [-H | -L | -P]] [-bdhkn] "
81             "[-a position entries] [-m entries] [-M file] "
82             "[-x entries] [-X file] [file ...]\n");
83         exit(1);
84 }
85
86 static char **
87 stdin_files(void)
88 {
89         char **files_list;
90         char filename[PATH_MAX];
91         size_t fl_count, i;
92
93         if (have_stdin)
94                 err(1, "cannot have more than one stdin");
95
96         i = 0;
97         have_stdin = true;
98         bzero(&filename, sizeof(filename));
99         /* Start with an array size sufficient for basic cases. */
100         fl_count = 1024;
101         files_list = zmalloc(fl_count * sizeof(char *));
102         while (fgets(filename, (int)sizeof(filename), stdin)) {
103                 /* remove the \n */
104                 filename[strlen(filename) - 1] = '\0';
105                 files_list[i] = strdup(filename);
106                 if (files_list[i] == NULL)
107                         err(1, "strdup() failed");
108                 /* Grow array if necessary. */
109                 if (++i == fl_count) {
110                         fl_count <<= 1;
111                         if (fl_count > SIZE_MAX / sizeof(char *))
112                                 errx(1, "Too many input files");
113                         files_list = zrealloc(files_list,
114                                         fl_count * sizeof(char *));
115                 }
116         }
117
118         /* fts_open() requires the last array element to be NULL. */
119         files_list[i] = NULL;
120
121         return (files_list);
122 }
123
124 /*
125  * Remove any inheritance flags from NFSv4 ACLs when running in recursive
126  * mode.  This is to avoid files being assigned identical ACLs to their
127  * parent directory while also being set to inherit them.
128  *
129  * The acl argument is assumed to be valid.
130  */
131 static acl_t
132 clear_inheritance_flags(acl_t acl)
133 {
134         acl_t nacl;
135         acl_entry_t acl_entry;
136         acl_flagset_t acl_flagset;
137         int acl_brand, entry_id;
138
139         (void)acl_get_brand_np(acl, &acl_brand);
140         if (acl_brand != ACL_BRAND_NFS4)
141                 return (acl);
142
143         nacl = acl_dup(acl);
144         if (nacl == NULL) {
145                 warn("acl_dup() failed");
146                 return (acl);
147         }
148
149         entry_id = ACL_FIRST_ENTRY;
150         while (acl_get_entry(nacl, entry_id, &acl_entry) == 1) {
151                 entry_id = ACL_NEXT_ENTRY;
152                 if (acl_get_flagset_np(acl_entry, &acl_flagset) != 0) {
153                         warn("acl_get_flagset_np() failed");
154                         continue;
155                 }
156                 if (acl_get_flag_np(acl_flagset, ACL_ENTRY_INHERIT_ONLY) == 1) {
157                         if (acl_delete_entry(nacl, acl_entry) != 0)
158                                 warn("acl_delete_entry() failed");
159                         continue;
160                 }
161                 if (acl_delete_flag_np(acl_flagset,
162                     ACL_ENTRY_FILE_INHERIT |
163                     ACL_ENTRY_DIRECTORY_INHERIT |
164                     ACL_ENTRY_NO_PROPAGATE_INHERIT) != 0)
165                         warn("acl_delete_flag_np() failed");
166         }
167
168         return (nacl);
169 }
170
171 static int
172 handle_file(FTS *ftsp, FTSENT *file)
173 {
174         acl_t acl, nacl;
175         acl_entry_t unused_entry;
176         int local_error, ret;
177         struct sf_entry *entry;
178         bool follow_symlink;
179
180         local_error = 0;
181         switch (file->fts_info) {
182         case FTS_D:
183                 /* Do not recurse if -R not specified. */
184                 if (!R_flag)
185                         fts_set(ftsp, file, FTS_SKIP);
186                 break;
187         case FTS_DP:
188                 /* Skip the second visit to a directory. */
189                 return (0);
190         case FTS_DNR:
191         case FTS_ERR:
192                 warnx("%s: %s", file->fts_path, strerror(file->fts_errno));
193                 return (0);
194         default:
195                 break;
196         }
197
198         if (acl_type == ACL_TYPE_DEFAULT && file->fts_info != FTS_D) {
199                 warnx("%s: default ACL may only be set on a directory",
200                     file->fts_path);
201                 return (1);
202         }
203
204         follow_symlink = (!R_flag && !h_flag) || (R_flag && L_flag) ||
205             (R_flag && H_flag && file->fts_level == FTS_ROOTLEVEL);
206
207         if (follow_symlink)
208                 ret = pathconf(file->fts_accpath, _PC_ACL_NFS4);
209         else
210                 ret = lpathconf(file->fts_accpath, _PC_ACL_NFS4);
211         if (ret > 0) {
212                 if (acl_type == ACL_TYPE_DEFAULT) {
213                         warnx("%s: there are no default entries in NFSv4 ACLs",
214                             file->fts_path);
215                         return (1);
216                 }
217                 acl_type = ACL_TYPE_NFS4;
218         } else if (ret == 0) {
219                 if (acl_type == ACL_TYPE_NFS4)
220                         acl_type = ACL_TYPE_ACCESS;
221         } else if (ret < 0 && errno != EINVAL && errno != ENOENT) {
222                 warn("%s: pathconf(_PC_ACL_NFS4) failed",
223                     file->fts_path);
224         }
225
226         if (follow_symlink)
227                 acl = acl_get_file(file->fts_accpath, acl_type);
228         else
229                 acl = acl_get_link_np(file->fts_accpath, acl_type);
230         if (acl == NULL) {
231                 if (follow_symlink)
232                         warn("%s: acl_get_file() failed", file->fts_path);
233                 else
234                         warn("%s: acl_get_link_np() failed", file->fts_path);
235                 return (1);
236         }
237
238         /* Cycle through each option. */
239         TAILQ_FOREACH(entry, &entrylist, next) {
240                 nacl = entry->acl;
241                 switch (entry->op) {
242                 case OP_ADD_ACL:
243                         if (R_flag && file->fts_info != FTS_D &&
244                             acl_type == ACL_TYPE_NFS4)
245                                 nacl = clear_inheritance_flags(nacl);
246                         local_error += add_acl(nacl, entry->entry_number, &acl,
247                             file->fts_path);
248                         break;
249                 case OP_MERGE_ACL:
250                         if (R_flag && file->fts_info != FTS_D &&
251                             acl_type == ACL_TYPE_NFS4)
252                                 nacl = clear_inheritance_flags(nacl);
253                         local_error += merge_acl(nacl, &acl, file->fts_path);
254                         need_mask = true;
255                         break;
256                 case OP_REMOVE_EXT:
257                         /*
258                          * Don't try to call remove_ext() for empty
259                          * default ACL.
260                          */
261                         if (acl_type == ACL_TYPE_DEFAULT &&
262                             acl_get_entry(acl, ACL_FIRST_ENTRY,
263                             &unused_entry) == 0) {
264                                 local_error += remove_default(&acl,
265                                     file->fts_path);
266                                 break;
267                         }
268                         remove_ext(&acl, file->fts_path);
269                         need_mask = false;
270                         break;
271                 case OP_REMOVE_DEF:
272                         if (acl_type == ACL_TYPE_NFS4) {
273                                 warnx("%s: there are no default entries in "
274                                     "NFSv4 ACLs; cannot remove",
275                                     file->fts_path);
276                                 local_error++;
277                                 break;
278                         }
279                         if (acl_delete_def_file(file->fts_accpath) == -1) {
280                                 warn("%s: acl_delete_def_file() failed",
281                                     file->fts_path);
282                                 local_error++;
283                         }
284                         if (acl_type == ACL_TYPE_DEFAULT)
285                                 local_error += remove_default(&acl,
286                                     file->fts_path);
287                         need_mask = false;
288                         break;
289                 case OP_REMOVE_ACL:
290                         local_error += remove_acl(nacl, &acl, file->fts_path);
291                         need_mask = true;
292                         break;
293                 case OP_REMOVE_BY_NUMBER:
294                         local_error += remove_by_number(entry->entry_number,
295                             &acl, file->fts_path);
296                         need_mask = true;
297                         break;
298                 }
299
300                 if (nacl != entry->acl) {
301                         acl_free(nacl);
302                         nacl = NULL;
303                 }
304                 if (local_error)
305                         break;
306         }
307
308         ret = 0;
309
310         /*
311          * Don't try to set an empty default ACL; it will always fail.
312          * Use acl_delete_def_file(3) instead.
313          */
314         if (acl_type == ACL_TYPE_DEFAULT &&
315             acl_get_entry(acl, ACL_FIRST_ENTRY, &unused_entry) == 0) {
316                 if (acl_delete_def_file(file->fts_accpath) == -1) {
317                         warn("%s: acl_delete_def_file() failed",
318                             file->fts_path);
319                         ret = 1;
320                 }
321                 goto out;
322         }
323
324         /* Don't bother setting the ACL if something is broken. */
325         if (local_error) {
326                 ret = 1;
327         } else if (acl_type != ACL_TYPE_NFS4 && need_mask &&
328             set_acl_mask(&acl, file->fts_path) == -1) {
329                 warnx("%s: failed to set ACL mask", file->fts_path);
330                 ret = 1;
331         } else if (follow_symlink) {
332                 if (acl_set_file(file->fts_accpath, acl_type, acl) == -1) {
333                         warn("%s: acl_set_file() failed", file->fts_path);
334                         ret = 1;
335                 }
336         } else {
337                 if (acl_set_link_np(file->fts_accpath, acl_type, acl) == -1) {
338                         warn("%s: acl_set_link_np() failed", file->fts_path);
339                         ret = 1;
340                 }
341         }
342
343 out:
344         acl_free(acl);
345         return (ret);
346 }
347
348 int
349 main(int argc, char *argv[])
350 {
351         int carried_error, ch, entry_number, fts_options;
352         FTS *ftsp;
353         FTSENT *file;
354         char **files_list;
355         struct sf_entry *entry;
356         char *end;
357
358         acl_type = ACL_TYPE_ACCESS;
359         carried_error = fts_options = 0;
360         have_mask = have_stdin = n_flag = false;
361
362         TAILQ_INIT(&entrylist);
363
364         while ((ch = getopt(argc, argv, "HLM:PRX:a:bdhkm:nx:")) != -1)
365                 switch(ch) {
366                 case 'H':
367                         H_flag = true;
368                         L_flag = false;
369                         break;
370                 case 'L':
371                         L_flag = true;
372                         H_flag = false;
373                         break;
374                 case 'M':
375                         entry = zmalloc(sizeof(struct sf_entry));
376                         entry->acl = get_acl_from_file(optarg);
377                         if (entry->acl == NULL)
378                                 err(1, "%s: get_acl_from_file() failed",
379                                     optarg);
380                         entry->op = OP_MERGE_ACL;
381                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
382                         break;
383                 case 'P':
384                         H_flag = L_flag = false;
385                         break;
386                 case 'R':
387                         R_flag = true;
388                         break;
389                 case 'X':
390                         entry = zmalloc(sizeof(struct sf_entry));
391                         entry->acl = get_acl_from_file(optarg);
392                         entry->op = OP_REMOVE_ACL;
393                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
394                         break;
395                 case 'a':
396                         entry = zmalloc(sizeof(struct sf_entry));
397
398                         entry_number = strtol(optarg, &end, 10);
399                         if (end - optarg != (int)strlen(optarg))
400                                 errx(1, "%s: invalid entry number", optarg);
401                         if (entry_number < 0)
402                                 errx(1,
403                                     "%s: entry number cannot be less than zero",
404                                     optarg);
405                         entry->entry_number = entry_number;
406
407                         if (argv[optind] == NULL)
408                                 errx(1, "missing ACL");
409                         entry->acl = acl_from_text(argv[optind]);
410                         if (entry->acl == NULL)
411                                 err(1, "%s", argv[optind]);
412                         optind++;
413                         entry->op = OP_ADD_ACL;
414                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
415                         break;
416                 case 'b':
417                         entry = zmalloc(sizeof(struct sf_entry));
418                         entry->op = OP_REMOVE_EXT;
419                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
420                         break;
421                 case 'd':
422                         acl_type = ACL_TYPE_DEFAULT;
423                         break;
424                 case 'h':
425                         h_flag = 1;
426                         break;
427                 case 'k':
428                         entry = zmalloc(sizeof(struct sf_entry));
429                         entry->op = OP_REMOVE_DEF;
430                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
431                         break;
432                 case 'm':
433                         entry = zmalloc(sizeof(struct sf_entry));
434                         entry->acl = acl_from_text(optarg);
435                         if (entry->acl == NULL)
436                                 err(1, "%s", optarg);
437                         entry->op = OP_MERGE_ACL;
438                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
439                         break;
440                 case 'n':
441                         n_flag = true;
442                         break;
443                 case 'x':
444                         entry = zmalloc(sizeof(struct sf_entry));
445                         entry_number = strtol(optarg, &end, 10);
446                         if (end - optarg == (int)strlen(optarg)) {
447                                 if (entry_number < 0)
448                                         errx(1,
449                                             "%s: entry number cannot be less than zero",
450                                             optarg);
451                                 entry->entry_number = entry_number;
452                                 entry->op = OP_REMOVE_BY_NUMBER;
453                         } else {
454                                 entry->acl = acl_from_text(optarg);
455                                 if (entry->acl == NULL)
456                                         err(1, "%s", optarg);
457                                 entry->op = OP_REMOVE_ACL;
458                         }
459                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
460                         break;
461                 default:
462                         usage();
463                         break;
464                 }
465         argc -= optind;
466         argv += optind;
467
468         if (!n_flag && TAILQ_EMPTY(&entrylist))
469                 usage();
470
471         /* Take list of files from stdin. */
472         if (argc == 0 || strcmp(argv[0], "-") == 0) {
473                 files_list = stdin_files();
474         } else
475                 files_list = argv;
476
477         if (R_flag) {
478                 if (h_flag)
479                         errx(1, "the -R and -h options may not be "
480                             "specified together.");
481                 if (L_flag) {
482                         fts_options = FTS_LOGICAL;
483                 } else {
484                         fts_options = FTS_PHYSICAL;
485
486                         if (H_flag) {
487                                 fts_options |= FTS_COMFOLLOW;
488                         }
489                 }
490         } else if (h_flag) {
491                 fts_options = FTS_PHYSICAL;
492         } else {
493                 fts_options = FTS_LOGICAL;
494         }
495
496         /* Open all files. */
497         if ((ftsp = fts_open(files_list, fts_options | FTS_NOSTAT, 0)) == NULL)
498                 err(1, "fts_open");
499         while (errno = 0, (file = fts_read(ftsp)) != NULL)
500                 carried_error += handle_file(ftsp, file);
501         if (errno != 0)
502                 err(1, "fts_read");
503
504         return (carried_error);
505 }