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: stable/11/usr.sbin/setfmac/setfmac.c 330449 2018-03-05 07:26:05Z eadler $
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
52struct 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
68struct label_specs {
69	STAILQ_HEAD(label_specs_head, label_spec) head;
70};
71
72void usage(int) __dead2;
73struct label_specs *new_specs(void);
74void add_specs(struct label_specs *, const char *, int);
75void add_setfmac_specs(struct label_specs *, char *);
76void add_spec_line(const char *, int, struct label_spec_entry *, char *);
77int apply_specs(struct label_specs *, FTSENT *, int, int);
78int specs_empty(struct label_specs *);
79
80static int qflag;
81
82int
83main(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
183void
184usage(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
194static int
195chomp_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
229void
230add_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
283void
284add_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
301void
302add_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
385int
386specs_empty(struct label_specs *specs)
387{
388
389	return (STAILQ_EMPTY(&specs->head));
390}
391
392int
393apply_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
490struct label_specs *
491new_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