]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - usr.sbin/setfmac/setfmac.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / usr.sbin / setfmac / setfmac.c
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/queue.h>
37 #include <sys/stat.h>
38
39 #include <ctype.h>
40 #include <err.h>
41 #include <errno.h>
42 #include <fts.h>
43 #include <libgen.h>
44 #include <regex.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49
50 struct label_spec {
51         struct label_spec_entry {
52                 regex_t regex;  /* compiled regular expression to match */
53                 char *regexstr; /* uncompiled regular expression */
54                 mode_t mode;    /* mode to possibly match */
55                 const char *modestr;    /* print-worthy ",-?" mode string */
56                 char *mactext;  /* MAC label to apply */
57                 int flags;      /* miscellaneous flags */
58 #define         F_DONTLABEL     0x01
59 #define         F_ALWAYSMATCH   0x02
60         } *entries,             /* entries[0..nentries] */
61           *match;               /* cached decision for MAC label to apply */
62         size_t nentries;        /* size of entries list */
63         STAILQ_ENTRY(label_spec) link;
64 };
65
66 struct label_specs {
67         STAILQ_HEAD(label_specs_head, label_spec) head;
68 };
69
70 void usage(int) __dead2;
71 struct label_specs *new_specs(void);
72 void add_specs(struct label_specs *, const char *, int);
73 void add_setfmac_specs(struct label_specs *, char *);
74 void add_spec_line(const char *, int, struct label_spec_entry *, char *);
75 int apply_specs(struct label_specs *, FTSENT *, int, int);
76 int specs_empty(struct label_specs *);
77
78 static int qflag;
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 ? "Rhq" : "ef:qs:vx")) !=
97             -1) {
98                 switch (ch) {
99                 case 'R':
100                         Rflag = 1;
101                         break;
102                 case 'e':
103                         eflag = 1;
104                         break;
105                 case 'f':
106                         add_specs(specs, optarg, 0);
107                         break;
108                 case 'h':
109                         hflag = FTS_PHYSICAL;
110                         break;
111                 case 'q':
112                         qflag = 1;
113                         break;
114                 case 's':
115                         add_specs(specs, optarg, 1);
116                         break;
117                 case 'v':
118                         vflag++;
119                         break;
120                 case 'x':
121                         xflag = FTS_XDEV;
122                         break;
123                 default:
124                         usage(is_setfmac);
125                 }
126         }
127         argc -= optind;
128         argv += optind;
129
130         if (is_setfmac) {
131                 if (argc <= 1)  
132                         usage(is_setfmac);
133                 add_setfmac_specs(specs, *argv);
134                 argc--;
135                 argv++;
136         } else {
137                 if (argc == 0 || specs_empty(specs))
138                         usage(is_setfmac);
139         }
140         fts = fts_open(argv, hflag | xflag, NULL);
141         if (fts == NULL)
142                 err(1, "cannot traverse filesystem%s", argc ? "s" : "");
143         while ((ftsent = fts_read(fts)) != NULL) {
144                 switch (ftsent->fts_info) {
145                 case FTS_DP:            /* skip post-order */
146                         break;
147                 case FTS_D:             /* do pre-order */
148                 case FTS_DC:            /* do cyclic? */
149                         /* don't ever recurse directories as setfmac(8) */
150                         if (is_setfmac && !Rflag)
151                                 fts_set(fts, ftsent, FTS_SKIP);
152                 case FTS_DEFAULT:       /* do default */
153                 case FTS_F:             /* do regular */
154                 case FTS_SL:            /* do symlink */
155                 case FTS_SLNONE:        /* do symlink */
156                 case FTS_W:             /* do whiteout */
157                         if (apply_specs(specs, ftsent, hflag, vflag)) {
158                                 if (eflag) {
159                                         errx(1, "labeling not supported in %s",
160                                             ftsent->fts_path);
161                                 }
162                                 if (!qflag)
163                                         warnx("labeling not supported in %s",
164                                             ftsent->fts_path);
165                                 fts_set(fts, ftsent, FTS_SKIP);
166                         }
167                         break;
168                 case FTS_DNR:           /* die on all errors */
169                 case FTS_ERR:
170                 case FTS_NS:
171                         err(1, "traversing %s", ftsent->fts_path);
172                 default:
173                         errx(1, "CANNOT HAPPEN (%d) traversing %s",
174                             ftsent->fts_info, ftsent->fts_path);
175                 }
176         }
177         fts_close(fts);
178         exit(0);
179 }
180
181 void
182 usage(int is_setfmac)
183 {
184
185         if (is_setfmac)
186                 fprintf(stderr, "usage: setfmac [-Rhq] label file ...\n");
187         else
188                 fprintf(stderr, "usage: setfsmac [-ehqvx] [-f specfile [...]] [-s specfile [...]] file ...\n");
189         exit(1);
190 }
191
192 static int
193 chomp_line(char **line, size_t *linesize)
194 {
195         char *s;
196         int freeme = 0;
197         
198         for (s = *line; (unsigned)(s - *line) < *linesize; s++) {
199                 if (!isspace(*s))
200                         break;
201         }
202         if (*s == '#') {
203                 **line = '\0';
204                 *linesize = 0;
205                 return (freeme);
206         }
207         memmove(*line, s, *linesize - (s - *line));
208         *linesize -= s - *line;
209         for (s = &(*line)[*linesize - 1]; s >= *line; s--) {
210                 if (!isspace(*s))
211                         break;
212         }
213         if (s != &(*line)[*linesize - 1]) {
214                 *linesize = s - *line + 1;
215         } else {
216                 s = malloc(*linesize + 1);
217                 if (s == NULL)
218                         err(1, "malloc");
219                 strncpy(s, *line, *linesize);
220                 *line = s;
221                 freeme = 1;
222         }
223         (*line)[*linesize] = '\0';
224         return (freeme);
225 }
226
227 void
228 add_specs(struct label_specs *specs, const char *file, int is_sebsd)
229 {
230         struct label_spec *spec;
231         FILE *fp;
232         char *line;
233         size_t nlines = 0, linesize;
234         int freeline;
235
236         spec = malloc(sizeof(*spec));
237         if (spec == NULL)
238                 err(1, "malloc");
239         fp = fopen(file, "r");
240         if (fp == NULL)
241                 err(1, "opening %s", file);
242         while ((line = fgetln(fp, &linesize)) != NULL) {
243                 freeline = chomp_line(&line, &linesize);
244                 if (linesize > 0) /* only allocate space for non-comments */
245                         nlines++;
246                 if (freeline)
247                         free(line);
248         }
249         if (ferror(fp))
250                 err(1, "fgetln on %s", file);
251         rewind(fp);
252         spec->entries = calloc(nlines, sizeof(*spec->entries));
253         if (spec->entries == NULL)
254                 err(1, "malloc");
255         spec->nentries = nlines;
256         while (nlines > 0) {
257                 line = fgetln(fp, &linesize);
258                 if (line == NULL) {
259                         if (feof(fp))
260                                 errx(1, "%s ended prematurely", file);
261                         else
262                                 err(1, "failure reading %s", file);
263                 }
264                 freeline = chomp_line(&line, &linesize);
265                 if (linesize == 0) {
266                         if (freeline)
267                                 free(line);
268                         continue;
269                 }
270                 add_spec_line(file, is_sebsd, &spec->entries[--nlines], line);
271                 if (freeline)
272                         free(line);
273         }
274         fclose(fp);
275         if (!qflag)
276                 warnx("%s: read %lu specifications", file,
277                     (long)spec->nentries);
278         STAILQ_INSERT_TAIL(&specs->head, spec, link);
279 }
280
281 void
282 add_setfmac_specs(struct label_specs *specs, char *label)
283 {
284         struct label_spec *spec;
285
286         spec = malloc(sizeof(*spec));
287         if (spec == NULL)
288                 err(1, "malloc");
289         spec->nentries = 1;
290         spec->entries = calloc(spec->nentries, sizeof(*spec->entries));
291         if (spec->entries == NULL)
292                 err(1, "malloc");
293         /* The _only_ thing specified here is the mactext! */
294         spec->entries->mactext = label;
295         spec->entries->flags |= F_ALWAYSMATCH;
296         STAILQ_INSERT_TAIL(&specs->head, spec, link);
297 }
298
299 void
300 add_spec_line(const char *file, int is_sebsd, struct label_spec_entry *entry,
301     char *line)
302 {
303         char *regexstr, *modestr, *macstr, *regerrorstr;
304         size_t size;
305         int error;
306
307         regexstr = strtok(line, " \t");
308         if (regexstr == NULL)
309                 errx(1, "%s: need regular expression", file);
310         modestr = strtok(NULL, " \t");
311         if (modestr == NULL)
312                 errx(1, "%s: need a label", file);
313         macstr = strtok(NULL, " \t");
314         if (macstr == NULL) {   /* the mode is just optional */
315                 macstr = modestr;
316                 modestr = NULL;
317         }
318         if (strtok(NULL, " \t") != NULL)
319                 errx(1, "%s: extraneous fields at end of line", file);
320         /* assume we need to anchor this regex */
321         if (asprintf(&regexstr, "^%s$", regexstr) == -1)
322                 err(1, "%s: processing regular expression", file);
323         entry->regexstr = regexstr;
324         error = regcomp(&entry->regex, regexstr, REG_EXTENDED | REG_NOSUB);
325         if (error) {
326                 size = regerror(error, &entry->regex, NULL, 0);
327                 regerrorstr = malloc(size);
328                 if (regerrorstr == NULL)
329                         err(1, "malloc");
330                 (void)regerror(error, &entry->regex, regerrorstr, size);
331                 errx(1, "%s: %s: %s", file, entry->regexstr, regerrorstr);
332         }
333         if (!is_sebsd) {
334                 entry->mactext = strdup(macstr);
335                 if (entry->mactext == NULL)
336                         err(1, "strdup");
337         } else {
338                 if (asprintf(&entry->mactext, "sebsd/%s", macstr) == -1)
339                         err(1, "asprintf");
340                 if (strcmp(macstr, "<<none>>") == 0)
341                         entry->flags |= F_DONTLABEL;
342         }
343         if (modestr != NULL) {
344                 if (strlen(modestr) != 2 || modestr[0] != '-')
345                         errx(1, "%s: invalid mode string: %s", file, modestr);
346                 switch (modestr[1]) {
347                 case 'b':
348                         entry->mode = S_IFBLK;
349                         entry->modestr = ",-b";
350                         break;
351                 case 'c':
352                         entry->mode = S_IFCHR;
353                         entry->modestr = ",-c";
354                         break;
355                 case 'd':
356                         entry->mode = S_IFDIR;
357                         entry->modestr = ",-d";
358                         break;
359                 case 'p':
360                         entry->mode = S_IFIFO;
361                         entry->modestr = ",-p";
362                         break;
363                 case 'l':
364                         entry->mode = S_IFLNK;
365                         entry->modestr = ",-l";
366                         break;
367                 case 's':
368                         entry->mode = S_IFSOCK;
369                         entry->modestr = ",-s";
370                         break;
371                 case '-':
372                         entry->mode = S_IFREG;
373                         entry->modestr = ",--";
374                         break;
375                 default:
376                         errx(1, "%s: invalid mode string: %s", file, modestr);
377                 }
378         } else {
379                 entry->modestr = "";
380         }
381 }
382
383 int
384 specs_empty(struct label_specs *specs)
385 {
386
387         return (STAILQ_EMPTY(&specs->head));
388 }
389
390 int
391 apply_specs(struct label_specs *specs, FTSENT *ftsent, int hflag, int vflag)
392 {
393         regmatch_t pmatch;
394         struct label_spec *ls;
395         struct label_spec_entry *ent;
396         char *regerrorstr, *macstr;
397         size_t size;
398         mac_t mac;
399         int error, matchedby;
400
401         /*
402          * Work through file context sources in order of specification
403          * on the command line, and through their entries in reverse
404          * order to find the "last" (hopefully "best") match.
405          */
406         matchedby = 0;
407         STAILQ_FOREACH(ls, &specs->head, link) {
408                 for (ls->match = NULL, ent = ls->entries;
409                     ent < &ls->entries[ls->nentries]; ent++) {
410                         if (ent->flags & F_ALWAYSMATCH)
411                                 goto matched;
412                         if (ent->mode != 0 &&
413                             (ftsent->fts_statp->st_mode & S_IFMT) != ent->mode)
414                                 continue;
415                         pmatch.rm_so = 0;
416                         pmatch.rm_eo = ftsent->fts_pathlen;
417                         error = regexec(&ent->regex, ftsent->fts_path, 1,
418                             &pmatch, REG_STARTEND);
419                         switch (error) {
420                         case REG_NOMATCH:
421                                 continue;
422                         case 0:
423                                 break;
424                         default:
425                                 size = regerror(error, &ent->regex, NULL, 0);
426                                 regerrorstr = malloc(size);
427                                 if (regerrorstr == NULL)
428                                         err(1, "malloc");
429                                 (void)regerror(error, &ent->regex, regerrorstr,
430                                     size);
431                                 errx(1, "%s: %s", ent->regexstr, regerrorstr);
432                         }
433                 matched:
434                         ls->match = ent;
435                         if (vflag) {
436                                 if (matchedby == 0) {
437                                         printf("%s matched by ",
438                                             ftsent->fts_path);
439                                         matchedby = 1;
440                                 }
441                                 printf("%s(%s%s,%s)", matchedby == 2 ? "," : "",
442                                     ent->regexstr, ent->modestr, ent->mactext);
443                                 if (matchedby == 1)
444                                         matchedby = 2;
445                         }
446                         break;
447                 }
448         }
449         if (vflag && matchedby)
450                 printf("\n");
451         size = 0;
452         STAILQ_FOREACH(ls, &specs->head, link) {
453                 /* cached match decision */
454                 if (ls->match && (ls->match->flags & F_DONTLABEL) == 0)
455                          /* add length of "x\0"/"y," */
456                         size += strlen(ls->match->mactext) + 1;
457         }
458         if (size == 0)
459                 return (0);
460         macstr = malloc(size);
461         if (macstr == NULL)
462                 err(1, "malloc");
463         *macstr = '\0';
464         STAILQ_FOREACH(ls, &specs->head, link) {
465                 /* cached match decision */
466                 if (ls->match && (ls->match->flags & F_DONTLABEL) == 0) {
467                         if (*macstr != '\0')
468                                 strcat(macstr, ",");
469                         strcat(macstr, ls->match->mactext);
470                 }
471         }
472         if (mac_from_text(&mac, macstr))
473                 err(1, "mac_from_text(%s)", macstr);
474         if ((hflag == FTS_PHYSICAL ? mac_set_link(ftsent->fts_accpath, mac) :
475             mac_set_file(ftsent->fts_accpath, mac)) != 0) {
476                 if (errno == EOPNOTSUPP) {
477                         mac_free(mac);
478                         free(macstr);
479                         return (1);
480                 }
481                 err(1, "mac_set_link(%s, %s)", ftsent->fts_path, macstr);
482         }
483         mac_free(mac);
484         free(macstr);
485         return (0);
486 }
487
488 struct label_specs *
489 new_specs(void)
490 {
491         struct label_specs *specs;
492
493         specs = malloc(sizeof(*specs));
494         if (specs == NULL)
495                 err(1, "malloc");
496         STAILQ_INIT(&specs->head);
497         return (specs);
498 }