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