xref: /freebsd/bin/setfacl/setfacl.c (revision 63f537551380d2dab29fa402ad1269feae17e594)
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 }
506