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 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 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 ((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 181 void 182 usage(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 192 static int 193 chomp_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 227 void 228 add_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 281 void 282 add_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 299 void 300 add_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(®exstr, "^%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 383 int 384 specs_empty(struct label_specs *specs) 385 { 386 387 return (STAILQ_EMPTY(&specs->head)); 388 } 389 390 int 391 apply_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 488 struct label_specs * 489 new_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