xref: /freebsd/usr.sbin/setfmac/setfmac.c (revision df9bd3e90c3e9d1a226903ee3ff9d1ad2f26420f)
1 /*-
2  * Copyright (c) 2002 Networks Associates Technology, Inc.
3  * All rights reserved.
4  *
5  * This software was developed for the FreeBSD Project by NAI Labs, the
6  * Security Research Division of Network Associates, Inc. under
7  * DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA
8  * CHATS research program.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  *
31  * $FreeBSD$
32  */
33 
34 #include <sys/types.h>
35 #include <sys/mac.h>
36 #include <sys/types.h>
37 #include <sys/queue.h>
38 #include <sys/mac.h>
39 #include <sys/stat.h>
40 
41 #include <ctype.h>
42 #include <err.h>
43 #include <errno.h>
44 #include <fts.h>
45 #include <libgen.h>
46 #include <regex.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <unistd.h>
51 
52 struct label_spec {
53 	struct label_spec_entry {
54 		regex_t regex;	/* compiled regular expression to match */
55 		char *regexstr;	/* uncompiled regular expression */
56 		mode_t mode;	/* mode to possibly match */
57 		char *modestr;	/* print-worthy ",-?" mode string */
58 		char *mactext;	/* MAC label to apply */
59 		int flags;	/* miscellaneous flags */
60 #define		F_DONTLABEL	0x01
61 #define		F_ALWAYSMATCH	0x02
62 	} *entries,		/* entries[0..nentries] */
63 	  *match;		/* cached decision for MAC label to apply */
64 	size_t nentries;	/* size of entries list */
65 	STAILQ_ENTRY(label_spec) link;
66 };
67 
68 struct label_specs {
69 	STAILQ_HEAD(label_specs_head, label_spec) head;
70 };
71 
72 void usage(int) __dead2;
73 struct label_specs *new_specs(void);
74 void add_specs(struct label_specs *, const char *, int);
75 void add_setfmac_specs(struct label_specs *, char *);
76 void add_spec_line(const char *, int, struct label_spec_entry *, char *);
77 int apply_specs(struct label_specs *, FTSENT *, int, int);
78 int specs_empty(struct label_specs *);
79 
80 int
81 main(int argc, char **argv)
82 {
83 	FTSENT *ftsent;
84 	FTS *fts;
85 	struct label_specs *specs;
86 	int eflag = 0, xflag = 0, vflag = 0, Rflag = 0, hflag;
87 	int ch, is_setfmac;
88 	char *bn;
89 
90 	bn = basename(argv[0]);
91 	if (bn == NULL)
92 		err(1, "basename");
93 	is_setfmac = strcmp(bn, "setfmac") == 0;
94 	hflag = is_setfmac ? FTS_LOGICAL : FTS_PHYSICAL;
95 	specs = new_specs();
96 	while ((ch = getopt(argc, argv, is_setfmac ? "Rh" : "ef:s:vx")) != -1) {
97 		switch (ch) {
98 		case 'R':
99 			Rflag = 1;
100 			break;
101 		case 'e':
102 			eflag = 1;
103 			break;
104 		case 'f':
105 			add_specs(specs, optarg, 0);
106 			break;
107 		case 'h':
108 			hflag = FTS_PHYSICAL;
109 			break;
110 		case 's':
111 			add_specs(specs, optarg, 1);
112 			break;
113 		case 'v':
114 			vflag++;
115 			break;
116 		case 'x':
117 			xflag = FTS_XDEV;
118 			break;
119 		default:
120 			usage(is_setfmac);
121 		}
122 	}
123 	argc -= optind;
124 	argv += optind;
125 
126 	if (is_setfmac) {
127 		if (argc <= 1)
128 			usage(is_setfmac);
129 		add_setfmac_specs(specs, *argv);
130 		argc--;
131 		argv++;
132 	} else {
133 		if (argc == 0 || specs_empty(specs))
134 			usage(is_setfmac);
135 	}
136 	fts = fts_open(argv, hflag | xflag, NULL);
137 	if (fts == NULL)
138 		err(1, "cannot traverse filesystem%s", argc ? "s" : "");
139 	while ((ftsent = fts_read(fts)) != NULL) {
140 		switch (ftsent->fts_info) {
141 		case FTS_DP:		/* skip post-order */
142 			break;
143 		case FTS_D:		/* do pre-order */
144 		case FTS_DC:		/* do cyclic? */
145 			/* don't ever recurse directories as setfmac(8) */
146 			if (is_setfmac && !Rflag)
147 				fts_set(fts, ftsent, FTS_SKIP);
148 		case FTS_DEFAULT:	/* do default */
149 		case FTS_F:		/* do regular */
150 		case FTS_SL:		/* do symlink */
151 		case FTS_W:		/* do whiteout */
152 			if (apply_specs(specs, ftsent, hflag, vflag)) {
153 				if (eflag) {
154 					errx(1, "labeling not supported in "
155 					    "%.*s", ftsent->fts_pathlen,
156 					    ftsent->fts_path);
157 				}
158 				warnx("labeling not supported in %.*s",
159 				    ftsent->fts_pathlen, ftsent->fts_path);
160 				fts_set(fts, ftsent, FTS_SKIP);
161 			}
162 			break;
163 		case FTS_DNR:		/* die on all errors */
164 		case FTS_ERR:
165 		case FTS_NS:
166 			err(1, "traversing %.*s", ftsent->fts_pathlen,
167 			    ftsent->fts_path);
168 		default:
169 			errx(1, "CANNOT HAPPEN (%d) traversing %.*s",
170 			    ftsent->fts_info, ftsent->fts_pathlen,
171 			    ftsent->fts_path);
172 		}
173 	}
174 	fts_close(fts);
175 	exit(0);
176 }
177 
178 void
179 usage(int is_setfmac)
180 {
181 
182 	if (is_setfmac)
183 		fprintf(stderr, "usage: setfmac [-Rh] label path [...]\n");
184 	else
185 		fprintf(stderr, "usage: setfsmac [-evx] [-f specfile [...]] [-s specfile [...]] path [...]\n");
186 	exit(1);
187 }
188 
189 int
190 chomp_line(char **line, size_t *linesize)
191 {
192 	char *s;
193 	int freeme = 0;
194 
195 	for (s = *line; s - *line < *linesize; s++) {
196 		if (!isspace(*s))
197 			break;
198 	}
199 	if (*s == '#') {
200 		**line = '\0';
201 		*linesize = 0;
202 		return (freeme);
203 	}
204 	memmove(*line, s, *linesize - (s - *line));
205 	*linesize -= s - *line;
206 	for (s = &(*line)[*linesize - 1]; s >= *line; s--) {
207 		if (!isspace(*s))
208 			break;
209 	}
210 	if (s != &(*line)[*linesize - 1]) {
211 		*linesize = s - *line + 1;
212 	} else {
213 		s = malloc(*linesize + 1);
214 		if (s == NULL)
215 			err(1, "malloc");
216 		strncpy(s, *line, *linesize);
217 		*line = s;
218 		freeme = 1;
219 	}
220 	(*line)[*linesize] = '\0';
221 	return (freeme);
222 }
223 
224 void
225 add_specs(struct label_specs *specs, const char *file, int is_sebsd)
226 {
227 	struct label_spec *spec;
228 	FILE *fp;
229 	char *line;
230 	size_t nlines = 0, linesize;
231 	int freeline;
232 
233 	spec = malloc(sizeof(*spec));
234 	if (spec == NULL)
235 		err(1, "malloc");
236 	fp = fopen(file, "r");
237 	if (fp == NULL)
238 		err(1, "opening %s", file);
239 	while ((line = fgetln(fp, &linesize)) != NULL) {
240 		freeline = chomp_line(&line, &linesize);
241 		if (linesize > 0) /* only allocate space for non-comments */
242 			nlines++;
243 		if (freeline)
244 			free(line);
245 	}
246 	if (ferror(fp))
247 		err(1, "fgetln on %s", file);
248 	rewind(fp);
249 	spec->entries = calloc(nlines, sizeof(*spec->entries));
250 	if (spec->entries == NULL)
251 		err(1, "malloc");
252 	spec->nentries = nlines;
253 	while (nlines > 0) {
254 		line = fgetln(fp, &linesize);
255 		if (line == NULL) {
256 			if (feof(fp))
257 				errx(1, "%s ended prematurely", file);
258 			else
259 				err(1, "failure reading %s", file);
260 		}
261 		freeline = chomp_line(&line, &linesize);
262 		if (linesize == 0) {
263 			if (freeline)
264 				free(line);
265 			continue;
266 		}
267 		add_spec_line(file, is_sebsd, &spec->entries[--nlines], line);
268 		if (freeline)
269 			free(line);
270 	}
271 	fclose(fp);
272 	warnx("%s: read %lu specifications", file, (long)spec->nentries);
273 	STAILQ_INSERT_TAIL(&specs->head, spec, link);
274 }
275 
276 void
277 add_setfmac_specs(struct label_specs *specs, char *label)
278 {
279 	struct label_spec *spec;
280 
281 	spec = malloc(sizeof(*spec));
282 	if (spec == NULL)
283 		err(1, "malloc");
284 	spec->nentries = 1;
285 	spec->entries = calloc(spec->nentries, sizeof(*spec->entries));
286 	if (spec->entries == NULL)
287 		err(1, "malloc");
288 	/* The _only_ thing specified here is the mactext! */
289 	spec->entries->mactext = label;
290 	spec->entries->flags |= F_ALWAYSMATCH;
291 	STAILQ_INSERT_TAIL(&specs->head, spec, link);
292 }
293 
294 void
295 add_spec_line(const char *file, int is_sebsd, struct label_spec_entry *entry,
296     char *line)
297 {
298 	char *regexstr, *modestr, *macstr, *regerrorstr;
299 	size_t size;
300 	int error;
301 
302 	regexstr = strtok(line, " \t");
303 	if (regexstr == NULL)
304 		errx(1, "%s: need regular expression", file);
305 	modestr = strtok(NULL, " \t");
306 	if (modestr == NULL)
307 		errx(1, "%s: need a label", file);
308 	macstr = strtok(NULL, " \t");
309 	if (macstr == NULL) {	/* the mode is just optional */
310 		macstr = modestr;
311 		modestr = NULL;
312 	}
313 	if (strtok(NULL, " \t") != NULL)
314 		errx(1, "%s: extraneous fields at end of line", file);
315 	/* assume we need to anchor this regex */
316 	if (asprintf(&regexstr, "^%s$", regexstr) == -1)
317 		err(1, "%s: processing regular expression", file);
318 	entry->regexstr = regexstr;
319 	error = regcomp(&entry->regex, regexstr, REG_EXTENDED | REG_NOSUB);
320 	if (error) {
321 		size = regerror(error, &entry->regex, NULL, 0);
322 		regerrorstr = malloc(size);
323 		if (regerrorstr == NULL)
324 			err(1, "malloc");
325 		(void)regerror(error, &entry->regex, regerrorstr, size);
326 		errx(1, "%s: %s: %s", file, entry->regexstr, regerrorstr);
327 	}
328 	if (!is_sebsd) {
329 		entry->mactext = strdup(macstr);
330 		if (entry->mactext == NULL)
331 			err(1, "strdup");
332 	} else {
333 		if (asprintf(&entry->mactext, "sebsd/%s", macstr) == -1)
334 			err(1, "asprintf");
335 		if (strcmp(macstr, "<<none>>") == 0)
336 			entry->flags |= F_DONTLABEL;
337 	}
338 	if (modestr != NULL) {
339 		if (strlen(modestr) != 2 || modestr[0] != '-')
340 			errx(1, "%s: invalid mode string: %s", file, modestr);
341 		switch (modestr[1]) {
342 		case 'b':
343 			entry->mode = S_IFBLK;
344 			entry->modestr = ",-b";
345 			break;
346 		case 'c':
347 			entry->mode = S_IFCHR;
348 			entry->modestr = ",-c";
349 			break;
350 		case 'd':
351 			entry->mode = S_IFDIR;
352 			entry->modestr = ",-d";
353 			break;
354 		case 'p':
355 			entry->mode = S_IFIFO;
356 			entry->modestr = ",-p";
357 			break;
358 		case 'l':
359 			entry->mode = S_IFLNK;
360 			entry->modestr = ",-l";
361 			break;
362 		case 's':
363 			entry->mode = S_IFSOCK;
364 			entry->modestr = ",-s";
365 			break;
366 		case '-':
367 			entry->mode = S_IFREG;
368 			entry->modestr = ",--";
369 			break;
370 		default:
371 			errx(1, "%s: invalid mode string: %s", file, modestr);
372 		}
373 	} else {
374 		entry->modestr = "";
375 	}
376 }
377 
378 int
379 specs_empty(struct label_specs *specs)
380 {
381 
382 	return (STAILQ_EMPTY(&specs->head));
383 }
384 
385 int
386 apply_specs(struct label_specs *specs, FTSENT *ftsent, int hflag, int vflag)
387 {
388 	regmatch_t pmatch;
389 	struct label_spec *ls;
390 	struct label_spec_entry *ent;
391 	char *regerrorstr, *macstr;
392 	size_t size;
393 	mac_t mac;
394 	int error, matchedby;
395 
396 	/*
397 	 * Work through file context sources in order of specification
398 	 * on the command line, and through their entries in reverse
399 	 * order to find the "last" (hopefully "best") match.
400 	 */
401 	matchedby = 0;
402 	STAILQ_FOREACH(ls, &specs->head, link) {
403 		for (ls->match = NULL, ent = ls->entries;
404 		    ent < &ls->entries[ls->nentries]; ent++) {
405 			if (ent->flags & F_ALWAYSMATCH)
406 				goto matched;
407 			if (ent->mode != 0 &&
408 			    (ftsent->fts_statp->st_mode & S_IFMT) != ent->mode)
409 				continue;
410 			pmatch.rm_so = 0;
411 			pmatch.rm_eo = ftsent->fts_pathlen;
412 			error = regexec(&ent->regex, ftsent->fts_path, 1,
413 			    &pmatch, REG_STARTEND);
414 			switch (error) {
415 			case REG_NOMATCH:
416 				continue;
417 			case 0:
418 				break;
419 			default:
420 				size = regerror(error, &ent->regex, NULL, 0);
421 				regerrorstr = malloc(size);
422 				if (regerrorstr == NULL)
423 					err(1, "malloc");
424 				(void)regerror(error, &ent->regex, regerrorstr,
425 				    size);
426 				errx(1, "%s: %s", ent->regexstr, regerrorstr);
427 			}
428 		matched:
429 			ls->match = ent;
430 			if (vflag) {
431 				if (matchedby == 0) {
432 					printf("%.*s matched by ",
433 					    ftsent->fts_pathlen,
434 					    ftsent->fts_path);
435 					matchedby = 1;
436 				}
437 				printf("%s(%s%s,%s)", matchedby == 2 ? "," : "",
438 				    ent->regexstr, ent->modestr, ent->mactext);
439 				if (matchedby == 1)
440 					matchedby = 2;
441 			}
442 			break;
443 		}
444 	}
445 	if (vflag && matchedby)
446 		printf("\n");
447 	size = 0;
448 	STAILQ_FOREACH(ls, &specs->head, link) {
449 		/* cached match decision */
450 		if (ls->match && (ls->match->flags & F_DONTLABEL) == 0)
451 			 /* add length of "x\0"/"y," */
452 			size += strlen(ls->match->mactext) + 1;
453 	}
454 	if (size == 0)
455 		return (0);
456 	macstr = malloc(size);
457 	if (macstr == NULL)
458 		err(1, "malloc");
459 	*macstr = '\0';
460 	STAILQ_FOREACH(ls, &specs->head, link) {
461 		/* cached match decision */
462 		if (ls->match && (ls->match->flags & F_DONTLABEL) == 0) {
463 			if (*macstr != '\0')
464 				strcat(macstr, ",");
465 			strcat(macstr, ls->match->mactext);
466 		}
467 	}
468 	if (mac_from_text(&mac, macstr))
469 		err(1, "mac_from_text(%s)", macstr);
470 	if ((hflag == FTS_PHYSICAL ? mac_set_link(ftsent->fts_accpath, mac) :
471 	    mac_set_file(ftsent->fts_accpath, mac)) != 0) {
472 		if (errno == EOPNOTSUPP) {
473 			mac_free(mac);
474 			free(macstr);
475 			return (1);
476 		}
477 		err(1, "mac_set_link(%.*s, %s)", ftsent->fts_pathlen,
478 		    ftsent->fts_path, macstr);
479 	}
480 	mac_free(mac);
481 	free(macstr);
482 	return (0);
483 }
484 
485 struct label_specs *
486 new_specs(void)
487 {
488 	struct label_specs *specs;
489 
490 	specs = malloc(sizeof(*specs));
491 	if (specs == NULL)
492 		err(1, "malloc");
493 	STAILQ_INIT(&specs->head);
494 	return (specs);
495 }
496