1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
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
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
main(int argc,char ** argv)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 (errno = 0, (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 if (errno != 0)
178 err(1, "fts_read");
179 fts_close(fts);
180 exit(0);
181 }
182
183 void
usage(int is_setfmac)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
chomp_line(char ** line,size_t * linesize)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
add_specs(struct label_specs * specs,const char * file,int is_sebsd)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
add_setfmac_specs(struct label_specs * specs,char * label)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
add_spec_line(const char * file,int is_sebsd,struct label_spec_entry * entry,char * line)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(®exstr, "^%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
specs_empty(struct label_specs * specs)386 specs_empty(struct label_specs *specs)
387 {
388
389 return (STAILQ_EMPTY(&specs->head));
390 }
391
392 int
apply_specs(struct label_specs * specs,FTSENT * ftsent,int hflag,int vflag)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 *
new_specs(void)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