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