FreeBSD-5.3/usr.sbin/setfmac/setfmac.c

Compare this file to the similar file:
Show the results in this format:

/*-
 * 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.
 *
 * $FreeBSD: src/usr.sbin/setfmac/setfmac.c,v 1.7 2004/02/18 05:40:15 rwatson Exp $
 */

#include <sys/types.h>
#include <sys/mac.h>
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/mac.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 */
		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 ((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_pathlen,
					    ftsent->fts_path);
				}
				if (!qflag)
					warnx("labeling not supported in %.*s",
					    ftsent->fts_pathlen,
					    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_pathlen,
			    ftsent->fts_path);
		default:
			errx(1, "CANNOT HAPPEN (%d) traversing %.*s",
			    ftsent->fts_info, ftsent->fts_pathlen,
			    ftsent->fts_path);
		}
	}
	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);
}

int
chomp_line(char **line, size_t *linesize)
{
	char *s;
	int freeme = 0;
	
	for (s = *line; 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(&regexstr, "^%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_pathlen,
					    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_pathlen,
		    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);
}