xref: /freebsd/usr.sbin/setfmac/setfmac.c (revision 39beb93c)
1 /*-
2  * Copyright (c) 2002, 2004 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/queue.h>
37 #include <sys/stat.h>
38 
39 #include <ctype.h>
40 #include <err.h>
41 #include <errno.h>
42 #include <fts.h>
43 #include <libgen.h>
44 #include <regex.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 
50 struct label_spec {
51 	struct label_spec_entry {
52 		regex_t regex;	/* compiled regular expression to match */
53 		char *regexstr;	/* uncompiled regular expression */
54 		mode_t mode;	/* mode to possibly match */
55 		const char *modestr;	/* print-worthy ",-?" mode string */
56 		char *mactext;	/* MAC label to apply */
57 		int flags;	/* miscellaneous flags */
58 #define		F_DONTLABEL	0x01
59 #define		F_ALWAYSMATCH	0x02
60 	} *entries,		/* entries[0..nentries] */
61 	  *match;		/* cached decision for MAC label to apply */
62 	size_t nentries;	/* size of entries list */
63 	STAILQ_ENTRY(label_spec) link;
64 };
65 
66 struct label_specs {
67 	STAILQ_HEAD(label_specs_head, label_spec) head;
68 };
69 
70 void usage(int) __dead2;
71 struct label_specs *new_specs(void);
72 void add_specs(struct label_specs *, const char *, int);
73 void add_setfmac_specs(struct label_specs *, char *);
74 void add_spec_line(const char *, int, struct label_spec_entry *, char *);
75 int apply_specs(struct label_specs *, FTSENT *, int, int);
76 int specs_empty(struct label_specs *);
77 
78 static int qflag;
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 ? "Rhq" : "ef:qs:vx")) !=
97 	    -1) {
98 		switch (ch) {
99 		case 'R':
100 			Rflag = 1;
101 			break;
102 		case 'e':
103 			eflag = 1;
104 			break;
105 		case 'f':
106 			add_specs(specs, optarg, 0);
107 			break;
108 		case 'h':
109 			hflag = FTS_PHYSICAL;
110 			break;
111 		case 'q':
112 			qflag = 1;
113 			break;
114 		case 's':
115 			add_specs(specs, optarg, 1);
116 			break;
117 		case 'v':
118 			vflag++;
119 			break;
120 		case 'x':
121 			xflag = FTS_XDEV;
122 			break;
123 		default:
124 			usage(is_setfmac);
125 		}
126 	}
127 	argc -= optind;
128 	argv += optind;
129 
130 	if (is_setfmac) {
131 		if (argc <= 1)
132 			usage(is_setfmac);
133 		add_setfmac_specs(specs, *argv);
134 		argc--;
135 		argv++;
136 	} else {
137 		if (argc == 0 || specs_empty(specs))
138 			usage(is_setfmac);
139 	}
140 	fts = fts_open(argv, hflag | xflag, NULL);
141 	if (fts == NULL)
142 		err(1, "cannot traverse filesystem%s", argc ? "s" : "");
143 	while ((ftsent = fts_read(fts)) != NULL) {
144 		switch (ftsent->fts_info) {
145 		case FTS_DP:		/* skip post-order */
146 			break;
147 		case FTS_D:		/* do pre-order */
148 		case FTS_DC:		/* do cyclic? */
149 			/* don't ever recurse directories as setfmac(8) */
150 			if (is_setfmac && !Rflag)
151 				fts_set(fts, ftsent, FTS_SKIP);
152 		case FTS_DEFAULT:	/* do default */
153 		case FTS_F:		/* do regular */
154 		case FTS_SL:		/* do symlink */
155 		case FTS_SLNONE:	/* do symlink */
156 		case FTS_W:		/* do whiteout */
157 			if (apply_specs(specs, ftsent, hflag, vflag)) {
158 				if (eflag) {
159 					errx(1, "labeling not supported in %s",
160 					    ftsent->fts_path);
161 				}
162 				if (!qflag)
163 					warnx("labeling not supported in %s",
164 					    ftsent->fts_path);
165 				fts_set(fts, ftsent, FTS_SKIP);
166 			}
167 			break;
168 		case FTS_DNR:		/* die on all errors */
169 		case FTS_ERR:
170 		case FTS_NS:
171 			err(1, "traversing %s", ftsent->fts_path);
172 		default:
173 			errx(1, "CANNOT HAPPEN (%d) traversing %s",
174 			    ftsent->fts_info, ftsent->fts_path);
175 		}
176 	}
177 	fts_close(fts);
178 	exit(0);
179 }
180 
181 void
182 usage(int is_setfmac)
183 {
184 
185 	if (is_setfmac)
186 		fprintf(stderr, "usage: setfmac [-Rhq] label file ...\n");
187 	else
188 		fprintf(stderr, "usage: setfsmac [-ehqvx] [-f specfile [...]] [-s specfile [...]] file ...\n");
189 	exit(1);
190 }
191 
192 static int
193 chomp_line(char **line, size_t *linesize)
194 {
195 	char *s;
196 	int freeme = 0;
197 
198 	for (s = *line; (unsigned)(s - *line) < *linesize; s++) {
199 		if (!isspace(*s))
200 			break;
201 	}
202 	if (*s == '#') {
203 		**line = '\0';
204 		*linesize = 0;
205 		return (freeme);
206 	}
207 	memmove(*line, s, *linesize - (s - *line));
208 	*linesize -= s - *line;
209 	for (s = &(*line)[*linesize - 1]; s >= *line; s--) {
210 		if (!isspace(*s))
211 			break;
212 	}
213 	if (s != &(*line)[*linesize - 1]) {
214 		*linesize = s - *line + 1;
215 	} else {
216 		s = malloc(*linesize + 1);
217 		if (s == NULL)
218 			err(1, "malloc");
219 		strncpy(s, *line, *linesize);
220 		*line = s;
221 		freeme = 1;
222 	}
223 	(*line)[*linesize] = '\0';
224 	return (freeme);
225 }
226 
227 void
228 add_specs(struct label_specs *specs, const char *file, int is_sebsd)
229 {
230 	struct label_spec *spec;
231 	FILE *fp;
232 	char *line;
233 	size_t nlines = 0, linesize;
234 	int freeline;
235 
236 	spec = malloc(sizeof(*spec));
237 	if (spec == NULL)
238 		err(1, "malloc");
239 	fp = fopen(file, "r");
240 	if (fp == NULL)
241 		err(1, "opening %s", file);
242 	while ((line = fgetln(fp, &linesize)) != NULL) {
243 		freeline = chomp_line(&line, &linesize);
244 		if (linesize > 0) /* only allocate space for non-comments */
245 			nlines++;
246 		if (freeline)
247 			free(line);
248 	}
249 	if (ferror(fp))
250 		err(1, "fgetln on %s", file);
251 	rewind(fp);
252 	spec->entries = calloc(nlines, sizeof(*spec->entries));
253 	if (spec->entries == NULL)
254 		err(1, "malloc");
255 	spec->nentries = nlines;
256 	while (nlines > 0) {
257 		line = fgetln(fp, &linesize);
258 		if (line == NULL) {
259 			if (feof(fp))
260 				errx(1, "%s ended prematurely", file);
261 			else
262 				err(1, "failure reading %s", file);
263 		}
264 		freeline = chomp_line(&line, &linesize);
265 		if (linesize == 0) {
266 			if (freeline)
267 				free(line);
268 			continue;
269 		}
270 		add_spec_line(file, is_sebsd, &spec->entries[--nlines], line);
271 		if (freeline)
272 			free(line);
273 	}
274 	fclose(fp);
275 	if (!qflag)
276 		warnx("%s: read %lu specifications", file,
277 		    (long)spec->nentries);
278 	STAILQ_INSERT_TAIL(&specs->head, spec, link);
279 }
280 
281 void
282 add_setfmac_specs(struct label_specs *specs, char *label)
283 {
284 	struct label_spec *spec;
285 
286 	spec = malloc(sizeof(*spec));
287 	if (spec == NULL)
288 		err(1, "malloc");
289 	spec->nentries = 1;
290 	spec->entries = calloc(spec->nentries, sizeof(*spec->entries));
291 	if (spec->entries == NULL)
292 		err(1, "malloc");
293 	/* The _only_ thing specified here is the mactext! */
294 	spec->entries->mactext = label;
295 	spec->entries->flags |= F_ALWAYSMATCH;
296 	STAILQ_INSERT_TAIL(&specs->head, spec, link);
297 }
298 
299 void
300 add_spec_line(const char *file, int is_sebsd, struct label_spec_entry *entry,
301     char *line)
302 {
303 	char *regexstr, *modestr, *macstr, *regerrorstr;
304 	size_t size;
305 	int error;
306 
307 	regexstr = strtok(line, " \t");
308 	if (regexstr == NULL)
309 		errx(1, "%s: need regular expression", file);
310 	modestr = strtok(NULL, " \t");
311 	if (modestr == NULL)
312 		errx(1, "%s: need a label", file);
313 	macstr = strtok(NULL, " \t");
314 	if (macstr == NULL) {	/* the mode is just optional */
315 		macstr = modestr;
316 		modestr = NULL;
317 	}
318 	if (strtok(NULL, " \t") != NULL)
319 		errx(1, "%s: extraneous fields at end of line", file);
320 	/* assume we need to anchor this regex */
321 	if (asprintf(&regexstr, "^%s$", regexstr) == -1)
322 		err(1, "%s: processing regular expression", file);
323 	entry->regexstr = regexstr;
324 	error = regcomp(&entry->regex, regexstr, REG_EXTENDED | REG_NOSUB);
325 	if (error) {
326 		size = regerror(error, &entry->regex, NULL, 0);
327 		regerrorstr = malloc(size);
328 		if (regerrorstr == NULL)
329 			err(1, "malloc");
330 		(void)regerror(error, &entry->regex, regerrorstr, size);
331 		errx(1, "%s: %s: %s", file, entry->regexstr, regerrorstr);
332 	}
333 	if (!is_sebsd) {
334 		entry->mactext = strdup(macstr);
335 		if (entry->mactext == NULL)
336 			err(1, "strdup");
337 	} else {
338 		if (asprintf(&entry->mactext, "sebsd/%s", macstr) == -1)
339 			err(1, "asprintf");
340 		if (strcmp(macstr, "<<none>>") == 0)
341 			entry->flags |= F_DONTLABEL;
342 	}
343 	if (modestr != NULL) {
344 		if (strlen(modestr) != 2 || modestr[0] != '-')
345 			errx(1, "%s: invalid mode string: %s", file, modestr);
346 		switch (modestr[1]) {
347 		case 'b':
348 			entry->mode = S_IFBLK;
349 			entry->modestr = ",-b";
350 			break;
351 		case 'c':
352 			entry->mode = S_IFCHR;
353 			entry->modestr = ",-c";
354 			break;
355 		case 'd':
356 			entry->mode = S_IFDIR;
357 			entry->modestr = ",-d";
358 			break;
359 		case 'p':
360 			entry->mode = S_IFIFO;
361 			entry->modestr = ",-p";
362 			break;
363 		case 'l':
364 			entry->mode = S_IFLNK;
365 			entry->modestr = ",-l";
366 			break;
367 		case 's':
368 			entry->mode = S_IFSOCK;
369 			entry->modestr = ",-s";
370 			break;
371 		case '-':
372 			entry->mode = S_IFREG;
373 			entry->modestr = ",--";
374 			break;
375 		default:
376 			errx(1, "%s: invalid mode string: %s", file, modestr);
377 		}
378 	} else {
379 		entry->modestr = "";
380 	}
381 }
382 
383 int
384 specs_empty(struct label_specs *specs)
385 {
386 
387 	return (STAILQ_EMPTY(&specs->head));
388 }
389 
390 int
391 apply_specs(struct label_specs *specs, FTSENT *ftsent, int hflag, int vflag)
392 {
393 	regmatch_t pmatch;
394 	struct label_spec *ls;
395 	struct label_spec_entry *ent;
396 	char *regerrorstr, *macstr;
397 	size_t size;
398 	mac_t mac;
399 	int error, matchedby;
400 
401 	/*
402 	 * Work through file context sources in order of specification
403 	 * on the command line, and through their entries in reverse
404 	 * order to find the "last" (hopefully "best") match.
405 	 */
406 	matchedby = 0;
407 	STAILQ_FOREACH(ls, &specs->head, link) {
408 		for (ls->match = NULL, ent = ls->entries;
409 		    ent < &ls->entries[ls->nentries]; ent++) {
410 			if (ent->flags & F_ALWAYSMATCH)
411 				goto matched;
412 			if (ent->mode != 0 &&
413 			    (ftsent->fts_statp->st_mode & S_IFMT) != ent->mode)
414 				continue;
415 			pmatch.rm_so = 0;
416 			pmatch.rm_eo = ftsent->fts_pathlen;
417 			error = regexec(&ent->regex, ftsent->fts_path, 1,
418 			    &pmatch, REG_STARTEND);
419 			switch (error) {
420 			case REG_NOMATCH:
421 				continue;
422 			case 0:
423 				break;
424 			default:
425 				size = regerror(error, &ent->regex, NULL, 0);
426 				regerrorstr = malloc(size);
427 				if (regerrorstr == NULL)
428 					err(1, "malloc");
429 				(void)regerror(error, &ent->regex, regerrorstr,
430 				    size);
431 				errx(1, "%s: %s", ent->regexstr, regerrorstr);
432 			}
433 		matched:
434 			ls->match = ent;
435 			if (vflag) {
436 				if (matchedby == 0) {
437 					printf("%s matched by ",
438 					    ftsent->fts_path);
439 					matchedby = 1;
440 				}
441 				printf("%s(%s%s,%s)", matchedby == 2 ? "," : "",
442 				    ent->regexstr, ent->modestr, ent->mactext);
443 				if (matchedby == 1)
444 					matchedby = 2;
445 			}
446 			break;
447 		}
448 	}
449 	if (vflag && matchedby)
450 		printf("\n");
451 	size = 0;
452 	STAILQ_FOREACH(ls, &specs->head, link) {
453 		/* cached match decision */
454 		if (ls->match && (ls->match->flags & F_DONTLABEL) == 0)
455 			 /* add length of "x\0"/"y," */
456 			size += strlen(ls->match->mactext) + 1;
457 	}
458 	if (size == 0)
459 		return (0);
460 	macstr = malloc(size);
461 	if (macstr == NULL)
462 		err(1, "malloc");
463 	*macstr = '\0';
464 	STAILQ_FOREACH(ls, &specs->head, link) {
465 		/* cached match decision */
466 		if (ls->match && (ls->match->flags & F_DONTLABEL) == 0) {
467 			if (*macstr != '\0')
468 				strcat(macstr, ",");
469 			strcat(macstr, ls->match->mactext);
470 		}
471 	}
472 	if (mac_from_text(&mac, macstr))
473 		err(1, "mac_from_text(%s)", macstr);
474 	if ((hflag == FTS_PHYSICAL ? mac_set_link(ftsent->fts_accpath, mac) :
475 	    mac_set_file(ftsent->fts_accpath, mac)) != 0) {
476 		if (errno == EOPNOTSUPP) {
477 			mac_free(mac);
478 			free(macstr);
479 			return (1);
480 		}
481 		err(1, "mac_set_link(%s, %s)", ftsent->fts_path, macstr);
482 	}
483 	mac_free(mac);
484 	free(macstr);
485 	return (0);
486 }
487 
488 struct label_specs *
489 new_specs(void)
490 {
491 	struct label_specs *specs;
492 
493 	specs = malloc(sizeof(*specs));
494 	if (specs == NULL)
495 		err(1, "malloc");
496 	STAILQ_INIT(&specs->head);
497 	return (specs);
498 }
499