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
50struct 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
66struct label_specs {
67	STAILQ_HEAD(label_specs_head, label_spec) head;
68};
69
70void usage(int) __dead2;
71struct label_specs *new_specs(void);
72void add_specs(struct label_specs *, const char *, int);
73void add_setfmac_specs(struct label_specs *, char *);
74void add_spec_line(const char *, int, struct label_spec_entry *, char *);
75int apply_specs(struct label_specs *, FTSENT *, int, int);
76int specs_empty(struct label_specs *);
77
78static int qflag;
79
80int
81main(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
181void
182usage(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
192static int
193chomp_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
227void
228add_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
281void
282add_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
299void
300add_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
383int
384specs_empty(struct label_specs *specs)
385{
386
387	return (STAILQ_EMPTY(&specs->head));
388}
389
390int
391apply_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
488struct label_specs *
489new_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