]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - bin/setfacl/setfacl.c
Upgrade Unbound to 1.7.1.
[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 __FBSDID("$FreeBSD$");
29
30 #include <sys/types.h>
31 #include <sys/param.h>
32 #include <sys/stat.h>
33 #include <sys/acl.h>
34 #include <sys/queue.h>
35
36 #include <err.h>
37 #include <errno.h>
38 #include <fts.h>
39 #include <stdbool.h>
40 #include <stdint.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45
46 #include "setfacl.h"
47
48 /* file operations */
49 #define OP_MERGE_ACL            0x00    /* merge acl's (-mM) */
50 #define OP_REMOVE_DEF           0x01    /* remove default acl's (-k) */
51 #define OP_REMOVE_EXT           0x02    /* remove extended acl's (-b) */
52 #define OP_REMOVE_ACL           0x03    /* remove acl's (-xX) */
53 #define OP_REMOVE_BY_NUMBER     0x04    /* remove acl's (-xX) by acl entry number */
54 #define OP_ADD_ACL              0x05    /* add acls entries at a given position */
55
56 /* TAILQ entry for acl operations */
57 struct sf_entry {
58         uint    op;
59         acl_t   acl;
60         uint    entry_number;
61         TAILQ_ENTRY(sf_entry) next;
62 };
63 static TAILQ_HEAD(, sf_entry) entrylist;
64
65 bool have_mask;
66 bool have_stdin;
67 bool n_flag;
68 static bool h_flag;
69 static bool H_flag;
70 static bool L_flag;
71 static bool R_flag;
72 static bool need_mask;
73 static acl_type_t acl_type = ACL_TYPE_ACCESS;
74
75 static int      handle_file(FTS *ftsp, FTSENT *file);
76 static char     **stdin_files(void);
77 static void     usage(void);
78
79 static void
80 usage(void)
81 {
82
83         fprintf(stderr, "usage: setfacl [-R [-H | -L | -P]] [-bdhkn] "
84             "[-a position entries] [-m entries] [-M file] "
85             "[-x entries] [-X file] [file ...]\n");
86         exit(1);
87 }
88
89 static char **
90 stdin_files(void)
91 {
92         char **files_list;
93         char filename[PATH_MAX];
94         size_t fl_count, i;
95
96         if (have_stdin)
97                 err(1, "cannot have more than one stdin");
98
99         i = 0;
100         have_stdin = true;
101         bzero(&filename, sizeof(filename));
102         /* Start with an array size sufficient for basic cases. */
103         fl_count = 1024;
104         files_list = zmalloc(fl_count * sizeof(char *));
105         while (fgets(filename, (int)sizeof(filename), stdin)) {
106                 /* remove the \n */
107                 filename[strlen(filename) - 1] = '\0';
108                 files_list[i] = strdup(filename);
109                 if (files_list[i] == NULL)
110                         err(1, "strdup() failed");
111                 /* Grow array if necessary. */
112                 if (++i == fl_count) {
113                         fl_count <<= 1;
114                         if (fl_count > SIZE_MAX / sizeof(char *))
115                                 errx(1, "Too many input files");
116                         files_list = zrealloc(files_list,
117                                         fl_count * sizeof(char *));
118                 }
119         }
120
121         /* fts_open() requires the last array element to be NULL. */
122         files_list[i] = NULL;
123
124         return (files_list);
125 }
126
127 static int
128 handle_file(FTS *ftsp, FTSENT *file)
129 {
130         acl_t acl;
131         acl_entry_t unused_entry;
132         int local_error, ret;
133         struct sf_entry *entry;
134         bool follow_symlink;
135
136         local_error = 0;
137         switch (file->fts_info) {
138         case FTS_D:
139                 /* Do not recurse if -R not specified. */
140                 if (!R_flag)
141                         fts_set(ftsp, file, FTS_SKIP);
142                 break;
143         case FTS_DP:
144                 /* Skip the second visit to a directory. */
145                 return (0);
146         case FTS_DNR:
147         case FTS_ERR:
148                 warnx("%s: %s", file->fts_path, strerror(file->fts_errno));
149                 return (0);
150         default:
151                 break;
152         }
153
154         if (acl_type == ACL_TYPE_DEFAULT && file->fts_info != FTS_D) {
155                 warnx("%s: default ACL may only be set on a directory",
156                     file->fts_path);
157                 return (1);
158         }
159
160         follow_symlink = (!R_flag && !h_flag) || (R_flag && L_flag) ||
161             (R_flag && H_flag && file->fts_level == FTS_ROOTLEVEL);
162
163         if (follow_symlink)
164                 ret = pathconf(file->fts_accpath, _PC_ACL_NFS4);
165         else
166                 ret = lpathconf(file->fts_accpath, _PC_ACL_NFS4);
167         if (ret > 0) {
168                 if (acl_type == ACL_TYPE_DEFAULT) {
169                         warnx("%s: there are no default entries in NFSv4 ACLs",
170                             file->fts_path);
171                         return (1);
172                 }
173                 acl_type = ACL_TYPE_NFS4;
174         } else if (ret == 0) {
175                 if (acl_type == ACL_TYPE_NFS4)
176                         acl_type = ACL_TYPE_ACCESS;
177         } else if (ret < 0 && errno != EINVAL) {
178                 warn("%s: pathconf(..., _PC_ACL_NFS4) failed",
179                     file->fts_path);
180         }
181
182         if (follow_symlink)
183                 acl = acl_get_file(file->fts_accpath, acl_type);
184         else
185                 acl = acl_get_link_np(file->fts_accpath, acl_type);
186         if (acl == NULL) {
187                 if (follow_symlink)
188                         warn("%s: acl_get_file() failed", file->fts_path);
189                 else
190                         warn("%s: acl_get_link_np() failed", file->fts_path);
191                 return (1);
192         }
193
194         /* Cycle through each option. */
195         TAILQ_FOREACH(entry, &entrylist, next) {
196                 if (local_error)
197                         continue;
198
199                 switch(entry->op) {
200                 case OP_ADD_ACL:
201                         local_error += add_acl(entry->acl, entry->entry_number,
202                             &acl, file->fts_path);
203                         break;
204                 case OP_MERGE_ACL:
205                         local_error += merge_acl(entry->acl, &acl,
206                             file->fts_path);
207                         need_mask = true;
208                         break;
209                 case OP_REMOVE_EXT:
210                         /*
211                          * Don't try to call remove_ext() for empty
212                          * default ACL.
213                          */
214                         if (acl_type == ACL_TYPE_DEFAULT &&
215                             acl_get_entry(acl, ACL_FIRST_ENTRY,
216                             &unused_entry) == 0) {
217                                 local_error += remove_default(&acl,
218                                     file->fts_path);
219                                 break;
220                         }
221                         remove_ext(&acl, file->fts_path);
222                         need_mask = false;
223                         break;
224                 case OP_REMOVE_DEF:
225                         if (acl_type == ACL_TYPE_NFS4) {
226                                 warnx("%s: there are no default entries in "
227                                     "NFSv4 ACLs; cannot remove",
228                                     file->fts_path);
229                                 local_error++;
230                                 break;
231                         }
232                         if (acl_delete_def_file(file->fts_accpath) == -1) {
233                                 warn("%s: acl_delete_def_file() failed",
234                                     file->fts_path);
235                                 local_error++;
236                         }
237                         if (acl_type == ACL_TYPE_DEFAULT)
238                                 local_error += remove_default(&acl,
239                                     file->fts_path);
240                         need_mask = false;
241                         break;
242                 case OP_REMOVE_ACL:
243                         local_error += remove_acl(entry->acl, &acl,
244                             file->fts_path);
245                         need_mask = true;
246                         break;
247                 case OP_REMOVE_BY_NUMBER:
248                         local_error += remove_by_number(entry->entry_number,
249                             &acl, file->fts_path);
250                         need_mask = true;
251                         break;
252                 }
253         }
254
255         /*
256          * Don't try to set an empty default ACL; it will always fail.
257          * Use acl_delete_def_file(3) instead.
258          */
259         if (acl_type == ACL_TYPE_DEFAULT &&
260             acl_get_entry(acl, ACL_FIRST_ENTRY, &unused_entry) == 0) {
261                 if (acl_delete_def_file(file->fts_accpath) == -1) {
262                         warn("%s: acl_delete_def_file() failed",
263                             file->fts_path);
264                         return (1);
265                 }
266                 return (0);
267         }
268
269         /* Don't bother setting the ACL if something is broken. */
270         if (local_error) {
271                 return (1);
272         }
273
274         if (acl_type != ACL_TYPE_NFS4 && need_mask &&
275             set_acl_mask(&acl, file->fts_path) == -1) {
276                 warnx("%s: failed to set ACL mask", file->fts_path);
277                 return (1);
278         } else if (follow_symlink) {
279                 if (acl_set_file(file->fts_accpath, acl_type, acl) == -1) {
280                         warn("%s: acl_set_file() failed", file->fts_path);
281                         return (1);
282                 }
283         } else {
284                 if (acl_set_link_np(file->fts_accpath, acl_type, acl) == -1) {
285                         warn("%s: acl_set_link_np() failed", file->fts_path);
286                         return (1);
287                 }
288         }
289
290         acl_free(acl);
291         return (0);
292 }
293
294 int
295 main(int argc, char *argv[])
296 {
297         int carried_error, ch, entry_number, fts_options;
298         FTS *ftsp;
299         FTSENT *file;
300         char **files_list;
301         struct sf_entry *entry;
302         char *end;
303
304         acl_type = ACL_TYPE_ACCESS;
305         carried_error = fts_options = 0;
306         have_mask = have_stdin = n_flag = false;
307
308         TAILQ_INIT(&entrylist);
309
310         while ((ch = getopt(argc, argv, "HLM:PRX:a:bdhkm:nx:")) != -1)
311                 switch(ch) {
312                 case 'H':
313                         H_flag = true;
314                         L_flag = false;
315                         break;
316                 case 'L':
317                         L_flag = true;
318                         H_flag = false;
319                         break;
320                 case 'M':
321                         entry = zmalloc(sizeof(struct sf_entry));
322                         entry->acl = get_acl_from_file(optarg);
323                         if (entry->acl == NULL)
324                                 err(1, "%s: get_acl_from_file() failed",
325                                     optarg);
326                         entry->op = OP_MERGE_ACL;
327                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
328                         break;
329                 case 'P':
330                         H_flag = L_flag = false;
331                         break;
332                 case 'R':
333                         R_flag = true;
334                         break;
335                 case 'X':
336                         entry = zmalloc(sizeof(struct sf_entry));
337                         entry->acl = get_acl_from_file(optarg);
338                         entry->op = OP_REMOVE_ACL;
339                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
340                         break;
341                 case 'a':
342                         entry = zmalloc(sizeof(struct sf_entry));
343
344                         entry_number = strtol(optarg, &end, 10);
345                         if (end - optarg != (int)strlen(optarg))
346                                 errx(1, "%s: invalid entry number", optarg);
347                         if (entry_number < 0)
348                                 errx(1,
349                                     "%s: entry number cannot be less than zero",
350                                     optarg);
351                         entry->entry_number = entry_number;
352
353                         if (argv[optind] == NULL)
354                                 errx(1, "missing ACL");
355                         entry->acl = acl_from_text(argv[optind]);
356                         if (entry->acl == NULL)
357                                 err(1, "%s", argv[optind]);
358                         optind++;
359                         entry->op = OP_ADD_ACL;
360                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
361                         break;
362                 case 'b':
363                         entry = zmalloc(sizeof(struct sf_entry));
364                         entry->op = OP_REMOVE_EXT;
365                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
366                         break;
367                 case 'd':
368                         acl_type = ACL_TYPE_DEFAULT;
369                         break;
370                 case 'h':
371                         h_flag = 1;
372                         break;
373                 case 'k':
374                         entry = zmalloc(sizeof(struct sf_entry));
375                         entry->op = OP_REMOVE_DEF;
376                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
377                         break;
378                 case 'm':
379                         entry = zmalloc(sizeof(struct sf_entry));
380                         entry->acl = acl_from_text(optarg);
381                         if (entry->acl == NULL)
382                                 err(1, "%s", optarg);
383                         entry->op = OP_MERGE_ACL;
384                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
385                         break;
386                 case 'n':
387                         n_flag = true;
388                         break;
389                 case 'x':
390                         entry = zmalloc(sizeof(struct sf_entry));
391                         entry_number = strtol(optarg, &end, 10);
392                         if (end - optarg == (int)strlen(optarg)) {
393                                 if (entry_number < 0)
394                                         errx(1,
395                                             "%s: entry number cannot be less than zero",
396                                             optarg);
397                                 entry->entry_number = entry_number;
398                                 entry->op = OP_REMOVE_BY_NUMBER;
399                         } else {
400                                 entry->acl = acl_from_text(optarg);
401                                 if (entry->acl == NULL)
402                                         err(1, "%s", optarg);
403                                 entry->op = OP_REMOVE_ACL;
404                         }
405                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
406                         break;
407                 default:
408                         usage();
409                         break;
410                 }
411         argc -= optind;
412         argv += optind;
413
414         if (!n_flag && TAILQ_EMPTY(&entrylist))
415                 usage();
416
417         /* Take list of files from stdin. */
418         if (argc == 0 || strcmp(argv[0], "-") == 0) {
419                 files_list = stdin_files();
420         } else
421                 files_list = argv;
422
423         if (R_flag) {
424                 if (h_flag)
425                         errx(1, "the -R and -h options may not be "
426                             "specified together.");
427                 if (L_flag) {
428                         fts_options = FTS_LOGICAL;
429                 } else {
430                         fts_options = FTS_PHYSICAL;
431
432                         if (H_flag) {
433                                 fts_options |= FTS_COMFOLLOW;
434                         }
435                 }
436         } else if (h_flag) {
437                 fts_options = FTS_PHYSICAL;
438         } else {
439                 fts_options = FTS_LOGICAL;
440         }
441
442         /* Open all files. */
443         if ((ftsp = fts_open(files_list, fts_options | FTS_NOSTAT, 0)) == NULL)
444                 err(1, "fts_open");
445         while ((file = fts_read(ftsp)) != NULL)
446                 carried_error += handle_file(ftsp, file);
447
448         return (carried_error);
449 }