Index: Makefile
===================================================================
RCS file: /data/ncvs/src/usr.sbin/setfmac/Makefile,v
retrieving revision 1.1
diff -u -r1.1 Makefile
--- Makefile	23 Oct 2002 03:15:23 -0000	1.1
+++ Makefile	2 Dec 2002 01:59:45 -0000
@@ -3,5 +3,11 @@
 PROG=   setfmac
 SRCS=   setfmac.c
 MAN=	setfmac.8
-CFLAGS+=-Wall
+
+LINKS+=	${BINDIR}/setfmac ${BINDIR}/setfsmac
+
+MLINKS+=	setfmac.8 setfsmac.8
+
+WARNS?=	2
+
 .include <bsd.prog.mk>
Index: setfmac.8
===================================================================
RCS file: /data/ncvs/src/usr.sbin/setfmac/setfmac.8,v
retrieving revision 1.2
diff -u -r1.2 setfmac.8
--- setfmac.8	29 Nov 2002 16:35:15 -0000	1.2
+++ setfmac.8	2 Dec 2002 02:28:23 -0000
@@ -15,9 +15,6 @@
 .\" 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.
-.\" 3. The names of the authors may not be used to endorse or promote
-.\"    products derived from this software without specific prior written
-.\"    permission.
 .\"
 .\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
@@ -35,18 +32,83 @@
 .Dd June 27, 2002
 .Dt SETFMAC 8
 .Sh NAME
-.Nm setfmac
+.Nm setfmac ,
+.Nm setfsmac
 .Nd set MAC label for a file system object
 .Sh SYNOPSIS
-.Nm
+.Nm setfmac
+.Op Fl h
 .Ar label
-.Ar
+.Op Ar file ...
+.Nm setfsmac
+.Op Fl ehvx
+.Op Fl f Ar specfile
+.Op Fl s Ar specfile
+.Ar path
+.Op Ar file ...
 .Sh DESCRIPTION
 The
-.Nm
-utility associates the specified MAC label to the specified files.
+.Nm setfmac
+utility assigns the specified MAC label to the specified files.
+The following options are available:
+.Bl -tag -width indent
+.It Fl h
+If the file is a symbolic link, change the label of the link rather
+than the file that the link points to.
+.El
+.Pp
+The
+.Nm setfsmac
+utility takes any number of specification files as input and sets the MAC
+labels on arbitrary file system hierarchies.
+Path names specified will be visited in order as given on the command line,
+and each tree will be traversed in pre-order.
+(Generally, it will not be very useful to use relative, instead of absolute,
+paths.)
+The labels that a given file matches will be combined and set in a single
+transaction.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl e
+Treat any filesystems encountered which do not support MAC labelling as
+errors, instead of warning and skipping past them.
+.It Fl f Ar specfile
+Add the specifications in
+.Ar specfile
+as a set of which at most one will be applied to each file traversed per
+.Fl f Ar specfile
+given.
+.It Fl h
+If the file is a symbolic link, change the label of the link rather
+than the file that the link points to.
+.It Fl s Ar specfile
+Add the specification in
+.Ar specfile ,
+but assume that the specification format is that used in the port
+of SELinux to FreeBSD as SEBSD.
+At most one of the specifications will be applied to each file traversed per
+.Fl f Ar specfile
+given.
+The prefix
+.Dq sebsd/
+will automatically be prepended to the labels in this file, and labels
+matching
+.Dq <<none>>
+will be explicitly not relabeled.
+This permits SEBSD to re-use existing SELinux policy specification files
+unmodified.
+.It Fl v
+Increase the degree of verbosity.
+When given, information detailing the labelling operation is printed while
+in progress.
+.It Fl x
+Do not cross recurse into new filesystems when traversing them.
+.El
 .Sh SEE ALSO
 .Xr mac 3 ,
 .Xr mac_set_file 3 ,
+.Xr mac_set_link 3 ,
+.Xr re_format 7 ,
 .Xr getfmac 8 ,
 .Xr mac 9
Index: setfmac.c
===================================================================
RCS file: /data/ncvs/src/usr.sbin/setfmac/setfmac.c,v
retrieving revision 1.1
diff -u -r1.1 setfmac.c
--- setfmac.c	23 Oct 2002 03:15:23 -0000	1.1
+++ setfmac.c	2 Dec 2002 00:58:33 -0000
@@ -31,73 +31,468 @@
  * 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.1 2002/10/23 03:15:23 rwatson Exp $
+ * $FreeBSD$
  */
+
+#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 <paths.h>
+#include <errno.h>
+#include <fts.h>
+#include <libgen.h>
+#include <regex.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sysexits.h>
 #include <unistd.h>
 
-#define	MAXELEMENTS	32
+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 *);
+
+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 ? "Rh" : "ef:s: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 '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_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);
+				}
+				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(void)
+usage(int is_setfmac)
 {
 
-	fprintf(stderr, "setfmac [-h] [label] [file1] [file2 ...]\n");
-	exit (EX_USAGE);
+	if (is_setfmac)
+		fprintf(stderr, "usage: setfmac [-Rh] label path [...]\n");
+	else
+		fprintf(stderr, "usage: setfsmac [-evx] [-f specfile [...]] [-s specfile [...]] path [...]\n");
+	exit(1);
 }
 
 int
-main(int argc, char *argv[])
+chomp_line(char **line, size_t *linesize)
 {
-	char ch;
-	mac_t label;
-	int hflag;
-	int error, i;
+	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);
+}
 
-	hflag = 0;
-	while ((ch = getopt(argc, argv, "h")) != -1) {
-		switch (ch) {
-		case 'h':
-			hflag = 1;
+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);
+	warnx("%s: read %u specifications", file, 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:
-			usage();
+			errx(1, "%s: invalid mode string: %s", file, modestr);
 		}
+	} else {
+		entry->modestr = "";
 	}
+}
 
-	argv += optind;
-	argc -= optind;
+int
+specs_empty(struct label_specs *specs)
+{
 
-	if (argc < 2)
-		usage();
+	return (STAILQ_EMPTY(&specs->head));
+}
 
-	error = mac_from_text(&label, argv[0]);
-	if (error != 0) {
-		perror("mac_from_text");
-		return (-1);
+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;
+		}
 	}
-
-	for (i = 1; i < argc; i++) {
-		if (hflag)
-			error = mac_set_link(argv[i], label);
-		else
-			error = mac_set_file(argv[i], label);
-		if (error != 0) {
-			perror(argv[i]);
-			return (-1);
+	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);
+}
 
-	mac_free(label);
+struct label_specs *
+new_specs(void)
+{
+	struct label_specs *specs;
 
-	exit(EX_OK);
+	specs = malloc(sizeof(*specs));
+	if (specs == NULL)
+		err(1, "malloc");
+	STAILQ_INIT(&specs->head);
+	return (specs);
 }
