1 /*- 2 * Copyright (c) 2002 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/types.h> 37 #include <sys/queue.h> 38 #include <sys/mac.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 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 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 ? "Rh" : "ef:s:vx")) != -1) { 97 switch (ch) { 98 case 'R': 99 Rflag = 1; 100 break; 101 case 'e': 102 eflag = 1; 103 break; 104 case 'f': 105 add_specs(specs, optarg, 0); 106 break; 107 case 'h': 108 hflag = FTS_PHYSICAL; 109 break; 110 case 's': 111 add_specs(specs, optarg, 1); 112 break; 113 case 'v': 114 vflag++; 115 break; 116 case 'x': 117 xflag = FTS_XDEV; 118 break; 119 default: 120 usage(is_setfmac); 121 } 122 } 123 argc -= optind; 124 argv += optind; 125 126 if (is_setfmac) { 127 if (argc <= 1) 128 usage(is_setfmac); 129 add_setfmac_specs(specs, *argv); 130 argc--; 131 argv++; 132 } else { 133 if (argc == 0 || specs_empty(specs)) 134 usage(is_setfmac); 135 } 136 fts = fts_open(argv, hflag | xflag, NULL); 137 if (fts == NULL) 138 err(1, "cannot traverse filesystem%s", argc ? "s" : ""); 139 while ((ftsent = fts_read(fts)) != NULL) { 140 switch (ftsent->fts_info) { 141 case FTS_DP: /* skip post-order */ 142 break; 143 case FTS_D: /* do pre-order */ 144 case FTS_DC: /* do cyclic? */ 145 /* don't ever recurse directories as setfmac(8) */ 146 if (is_setfmac && !Rflag) 147 fts_set(fts, ftsent, FTS_SKIP); 148 case FTS_DEFAULT: /* do default */ 149 case FTS_F: /* do regular */ 150 case FTS_SL: /* do symlink */ 151 case FTS_SLNONE: /* do symlink */ 152 case FTS_W: /* do whiteout */ 153 if (apply_specs(specs, ftsent, hflag, vflag)) { 154 if (eflag) { 155 errx(1, "labeling not supported in " 156 "%.*s", ftsent->fts_pathlen, 157 ftsent->fts_path); 158 } 159 warnx("labeling not supported in %.*s", 160 ftsent->fts_pathlen, ftsent->fts_path); 161 fts_set(fts, ftsent, FTS_SKIP); 162 } 163 break; 164 case FTS_DNR: /* die on all errors */ 165 case FTS_ERR: 166 case FTS_NS: 167 err(1, "traversing %.*s", ftsent->fts_pathlen, 168 ftsent->fts_path); 169 default: 170 errx(1, "CANNOT HAPPEN (%d) traversing %.*s", 171 ftsent->fts_info, ftsent->fts_pathlen, 172 ftsent->fts_path); 173 } 174 } 175 fts_close(fts); 176 exit(0); 177 } 178 179 void 180 usage(int is_setfmac) 181 { 182 183 if (is_setfmac) 184 fprintf(stderr, "usage: setfmac [-Rh] label file ...\n"); 185 else 186 fprintf(stderr, "usage: setfsmac [-ehvx] [-f specfile [...]] [-s specfile [...]] file ...\n"); 187 exit(1); 188 } 189 190 int 191 chomp_line(char **line, size_t *linesize) 192 { 193 char *s; 194 int freeme = 0; 195 196 for (s = *line; s - *line < *linesize; s++) { 197 if (!isspace(*s)) 198 break; 199 } 200 if (*s == '#') { 201 **line = '\0'; 202 *linesize = 0; 203 return (freeme); 204 } 205 memmove(*line, s, *linesize - (s - *line)); 206 *linesize -= s - *line; 207 for (s = &(*line)[*linesize - 1]; s >= *line; s--) { 208 if (!isspace(*s)) 209 break; 210 } 211 if (s != &(*line)[*linesize - 1]) { 212 *linesize = s - *line + 1; 213 } else { 214 s = malloc(*linesize + 1); 215 if (s == NULL) 216 err(1, "malloc"); 217 strncpy(s, *line, *linesize); 218 *line = s; 219 freeme = 1; 220 } 221 (*line)[*linesize] = '\0'; 222 return (freeme); 223 } 224 225 void 226 add_specs(struct label_specs *specs, const char *file, int is_sebsd) 227 { 228 struct label_spec *spec; 229 FILE *fp; 230 char *line; 231 size_t nlines = 0, linesize; 232 int freeline; 233 234 spec = malloc(sizeof(*spec)); 235 if (spec == NULL) 236 err(1, "malloc"); 237 fp = fopen(file, "r"); 238 if (fp == NULL) 239 err(1, "opening %s", file); 240 while ((line = fgetln(fp, &linesize)) != NULL) { 241 freeline = chomp_line(&line, &linesize); 242 if (linesize > 0) /* only allocate space for non-comments */ 243 nlines++; 244 if (freeline) 245 free(line); 246 } 247 if (ferror(fp)) 248 err(1, "fgetln on %s", file); 249 rewind(fp); 250 spec->entries = calloc(nlines, sizeof(*spec->entries)); 251 if (spec->entries == NULL) 252 err(1, "malloc"); 253 spec->nentries = nlines; 254 while (nlines > 0) { 255 line = fgetln(fp, &linesize); 256 if (line == NULL) { 257 if (feof(fp)) 258 errx(1, "%s ended prematurely", file); 259 else 260 err(1, "failure reading %s", file); 261 } 262 freeline = chomp_line(&line, &linesize); 263 if (linesize == 0) { 264 if (freeline) 265 free(line); 266 continue; 267 } 268 add_spec_line(file, is_sebsd, &spec->entries[--nlines], line); 269 if (freeline) 270 free(line); 271 } 272 fclose(fp); 273 warnx("%s: read %lu specifications", file, (long)spec->nentries); 274 STAILQ_INSERT_TAIL(&specs->head, spec, link); 275 } 276 277 void 278 add_setfmac_specs(struct label_specs *specs, char *label) 279 { 280 struct label_spec *spec; 281 282 spec = malloc(sizeof(*spec)); 283 if (spec == NULL) 284 err(1, "malloc"); 285 spec->nentries = 1; 286 spec->entries = calloc(spec->nentries, sizeof(*spec->entries)); 287 if (spec->entries == NULL) 288 err(1, "malloc"); 289 /* The _only_ thing specified here is the mactext! */ 290 spec->entries->mactext = label; 291 spec->entries->flags |= F_ALWAYSMATCH; 292 STAILQ_INSERT_TAIL(&specs->head, spec, link); 293 } 294 295 void 296 add_spec_line(const char *file, int is_sebsd, struct label_spec_entry *entry, 297 char *line) 298 { 299 char *regexstr, *modestr, *macstr, *regerrorstr; 300 size_t size; 301 int error; 302 303 regexstr = strtok(line, " \t"); 304 if (regexstr == NULL) 305 errx(1, "%s: need regular expression", file); 306 modestr = strtok(NULL, " \t"); 307 if (modestr == NULL) 308 errx(1, "%s: need a label", file); 309 macstr = strtok(NULL, " \t"); 310 if (macstr == NULL) { /* the mode is just optional */ 311 macstr = modestr; 312 modestr = NULL; 313 } 314 if (strtok(NULL, " \t") != NULL) 315 errx(1, "%s: extraneous fields at end of line", file); 316 /* assume we need to anchor this regex */ 317 if (asprintf(®exstr, "^%s$", regexstr) == -1) 318 err(1, "%s: processing regular expression", file); 319 entry->regexstr = regexstr; 320 error = regcomp(&entry->regex, regexstr, REG_EXTENDED | REG_NOSUB); 321 if (error) { 322 size = regerror(error, &entry->regex, NULL, 0); 323 regerrorstr = malloc(size); 324 if (regerrorstr == NULL) 325 err(1, "malloc"); 326 (void)regerror(error, &entry->regex, regerrorstr, size); 327 errx(1, "%s: %s: %s", file, entry->regexstr, regerrorstr); 328 } 329 if (!is_sebsd) { 330 entry->mactext = strdup(macstr); 331 if (entry->mactext == NULL) 332 err(1, "strdup"); 333 } else { 334 if (asprintf(&entry->mactext, "sebsd/%s", macstr) == -1) 335 err(1, "asprintf"); 336 if (strcmp(macstr, "<<none>>") == 0) 337 entry->flags |= F_DONTLABEL; 338 } 339 if (modestr != NULL) { 340 if (strlen(modestr) != 2 || modestr[0] != '-') 341 errx(1, "%s: invalid mode string: %s", file, modestr); 342 switch (modestr[1]) { 343 case 'b': 344 entry->mode = S_IFBLK; 345 entry->modestr = ",-b"; 346 break; 347 case 'c': 348 entry->mode = S_IFCHR; 349 entry->modestr = ",-c"; 350 break; 351 case 'd': 352 entry->mode = S_IFDIR; 353 entry->modestr = ",-d"; 354 break; 355 case 'p': 356 entry->mode = S_IFIFO; 357 entry->modestr = ",-p"; 358 break; 359 case 'l': 360 entry->mode = S_IFLNK; 361 entry->modestr = ",-l"; 362 break; 363 case 's': 364 entry->mode = S_IFSOCK; 365 entry->modestr = ",-s"; 366 break; 367 case '-': 368 entry->mode = S_IFREG; 369 entry->modestr = ",--"; 370 break; 371 default: 372 errx(1, "%s: invalid mode string: %s", file, modestr); 373 } 374 } else { 375 entry->modestr = ""; 376 } 377 } 378 379 int 380 specs_empty(struct label_specs *specs) 381 { 382 383 return (STAILQ_EMPTY(&specs->head)); 384 } 385 386 int 387 apply_specs(struct label_specs *specs, FTSENT *ftsent, int hflag, int vflag) 388 { 389 regmatch_t pmatch; 390 struct label_spec *ls; 391 struct label_spec_entry *ent; 392 char *regerrorstr, *macstr; 393 size_t size; 394 mac_t mac; 395 int error, matchedby; 396 397 /* 398 * Work through file context sources in order of specification 399 * on the command line, and through their entries in reverse 400 * order to find the "last" (hopefully "best") match. 401 */ 402 matchedby = 0; 403 STAILQ_FOREACH(ls, &specs->head, link) { 404 for (ls->match = NULL, ent = ls->entries; 405 ent < &ls->entries[ls->nentries]; ent++) { 406 if (ent->flags & F_ALWAYSMATCH) 407 goto matched; 408 if (ent->mode != 0 && 409 (ftsent->fts_statp->st_mode & S_IFMT) != ent->mode) 410 continue; 411 pmatch.rm_so = 0; 412 pmatch.rm_eo = ftsent->fts_pathlen; 413 error = regexec(&ent->regex, ftsent->fts_path, 1, 414 &pmatch, REG_STARTEND); 415 switch (error) { 416 case REG_NOMATCH: 417 continue; 418 case 0: 419 break; 420 default: 421 size = regerror(error, &ent->regex, NULL, 0); 422 regerrorstr = malloc(size); 423 if (regerrorstr == NULL) 424 err(1, "malloc"); 425 (void)regerror(error, &ent->regex, regerrorstr, 426 size); 427 errx(1, "%s: %s", ent->regexstr, regerrorstr); 428 } 429 matched: 430 ls->match = ent; 431 if (vflag) { 432 if (matchedby == 0) { 433 printf("%.*s matched by ", 434 ftsent->fts_pathlen, 435 ftsent->fts_path); 436 matchedby = 1; 437 } 438 printf("%s(%s%s,%s)", matchedby == 2 ? "," : "", 439 ent->regexstr, ent->modestr, ent->mactext); 440 if (matchedby == 1) 441 matchedby = 2; 442 } 443 break; 444 } 445 } 446 if (vflag && matchedby) 447 printf("\n"); 448 size = 0; 449 STAILQ_FOREACH(ls, &specs->head, link) { 450 /* cached match decision */ 451 if (ls->match && (ls->match->flags & F_DONTLABEL) == 0) 452 /* add length of "x\0"/"y," */ 453 size += strlen(ls->match->mactext) + 1; 454 } 455 if (size == 0) 456 return (0); 457 macstr = malloc(size); 458 if (macstr == NULL) 459 err(1, "malloc"); 460 *macstr = '\0'; 461 STAILQ_FOREACH(ls, &specs->head, link) { 462 /* cached match decision */ 463 if (ls->match && (ls->match->flags & F_DONTLABEL) == 0) { 464 if (*macstr != '\0') 465 strcat(macstr, ","); 466 strcat(macstr, ls->match->mactext); 467 } 468 } 469 if (mac_from_text(&mac, macstr)) 470 err(1, "mac_from_text(%s)", macstr); 471 if ((hflag == FTS_PHYSICAL ? mac_set_link(ftsent->fts_accpath, mac) : 472 mac_set_file(ftsent->fts_accpath, mac)) != 0) { 473 if (errno == EOPNOTSUPP) { 474 mac_free(mac); 475 free(macstr); 476 return (1); 477 } 478 err(1, "mac_set_link(%.*s, %s)", ftsent->fts_pathlen, 479 ftsent->fts_path, macstr); 480 } 481 mac_free(mac); 482 free(macstr); 483 return (0); 484 } 485 486 struct label_specs * 487 new_specs(void) 488 { 489 struct label_specs *specs; 490 491 specs = malloc(sizeof(*specs)); 492 if (specs == NULL) 493 err(1, "malloc"); 494 STAILQ_INIT(&specs->head); 495 return (specs); 496 } 497