/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2002, 2004 Networks Associates Technology, Inc.
* All rights reserved.
*
* This software was developed for the FreeBSD Project by NAI Labs, the
* Security Research Division of Network Associates, Inc. under
* DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA
* CHATS research program.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/types.h>
#include <sys/mac.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fts.h>
#include <libgen.h>
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
struct label_spec {
struct label_spec_entry {
regex_t regex; /* compiled regular expression to match */
char *regexstr; /* uncompiled regular expression */
mode_t mode; /* mode to possibly match */
const char *modestr; /* print-worthy ",-?" mode string */
char *mactext; /* MAC label to apply */
int flags; /* miscellaneous flags */
#define F_DONTLABEL 0x01
#define F_ALWAYSMATCH 0x02
} *entries, /* entries[0..nentries] */
*match; /* cached decision for MAC label to apply */
size_t nentries; /* size of entries list */
STAILQ_ENTRY(label_spec) link;
};
struct label_specs {
STAILQ_HEAD(label_specs_head, label_spec) head;
};
void usage(int) __dead2;
struct label_specs *new_specs(void);
void add_specs(struct label_specs *, const char *, int);
void add_setfmac_specs(struct label_specs *, char *);
void add_spec_line(const char *, int, struct label_spec_entry *, char *);
int apply_specs(struct label_specs *, FTSENT *, int, int);
int specs_empty(struct label_specs *);
static int qflag;
int
main(int argc, char **argv)
{
FTSENT *ftsent;
FTS *fts;
struct label_specs *specs;
int eflag = 0, xflag = 0, vflag = 0, Rflag = 0, hflag;
int ch, is_setfmac;
char *bn;
bn = basename(argv[0]);
if (bn == NULL)
err(1, "basename");
is_setfmac = strcmp(bn, "setfmac") == 0;
hflag = is_setfmac ? FTS_LOGICAL : FTS_PHYSICAL;
specs = new_specs();
while ((ch = getopt(argc, argv, is_setfmac ? "Rhq" : "ef:qs:vx")) !=
-1) {
switch (ch) {
case 'R':
Rflag = 1;
break;
case 'e':
eflag = 1;
break;
case 'f':
add_specs(specs, optarg, 0);
break;
case 'h':
hflag = FTS_PHYSICAL;
break;
case 'q':
qflag = 1;
break;
case 's':
add_specs(specs, optarg, 1);
break;
case 'v':
vflag++;
break;
case 'x':
xflag = FTS_XDEV;
break;
default:
usage(is_setfmac);
}
}
argc -= optind;
argv += optind;
if (is_setfmac) {
if (argc <= 1)
usage(is_setfmac);
add_setfmac_specs(specs, *argv);
argc--;
argv++;
} else {
if (argc == 0 || specs_empty(specs))
usage(is_setfmac);
}
fts = fts_open(argv, hflag | xflag, NULL);
if (fts == NULL)
err(1, "cannot traverse filesystem%s", argc ? "s" : "");
while (errno = 0, (ftsent = fts_read(fts)) != NULL) {
switch (ftsent->fts_info) {
case FTS_DP: /* skip post-order */
break;
case FTS_D: /* do pre-order */
case FTS_DC: /* do cyclic? */
/* don't ever recurse directories as setfmac(8) */
if (is_setfmac && !Rflag)
fts_set(fts, ftsent, FTS_SKIP);
case FTS_DEFAULT: /* do default */
case FTS_F: /* do regular */
case FTS_SL: /* do symlink */
case FTS_SLNONE: /* do symlink */
case FTS_W: /* do whiteout */
if (apply_specs(specs, ftsent, hflag, vflag)) {
if (eflag) {
errx(1, "labeling not supported in %s",
ftsent->fts_path);
}
if (!qflag)
warnx("labeling not supported in %s",
ftsent->fts_path);
fts_set(fts, ftsent, FTS_SKIP);
}
break;
case FTS_DNR: /* die on all errors */
case FTS_ERR:
case FTS_NS:
err(1, "traversing %s", ftsent->fts_path);
default:
errx(1, "CANNOT HAPPEN (%d) traversing %s",
ftsent->fts_info, ftsent->fts_path);
}
}
if (errno != 0)
err(1, "fts_read");
fts_close(fts);
exit(0);
}
void
usage(int is_setfmac)
{
if (is_setfmac)
fprintf(stderr, "usage: setfmac [-Rhq] label file ...\n");
else
fprintf(stderr, "usage: setfsmac [-ehqvx] [-f specfile [...]] [-s specfile [...]] file ...\n");
exit(1);
}
static int
chomp_line(char **line, size_t *linesize)
{
char *s;
int freeme = 0;
for (s = *line; (unsigned)(s - *line) < *linesize; s++) {
if (!isspace(*s))
break;
}
if (*s == '#') {
**line = '\0';
*linesize = 0;
return (freeme);
}
memmove(*line, s, *linesize - (s - *line));
*linesize -= s - *line;
for (s = &(*line)[*linesize - 1]; s >= *line; s--) {
if (!isspace(*s))
break;
}
if (s != &(*line)[*linesize - 1]) {
*linesize = s - *line + 1;
} else {
s = malloc(*linesize + 1);
if (s == NULL)
err(1, "malloc");
strncpy(s, *line, *linesize);
*line = s;
freeme = 1;
}
(*line)[*linesize] = '\0';
return (freeme);
}
void
add_specs(struct label_specs *specs, const char *file, int is_sebsd)
{
struct label_spec *spec;
FILE *fp;
char *line;
size_t nlines = 0, linesize;
int freeline;
spec = malloc(sizeof(*spec));
if (spec == NULL)
err(1, "malloc");
fp = fopen(file, "r");
if (fp == NULL)
err(1, "opening %s", file);
while ((line = fgetln(fp, &linesize)) != NULL) {
freeline = chomp_line(&line, &linesize);
if (linesize > 0) /* only allocate space for non-comments */
nlines++;
if (freeline)
free(line);
}
if (ferror(fp))
err(1, "fgetln on %s", file);
rewind(fp);
spec->entries = calloc(nlines, sizeof(*spec->entries));
if (spec->entries == NULL)
err(1, "malloc");
spec->nentries = nlines;
while (nlines > 0) {
line = fgetln(fp, &linesize);
if (line == NULL) {
if (feof(fp))
errx(1, "%s ended prematurely", file);
else
err(1, "failure reading %s", file);
}
freeline = chomp_line(&line, &linesize);
if (linesize == 0) {
if (freeline)
free(line);
continue;
}
add_spec_line(file, is_sebsd, &spec->entries[--nlines], line);
if (freeline)
free(line);
}
fclose(fp);
if (!qflag)
warnx("%s: read %lu specifications", file,
(long)spec->nentries);
STAILQ_INSERT_TAIL(&specs->head, spec, link);
}
void
add_setfmac_specs(struct label_specs *specs, char *label)
{
struct label_spec *spec;
spec = malloc(sizeof(*spec));
if (spec == NULL)
err(1, "malloc");
spec->nentries = 1;
spec->entries = calloc(spec->nentries, sizeof(*spec->entries));
if (spec->entries == NULL)
err(1, "malloc");
/* The _only_ thing specified here is the mactext! */
spec->entries->mactext = label;
spec->entries->flags |= F_ALWAYSMATCH;
STAILQ_INSERT_TAIL(&specs->head, spec, link);
}
void
add_spec_line(const char *file, int is_sebsd, struct label_spec_entry *entry,
char *line)
{
char *regexstr, *modestr, *macstr, *regerrorstr;
size_t size;
int error;
regexstr = strtok(line, " \t");
if (regexstr == NULL)
errx(1, "%s: need regular expression", file);
modestr = strtok(NULL, " \t");
if (modestr == NULL)
errx(1, "%s: need a label", file);
macstr = strtok(NULL, " \t");
if (macstr == NULL) { /* the mode is just optional */
macstr = modestr;
modestr = NULL;
}
if (strtok(NULL, " \t") != NULL)
errx(1, "%s: extraneous fields at end of line", file);
/* assume we need to anchor this regex */
if (asprintf(®exstr, "^%s$", regexstr) == -1)
err(1, "%s: processing regular expression", file);
entry->regexstr = regexstr;
error = regcomp(&entry->regex, regexstr, REG_EXTENDED | REG_NOSUB);
if (error) {
size = regerror(error, &entry->regex, NULL, 0);
regerrorstr = malloc(size);
if (regerrorstr == NULL)
err(1, "malloc");
(void)regerror(error, &entry->regex, regerrorstr, size);
errx(1, "%s: %s: %s", file, entry->regexstr, regerrorstr);
}
if (!is_sebsd) {
entry->mactext = strdup(macstr);
if (entry->mactext == NULL)
err(1, "strdup");
} else {
if (asprintf(&entry->mactext, "sebsd/%s", macstr) == -1)
err(1, "asprintf");
if (strcmp(macstr, "<<none>>") == 0)
entry->flags |= F_DONTLABEL;
}
if (modestr != NULL) {
if (strlen(modestr) != 2 || modestr[0] != '-')
errx(1, "%s: invalid mode string: %s", file, modestr);
switch (modestr[1]) {
case 'b':
entry->mode = S_IFBLK;
entry->modestr = ",-b";
break;
case 'c':
entry->mode = S_IFCHR;
entry->modestr = ",-c";
break;
case 'd':
entry->mode = S_IFDIR;
entry->modestr = ",-d";
break;
case 'p':
entry->mode = S_IFIFO;
entry->modestr = ",-p";
break;
case 'l':
entry->mode = S_IFLNK;
entry->modestr = ",-l";
break;
case 's':
entry->mode = S_IFSOCK;
entry->modestr = ",-s";
break;
case '-':
entry->mode = S_IFREG;
entry->modestr = ",--";
break;
default:
errx(1, "%s: invalid mode string: %s", file, modestr);
}
} else {
entry->modestr = "";
}
}
int
specs_empty(struct label_specs *specs)
{
return (STAILQ_EMPTY(&specs->head));
}
int
apply_specs(struct label_specs *specs, FTSENT *ftsent, int hflag, int vflag)
{
regmatch_t pmatch;
struct label_spec *ls;
struct label_spec_entry *ent;
char *regerrorstr, *macstr;
size_t size;
mac_t mac;
int error, matchedby;
/*
* Work through file context sources in order of specification
* on the command line, and through their entries in reverse
* order to find the "last" (hopefully "best") match.
*/
matchedby = 0;
STAILQ_FOREACH(ls, &specs->head, link) {
for (ls->match = NULL, ent = ls->entries;
ent < &ls->entries[ls->nentries]; ent++) {
if (ent->flags & F_ALWAYSMATCH)
goto matched;
if (ent->mode != 0 &&
(ftsent->fts_statp->st_mode & S_IFMT) != ent->mode)
continue;
pmatch.rm_so = 0;
pmatch.rm_eo = ftsent->fts_pathlen;
error = regexec(&ent->regex, ftsent->fts_path, 1,
&pmatch, REG_STARTEND);
switch (error) {
case REG_NOMATCH:
continue;
case 0:
break;
default:
size = regerror(error, &ent->regex, NULL, 0);
regerrorstr = malloc(size);
if (regerrorstr == NULL)
err(1, "malloc");
(void)regerror(error, &ent->regex, regerrorstr,
size);
errx(1, "%s: %s", ent->regexstr, regerrorstr);
}
matched:
ls->match = ent;
if (vflag) {
if (matchedby == 0) {
printf("%s matched by ",
ftsent->fts_path);
matchedby = 1;
}
printf("%s(%s%s,%s)", matchedby == 2 ? "," : "",
ent->regexstr, ent->modestr, ent->mactext);
if (matchedby == 1)
matchedby = 2;
}
break;
}
}
if (vflag && matchedby)
printf("\n");
size = 0;
STAILQ_FOREACH(ls, &specs->head, link) {
/* cached match decision */
if (ls->match && (ls->match->flags & F_DONTLABEL) == 0)
/* add length of "x\0"/"y," */
size += strlen(ls->match->mactext) + 1;
}
if (size == 0)
return (0);
macstr = malloc(size);
if (macstr == NULL)
err(1, "malloc");
*macstr = '\0';
STAILQ_FOREACH(ls, &specs->head, link) {
/* cached match decision */
if (ls->match && (ls->match->flags & F_DONTLABEL) == 0) {
if (*macstr != '\0')
strcat(macstr, ",");
strcat(macstr, ls->match->mactext);
}
}
if (mac_from_text(&mac, macstr))
err(1, "mac_from_text(%s)", macstr);
if ((hflag == FTS_PHYSICAL ? mac_set_link(ftsent->fts_accpath, mac) :
mac_set_file(ftsent->fts_accpath, mac)) != 0) {
if (errno == EOPNOTSUPP) {
mac_free(mac);
free(macstr);
return (1);
}
err(1, "mac_set_link(%s, %s)", ftsent->fts_path, macstr);
}
mac_free(mac);
free(macstr);
return (0);
}
struct label_specs *
new_specs(void)
{
struct label_specs *specs;
specs = malloc(sizeof(*specs));
if (specs == NULL)
err(1, "malloc");
STAILQ_INIT(&specs->head);
return (specs);
}