xref: /freebsd/bin/setfacl/setfacl.c (revision 389e4940069316fe667ffa263fa7d6390d0a960f)
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 }
450