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
main(int argc,char ** argv)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 (errno = 0, (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 if (errno != 0)
180 err(1, "fts_read");
181 fts_close(fts);
182 exit(0);
183 }
184
185 void
usage(int is_setfmac)186 usage(int is_setfmac)
187 {
188
189 if (is_setfmac)
190 fprintf(stderr, "usage: setfmac [-Rhq] label file ...\n");
191 else
192 fprintf(stderr, "usage: setfsmac [-ehqvx] [-f specfile [...]] [-s specfile [...]] file ...\n");
193 exit(1);
194 }
195
196 static int
chomp_line(char ** line,size_t * linesize)197 chomp_line(char **line, size_t *linesize)
198 {
199 char *s;
200 int freeme = 0;
201
202 for (s = *line; (unsigned)(s - *line) < *linesize; s++) {
203 if (!isspace(*s))
204 break;
205 }
206 if (*s == '#') {
207 **line = '\0';
208 *linesize = 0;
209 return (freeme);
210 }
211 memmove(*line, s, *linesize - (s - *line));
212 *linesize -= s - *line;
213 for (s = &(*line)[*linesize - 1]; s >= *line; s--) {
214 if (!isspace(*s))
215 break;
216 }
217 if (s != &(*line)[*linesize - 1]) {
218 *linesize = s - *line + 1;
219 } else {
220 s = malloc(*linesize + 1);
221 if (s == NULL)
222 err(1, "malloc");
223 strncpy(s, *line, *linesize);
224 *line = s;
225 freeme = 1;
226 }
227 (*line)[*linesize] = '\0';
228 return (freeme);
229 }
230
231 void
add_specs(struct label_specs * specs,const char * file,int is_sebsd)232 add_specs(struct label_specs *specs, const char *file, int is_sebsd)
233 {
234 struct label_spec *spec;
235 FILE *fp;
236 char *line;
237 size_t nlines = 0, linesize;
238 int freeline;
239
240 spec = malloc(sizeof(*spec));
241 if (spec == NULL)
242 err(1, "malloc");
243 fp = fopen(file, "r");
244 if (fp == NULL)
245 err(1, "opening %s", file);
246 while ((line = fgetln(fp, &linesize)) != NULL) {
247 freeline = chomp_line(&line, &linesize);
248 if (linesize > 0) /* only allocate space for non-comments */
249 nlines++;
250 if (freeline)
251 free(line);
252 }
253 if (ferror(fp))
254 err(1, "fgetln on %s", file);
255 rewind(fp);
256 spec->entries = calloc(nlines, sizeof(*spec->entries));
257 if (spec->entries == NULL)
258 err(1, "malloc");
259 spec->nentries = nlines;
260 while (nlines > 0) {
261 line = fgetln(fp, &linesize);
262 if (line == NULL) {
263 if (feof(fp))
264 errx(1, "%s ended prematurely", file);
265 else
266 err(1, "failure reading %s", file);
267 }
268 freeline = chomp_line(&line, &linesize);
269 if (linesize == 0) {
270 if (freeline)
271 free(line);
272 continue;
273 }
274 add_spec_line(file, is_sebsd, &spec->entries[--nlines], line);
275 if (freeline)
276 free(line);
277 }
278 fclose(fp);
279 if (!qflag)
280 warnx("%s: read %lu specifications", file,
281 (long)spec->nentries);
282 STAILQ_INSERT_TAIL(&specs->head, spec, link);
283 }
284
285 void
add_setfmac_specs(struct label_specs * specs,char * label)286 add_setfmac_specs(struct label_specs *specs, char *label)
287 {
288 struct label_spec *spec;
289
290 spec = malloc(sizeof(*spec));
291 if (spec == NULL)
292 err(1, "malloc");
293 spec->nentries = 1;
294 spec->entries = calloc(spec->nentries, sizeof(*spec->entries));
295 if (spec->entries == NULL)
296 err(1, "malloc");
297 /* The _only_ thing specified here is the mactext! */
298 spec->entries->mactext = label;
299 spec->entries->flags |= F_ALWAYSMATCH;
300 STAILQ_INSERT_TAIL(&specs->head, spec, link);
301 }
302
303 void
add_spec_line(const char * file,int is_sebsd,struct label_spec_entry * entry,char * line)304 add_spec_line(const char *file, int is_sebsd, struct label_spec_entry *entry,
305 char *line)
306 {
307 char *regexstr, *modestr, *macstr, *regerrorstr;
308 size_t size;
309 int error;
310
311 regexstr = strtok(line, " \t");
312 if (regexstr == NULL)
313 errx(1, "%s: need regular expression", file);
314 modestr = strtok(NULL, " \t");
315 if (modestr == NULL)
316 errx(1, "%s: need a label", file);
317 macstr = strtok(NULL, " \t");
318 if (macstr == NULL) { /* the mode is just optional */
319 macstr = modestr;
320 modestr = NULL;
321 }
322 if (strtok(NULL, " \t") != NULL)
323 errx(1, "%s: extraneous fields at end of line", file);
324 /* assume we need to anchor this regex */
325 if (asprintf(®exstr, "^%s$", regexstr) == -1)
326 err(1, "%s: processing regular expression", file);
327 entry->regexstr = regexstr;
328 error = regcomp(&entry->regex, regexstr, REG_EXTENDED | REG_NOSUB);
329 if (error) {
330 size = regerror(error, &entry->regex, NULL, 0);
331 regerrorstr = malloc(size);
332 if (regerrorstr == NULL)
333 err(1, "malloc");
334 (void)regerror(error, &entry->regex, regerrorstr, size);
335 errx(1, "%s: %s: %s", file, entry->regexstr, regerrorstr);
336 }
337 if (!is_sebsd) {
338 entry->mactext = strdup(macstr);
339 if (entry->mactext == NULL)
340 err(1, "strdup");
341 } else {
342 if (asprintf(&entry->mactext, "sebsd/%s", macstr) == -1)
343 err(1, "asprintf");
344 if (strcmp(macstr, "<<none>>") == 0)
345 entry->flags |= F_DONTLABEL;
346 }
347 if (modestr != NULL) {
348 if (strlen(modestr) != 2 || modestr[0] != '-')
349 errx(1, "%s: invalid mode string: %s", file, modestr);
350 switch (modestr[1]) {
351 case 'b':
352 entry->mode = S_IFBLK;
353 entry->modestr = ",-b";
354 break;
355 case 'c':
356 entry->mode = S_IFCHR;
357 entry->modestr = ",-c";
358 break;
359 case 'd':
360 entry->mode = S_IFDIR;
361 entry->modestr = ",-d";
362 break;
363 case 'p':
364 entry->mode = S_IFIFO;
365 entry->modestr = ",-p";
366 break;
367 case 'l':
368 entry->mode = S_IFLNK;
369 entry->modestr = ",-l";
370 break;
371 case 's':
372 entry->mode = S_IFSOCK;
373 entry->modestr = ",-s";
374 break;
375 case '-':
376 entry->mode = S_IFREG;
377 entry->modestr = ",--";
378 break;
379 default:
380 errx(1, "%s: invalid mode string: %s", file, modestr);
381 }
382 } else {
383 entry->modestr = "";
384 }
385 }
386
387 int
specs_empty(struct label_specs * specs)388 specs_empty(struct label_specs *specs)
389 {
390
391 return (STAILQ_EMPTY(&specs->head));
392 }
393
394 int
apply_specs(struct label_specs * specs,FTSENT * ftsent,int hflag,int vflag)395 apply_specs(struct label_specs *specs, FTSENT *ftsent, int hflag, int vflag)
396 {
397 regmatch_t pmatch;
398 struct label_spec *ls;
399 struct label_spec_entry *ent;
400 char *regerrorstr, *macstr;
401 size_t size;
402 mac_t mac;
403 int error, matchedby;
404
405 /*
406 * Work through file context sources in order of specification
407 * on the command line, and through their entries in reverse
408 * order to find the "last" (hopefully "best") match.
409 */
410 matchedby = 0;
411 STAILQ_FOREACH(ls, &specs->head, link) {
412 for (ls->match = NULL, ent = ls->entries;
413 ent < &ls->entries[ls->nentries]; ent++) {
414 if (ent->flags & F_ALWAYSMATCH)
415 goto matched;
416 if (ent->mode != 0 &&
417 (ftsent->fts_statp->st_mode & S_IFMT) != ent->mode)
418 continue;
419 pmatch.rm_so = 0;
420 pmatch.rm_eo = ftsent->fts_pathlen;
421 error = regexec(&ent->regex, ftsent->fts_path, 1,
422 &pmatch, REG_STARTEND);
423 switch (error) {
424 case REG_NOMATCH:
425 continue;
426 case 0:
427 break;
428 default:
429 size = regerror(error, &ent->regex, NULL, 0);
430 regerrorstr = malloc(size);
431 if (regerrorstr == NULL)
432 err(1, "malloc");
433 (void)regerror(error, &ent->regex, regerrorstr,
434 size);
435 errx(1, "%s: %s", ent->regexstr, regerrorstr);
436 }
437 matched:
438 ls->match = ent;
439 if (vflag) {
440 if (matchedby == 0) {
441 printf("%s matched by ",
442 ftsent->fts_path);
443 matchedby = 1;
444 }
445 printf("%s(%s%s,%s)", matchedby == 2 ? "," : "",
446 ent->regexstr, ent->modestr, ent->mactext);
447 if (matchedby == 1)
448 matchedby = 2;
449 }
450 break;
451 }
452 }
453 if (vflag && matchedby)
454 printf("\n");
455 size = 0;
456 STAILQ_FOREACH(ls, &specs->head, link) {
457 /* cached match decision */
458 if (ls->match && (ls->match->flags & F_DONTLABEL) == 0)
459 /* add length of "x\0"/"y," */
460 size += strlen(ls->match->mactext) + 1;
461 }
462 if (size == 0)
463 return (0);
464 macstr = malloc(size);
465 if (macstr == NULL)
466 err(1, "malloc");
467 *macstr = '\0';
468 STAILQ_FOREACH(ls, &specs->head, link) {
469 /* cached match decision */
470 if (ls->match && (ls->match->flags & F_DONTLABEL) == 0) {
471 if (*macstr != '\0')
472 strcat(macstr, ",");
473 strcat(macstr, ls->match->mactext);
474 }
475 }
476 if (mac_from_text(&mac, macstr))
477 err(1, "mac_from_text(%s)", macstr);
478 if ((hflag == FTS_PHYSICAL ? mac_set_link(ftsent->fts_accpath, mac) :
479 mac_set_file(ftsent->fts_accpath, mac)) != 0) {
480 if (errno == EOPNOTSUPP) {
481 mac_free(mac);
482 free(macstr);
483 return (1);
484 }
485 err(1, "mac_set_link(%s, %s)", ftsent->fts_path, macstr);
486 }
487 mac_free(mac);
488 free(macstr);
489 return (0);
490 }
491
492 struct label_specs *
new_specs(void)493 new_specs(void)
494 {
495 struct label_specs *specs;
496
497 specs = malloc(sizeof(*specs));
498 if (specs == NULL)
499 err(1, "malloc");
500 STAILQ_INIT(&specs->head);
501 return (specs);
502 }
503