xref: /freebsd/bin/setfacl/setfacl.c (revision 193d9e768ba63fcfb187cfd17f461f7d41345048)
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 <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 #include "setfacl.h"
44 
45 /* file operations */
46 #define	OP_MERGE_ACL		0x00	/* merge acl's (-mM) */
47 #define	OP_REMOVE_DEF		0x01	/* remove default acl's (-k) */
48 #define	OP_REMOVE_EXT		0x02	/* remove extended acl's (-b) */
49 #define	OP_REMOVE_ACL		0x03	/* remove acl's (-xX) */
50 #define	OP_REMOVE_BY_NUMBER	0x04	/* remove acl's (-xX) by acl entry number */
51 #define	OP_ADD_ACL		0x05	/* add acls entries at a given position */
52 
53 /* TAILQ entry for acl operations */
54 struct sf_entry {
55 	uint	op;
56 	acl_t	acl;
57 	uint	entry_number;
58 	TAILQ_ENTRY(sf_entry) next;
59 };
60 static TAILQ_HEAD(, sf_entry) entrylist;
61 
62 /* TAILQ entry for files */
63 struct sf_file {
64 	const char *filename;
65 	TAILQ_ENTRY(sf_file) next;
66 };
67 static TAILQ_HEAD(, sf_file) filelist;
68 
69 uint have_mask;
70 uint need_mask;
71 uint have_stdin;
72 uint n_flag;
73 
74 static void	add_filename(const char *filename);
75 static void	usage(void);
76 
77 static void
78 add_filename(const char *filename)
79 {
80 	struct sf_file *file;
81 
82 	if (strlen(filename) > PATH_MAX - 1) {
83 		warn("illegal filename");
84 		return;
85 	}
86 	file = zmalloc(sizeof(struct sf_file));
87 	file->filename = filename;
88 	TAILQ_INSERT_TAIL(&filelist, file, next);
89 }
90 
91 static void
92 usage(void)
93 {
94 
95 	fprintf(stderr, "usage: setfacl [-bdhkn] [-a position entries] "
96 	    "[-m entries] [-M file] [-x entries] [-X file] [file ...]\n");
97 	exit(1);
98 }
99 
100 int
101 main(int argc, char *argv[])
102 {
103 	acl_t acl;
104 	acl_type_t acl_type;
105 	acl_entry_t unused_entry;
106 	char filename[PATH_MAX];
107 	int local_error, carried_error, ch, i, entry_number, ret;
108 	int h_flag;
109 	struct sf_file *file;
110 	struct sf_entry *entry;
111 	const char *fn_dup;
112 	char *end;
113 	struct stat sb;
114 
115 	acl_type = ACL_TYPE_ACCESS;
116 	carried_error = local_error = 0;
117 	h_flag = have_mask = have_stdin = n_flag = need_mask = 0;
118 
119 	TAILQ_INIT(&entrylist);
120 	TAILQ_INIT(&filelist);
121 
122 	while ((ch = getopt(argc, argv, "M:X:a:bdhkm:nx:")) != -1)
123 		switch(ch) {
124 		case 'M':
125 			entry = zmalloc(sizeof(struct sf_entry));
126 			entry->acl = get_acl_from_file(optarg);
127 			if (entry->acl == NULL)
128 				err(1, "%s: get_acl_from_file() failed", optarg);
129 			entry->op = OP_MERGE_ACL;
130 			TAILQ_INSERT_TAIL(&entrylist, entry, next);
131 			break;
132 		case 'X':
133 			entry = zmalloc(sizeof(struct sf_entry));
134 			entry->acl = get_acl_from_file(optarg);
135 			entry->op = OP_REMOVE_ACL;
136 			TAILQ_INSERT_TAIL(&entrylist, entry, next);
137 			break;
138 		case 'a':
139 			entry = zmalloc(sizeof(struct sf_entry));
140 
141 			entry_number = strtol(optarg, &end, 10);
142 			if (end - optarg != (int)strlen(optarg))
143 				errx(1, "%s: invalid entry number", optarg);
144 			if (entry_number < 0)
145 				errx(1, "%s: entry number cannot be less than zero", optarg);
146 			entry->entry_number = entry_number;
147 
148 			if (argv[optind] == NULL)
149 				errx(1, "missing ACL");
150 			entry->acl = acl_from_text(argv[optind]);
151 			if (entry->acl == NULL)
152 				err(1, "%s", argv[optind]);
153 			optind++;
154 			entry->op = OP_ADD_ACL;
155 			TAILQ_INSERT_TAIL(&entrylist, entry, next);
156 			break;
157 		case 'b':
158 			entry = zmalloc(sizeof(struct sf_entry));
159 			entry->op = OP_REMOVE_EXT;
160 			TAILQ_INSERT_TAIL(&entrylist, entry, next);
161 			break;
162 		case 'd':
163 			acl_type = ACL_TYPE_DEFAULT;
164 			break;
165 		case 'h':
166 			h_flag = 1;
167 			break;
168 		case 'k':
169 			entry = zmalloc(sizeof(struct sf_entry));
170 			entry->op = OP_REMOVE_DEF;
171 			TAILQ_INSERT_TAIL(&entrylist, entry, next);
172 			break;
173 		case 'm':
174 			entry = zmalloc(sizeof(struct sf_entry));
175 			entry->acl = acl_from_text(optarg);
176 			if (entry->acl == NULL)
177 				err(1, "%s", optarg);
178 			entry->op = OP_MERGE_ACL;
179 			TAILQ_INSERT_TAIL(&entrylist, entry, next);
180 			break;
181 		case 'n':
182 			n_flag++;
183 			break;
184 		case 'x':
185 			entry = zmalloc(sizeof(struct sf_entry));
186 			entry_number = strtol(optarg, &end, 10);
187 			if (end - optarg == (int)strlen(optarg)) {
188 				if (entry_number < 0)
189 					errx(1, "%s: entry number cannot be less than zero", optarg);
190 				entry->entry_number = entry_number;
191 				entry->op = OP_REMOVE_BY_NUMBER;
192 			} else {
193 				entry->acl = acl_from_text(optarg);
194 				if (entry->acl == NULL)
195 					err(1, "%s", optarg);
196 				entry->op = OP_REMOVE_ACL;
197 			}
198 			TAILQ_INSERT_TAIL(&entrylist, entry, next);
199 			break;
200 		default:
201 			usage();
202 			break;
203 		}
204 	argc -= optind;
205 	argv += optind;
206 
207 	if (n_flag == 0 && TAILQ_EMPTY(&entrylist))
208 		usage();
209 
210 	/* take list of files from stdin */
211 	if (argc == 0 || strcmp(argv[0], "-") == 0) {
212 		if (have_stdin)
213 			err(1, "cannot have more than one stdin");
214 		have_stdin = 1;
215 		bzero(&filename, sizeof(filename));
216 		while (fgets(filename, (int)sizeof(filename), stdin)) {
217 			/* remove the \n */
218 			filename[strlen(filename) - 1] = '\0';
219 			fn_dup = strdup(filename);
220 			if (fn_dup == NULL)
221 				err(1, "strdup() failed");
222 			add_filename(fn_dup);
223 		}
224 	} else
225 		for (i = 0; i < argc; i++)
226 			add_filename(argv[i]);
227 
228 	/* cycle through each file */
229 	TAILQ_FOREACH(file, &filelist, next) {
230 		local_error = 0;
231 
232 		if (stat(file->filename, &sb) == -1) {
233 			warn("%s: stat() failed", file->filename);
234 			carried_error++;
235 			continue;
236 		}
237 
238 		if (acl_type == ACL_TYPE_DEFAULT && S_ISDIR(sb.st_mode) == 0) {
239 			warnx("%s: default ACL may only be set on a directory",
240 			    file->filename);
241 			carried_error++;
242 			continue;
243 		}
244 
245 		if (h_flag)
246 			ret = lpathconf(file->filename, _PC_ACL_NFS4);
247 		else
248 			ret = pathconf(file->filename, _PC_ACL_NFS4);
249 		if (ret > 0) {
250 			if (acl_type == ACL_TYPE_DEFAULT) {
251 				warnx("%s: there are no default entries "
252 			           "in NFSv4 ACLs", file->filename);
253 				carried_error++;
254 				continue;
255 			}
256 			acl_type = ACL_TYPE_NFS4;
257 		} else if (ret == 0) {
258 			if (acl_type == ACL_TYPE_NFS4)
259 				acl_type = ACL_TYPE_ACCESS;
260 		} else if (ret < 0 && errno != EINVAL) {
261 			warn("%s: pathconf(..., _PC_ACL_NFS4) failed",
262 			    file->filename);
263 		}
264 
265 		if (h_flag)
266 			acl = acl_get_link_np(file->filename, acl_type);
267 		else
268 			acl = acl_get_file(file->filename, acl_type);
269 		if (acl == NULL) {
270 			if (h_flag)
271 				warn("%s: acl_get_link_np() failed",
272 				    file->filename);
273 			else
274 				warn("%s: acl_get_file() failed",
275 				    file->filename);
276 			carried_error++;
277 			continue;
278 		}
279 
280 		/* cycle through each option */
281 		TAILQ_FOREACH(entry, &entrylist, next) {
282 			if (local_error)
283 				continue;
284 
285 			switch(entry->op) {
286 			case OP_ADD_ACL:
287 				local_error += add_acl(entry->acl,
288 				    entry->entry_number, &acl, file->filename);
289 				break;
290 			case OP_MERGE_ACL:
291 				local_error += merge_acl(entry->acl, &acl,
292 				    file->filename);
293 				need_mask = 1;
294 				break;
295 			case OP_REMOVE_EXT:
296 				/*
297 				 * Don't try to call remove_ext() for empty
298 				 * default ACL.
299 				 */
300 				if (acl_type == ACL_TYPE_DEFAULT &&
301 				    acl_get_entry(acl, ACL_FIRST_ENTRY,
302 				    &unused_entry) == 0) {
303 					local_error += remove_default(&acl,
304 					    file->filename);
305 					break;
306 				}
307 				remove_ext(&acl, file->filename);
308 				need_mask = 0;
309 				break;
310 			case OP_REMOVE_DEF:
311 				if (acl_type == ACL_TYPE_NFS4) {
312 					warnx("%s: there are no default entries in NFSv4 ACLs; "
313 					    "cannot remove", file->filename);
314 					local_error++;
315 					break;
316 				}
317 				if (acl_delete_def_file(file->filename) == -1) {
318 					warn("%s: acl_delete_def_file() failed",
319 					    file->filename);
320 					local_error++;
321 				}
322 				if (acl_type == ACL_TYPE_DEFAULT)
323 					local_error += remove_default(&acl,
324 					    file->filename);
325 				need_mask = 0;
326 				break;
327 			case OP_REMOVE_ACL:
328 				local_error += remove_acl(entry->acl, &acl,
329 				    file->filename);
330 				need_mask = 1;
331 				break;
332 			case OP_REMOVE_BY_NUMBER:
333 				local_error += remove_by_number(entry->entry_number,
334 				    &acl, file->filename);
335 				need_mask = 1;
336 				break;
337 			}
338 		}
339 
340 		/*
341 		 * Don't try to set an empty default ACL; it will always fail.
342 		 * Use acl_delete_def_file(3) instead.
343 		 */
344 		if (acl_type == ACL_TYPE_DEFAULT &&
345 		    acl_get_entry(acl, ACL_FIRST_ENTRY, &unused_entry) == 0) {
346 			if (acl_delete_def_file(file->filename) == -1) {
347 				warn("%s: acl_delete_def_file() failed",
348 				    file->filename);
349 				carried_error++;
350 			}
351 			continue;
352 		}
353 
354 		/* don't bother setting the ACL if something is broken */
355 		if (local_error) {
356 			carried_error++;
357 			continue;
358 		}
359 
360 		if (acl_type != ACL_TYPE_NFS4 && need_mask &&
361 		    set_acl_mask(&acl, file->filename) == -1) {
362 			warnx("%s: failed to set ACL mask", file->filename);
363 			carried_error++;
364 		} else if (h_flag) {
365 			if (acl_set_link_np(file->filename, acl_type,
366 			    acl) == -1) {
367 				carried_error++;
368 				warn("%s: acl_set_link_np() failed",
369 				    file->filename);
370 			}
371 		} else {
372 			if (acl_set_file(file->filename, acl_type,
373 			    acl) == -1) {
374 				carried_error++;
375 				warn("%s: acl_set_file() failed",
376 				    file->filename);
377 			}
378 		}
379 
380 		acl_free(acl);
381 	}
382 
383 	return (carried_error);
384 }
385