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