devfsctl - Bring in sync with kernel rule changes
[dragonfly.git] / usr.sbin / devfsctl / devfsctl.c
1 /*
2  * Copyright (c) 2009 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Alex Hornung <ahornung@gmail.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34
35 #include <sys/types.h>
36 #include <sys/cdefs.h>
37 #include <sys/syslimits.h>
38 #include <sys/ioctl.h>
39 #include <sys/device.h>
40 #include <sys/queue.h>
41 #include <vfs/devfs/devfs_rules.h>
42
43 #include <ctype.h>
44 #include <err.h>
45 #include <fcntl.h>
46 #include <grp.h>
47 #include <pwd.h>
48 #include <stdarg.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53
54
55 #include "devfsctl.h"
56
57 struct verb {
58         const char *verb;
59         rule_parser_t *parser;
60         int     min_args;
61 };
62
63 struct devtype {
64         const char *name;
65         int     value;
66 };
67
68
69
70 static int parser_include(char **);
71 static int parser_jail(char **);
72 static int parser_hide(char **);
73 static int parser_show(char **);
74 static int parser_link(char **);
75 static int parser_group(char **);
76 static int parser_perm(char **);
77 static int dump_config_entry(struct rule *, struct groupdevid *);
78 static int rule_id_iterate(struct groupdevid *, struct rule *,
79                 rule_iterate_callback_t *);
80 static int rule_ioctl(unsigned long, struct devfs_rule_ioctl *);
81 static void rule_fill(struct devfs_rule_ioctl *, struct rule *,
82                 struct groupdevid *);
83 static int rule_send(struct rule *, struct groupdevid *);
84 static int rule_check_num_args(char **, int);
85 static int process_line(FILE*);
86 static void usage(void);
87
88 static int dev_fd;
89
90 const char *config_name = NULL, *mountp = NULL;
91 static int dflag = 0;
92 static int aflag = 0, cflag = 0, rflag = 0;
93 static int line_stack[RULE_MAX_STACK];
94 static char *file_stack[RULE_MAX_STACK];
95 static int line_stack_depth = 0;
96 static int jail = 0;
97
98 static TAILQ_HEAD(, rule) rule_list =
99                 TAILQ_HEAD_INITIALIZER(rule_list);
100
101 static TAILQ_HEAD(, groupdevid) group_list =
102                 TAILQ_HEAD_INITIALIZER(group_list);
103
104
105 static const struct verb parsers[] = {
106         { "include", parser_include, 1 },
107         { "jail", parser_jail, 1 },
108         { "group", parser_group, 2 },
109         { "perm", parser_perm, 2 },
110         { "link", parser_link, 2 },
111         { "hide", parser_hide, 2 },
112         { "show", parser_show, 2 },
113         { NULL, NULL, 0 }
114 };
115
116 static const struct devtype devtypes[] = {
117         { "D_TAPE", D_TAPE },
118         { "D_DISK", D_DISK },
119         { "D_TTY", D_TTY },
120         { "D_MEM", D_MEM },
121         { NULL, 0 }
122 };
123
124 int
125 syntax_error(const char *fmt, ...)
126 {
127         char buf[1024];
128         va_list ap;
129
130         va_start(ap, fmt);
131         vsnprintf(buf, sizeof(buf), fmt, ap);
132         va_end(ap);
133         errx(1, "%s: syntax error on line %d: %s\n",file_stack[line_stack_depth],
134                         line_stack[line_stack_depth], buf);
135 }
136
137 static int
138 parser_include(char **tokens)
139 {
140         read_config(tokens[1]);
141
142         return 0;
143 }
144
145 static int
146 parser_jail(char **tokens)
147 {
148         if (tokens[1][0] == 'y') {
149                 jail = 1;
150         } else if (tokens[1][0] == 'n') {
151                 jail = 0;
152         } else {
153                 syntax_error("incorrect argument to 'jail'. Must be either y[es] or n[o]");
154         }
155
156         return 0;
157 }
158
159 static int
160 parser_hide(char **tokens)
161 {
162         struct groupdevid *id;
163         struct rule *rule;
164
165         id = get_id(tokens[1]);
166         rule = new_rule(rHIDE, id);
167         add_rule(rule);
168
169         return 0;
170 }
171
172 static int
173 parser_show(char **tokens)
174 {
175         struct groupdevid *id;
176         struct rule *rule;
177
178         id = get_id(tokens[1]);
179         rule = new_rule(rSHOW, id);
180         add_rule(rule);
181
182         return 0;
183 }
184
185 static int
186 parser_link(char **tokens)
187 {
188         struct groupdevid *id;
189         struct rule *rule;
190
191         id = get_id(tokens[1]);
192         rule = new_rule(rLINK, id);
193         rule->dest = strdup(tokens[2]);
194         add_rule(rule);
195
196         return 0;
197 }
198
199 static int
200 parser_group(char **tokens)
201 {
202         struct groupdevid *gid, *id;
203         int i;
204         size_t k;
205
206         gid = get_group(tokens[1], 1);
207         for (k = 0; gid->list[k] != NULL; k++)
208                 /* Do nothing */;
209         for (i = 2; tokens[i] != NULL; i++) {
210                 id = get_id(tokens[i]);
211                 if (id == gid) {
212                         syntax_error("recursive group definition for group %s", gid->name);
213                 } else {
214                         if (k >= gid->listsize-1 ) {
215                                 gid->list = realloc(gid->list,
216                                                 2*gid->listsize*sizeof(struct groupdevid *));
217                                 gid->listsize *= 2;
218                         }
219
220                         gid->list[k++] = id;
221                 }
222         }
223         gid->list[k] = NULL;
224
225         return 0;
226 }
227
228 static int
229 parser_perm(char **tokens)
230 {
231         struct passwd *pwd;
232         struct group *grp;
233         struct groupdevid *id;
234         struct rule *rule;
235         char *uname;
236         char *grname;
237
238         id = get_id(tokens[1]);
239         rule = new_rule(rPERM, id);
240
241         rule->mode = strtol(tokens[3], NULL, 8);
242         uname = tokens[2];
243         grname = strchr(tokens[2], ':');
244         if (grname == NULL)
245                 syntax_error("invalid format for user/group (%s)", tokens[2]);
246
247         *grname = '\0';
248         ++grname;
249         if ((pwd = getpwnam(uname)))
250                 rule->uid = pwd->pw_uid;
251         else
252                 syntax_error("invalid user name %s", uname);
253
254         if ((grp = getgrnam(grname)))
255                 rule->gid = grp->gr_gid;
256         else
257                 syntax_error("invalid group name %s", grname);
258
259         add_rule(rule);
260         return 0;
261 }
262
263 struct groupdevid *
264 new_id(const char *name, int type_in)
265 {
266         struct groupdevid *id;
267         int type = (type_in != 0)?(type_in):(isNAME), i;
268
269         id = calloc(1, sizeof(*id));
270         if (id == NULL)
271                 err(1, NULL);
272
273         if (type_in == 0) {
274                 for (i = 0; devtypes[i].name != NULL; i++) {
275                         if (!strcmp(devtypes[i].name, name)) {
276                                 type = isTYPE;
277                                 id->devtype = devtypes[i].value;
278                                 break;
279                         }
280                 }
281         }
282         id->type = type;
283
284         if ((type == isNAME) || (type == isGROUP)) {
285                 id->name = strdup(name);
286         }
287
288         if (type == isGROUP) {
289                 id->list = calloc(4, sizeof(struct groupdevid *));
290                 memset(id->list, 0, 4 * sizeof(struct groupdevid *));
291                 id->listsize = 4;
292         }
293
294         return (id);
295 }
296
297 struct groupdevid *
298 get_id(const char *name)
299 {
300         struct groupdevid *id;
301
302         if ((name[0] == '@') && (name[1] != '\0')) {
303                 id = get_group(name+1, 0);
304                 if (id == NULL)
305                         syntax_error("unknown group name '%s', you "
306                                         "have to use the 'group' verb first.", name+1);
307         }
308         else
309                 id = new_id(name, 0);
310
311         return id;
312 }
313
314 struct groupdevid *
315 get_group(const char *name, int expect)
316 {
317         struct groupdevid *g;
318
319         TAILQ_FOREACH(g, &group_list, link) {
320                 if (strcmp(g->name, name) == 0)
321                         return (g);
322         }
323
324         /* Caller doesn't expect to get a group no matter what */
325         if (!expect)
326                 return NULL;
327
328         g = new_id(name, isGROUP);
329         TAILQ_INSERT_TAIL(&group_list, g, link);
330         return (g);
331 }
332
333 struct rule *
334 new_rule(int type, struct groupdevid *id)
335 {
336         struct rule *rule;
337
338         rule = calloc(1, sizeof(*rule));
339         if (rule == NULL)
340                 err(1, NULL);
341
342         rule->type = type;
343         rule->id = id;
344         rule->jail = jail;
345         return (rule);
346 }
347
348 void
349 add_rule(struct rule *rule)
350 {
351         TAILQ_INSERT_TAIL(&rule_list, rule, link);
352 }
353
354 static int
355 dump_config_entry(struct rule *rule, struct groupdevid *id)
356 {
357         struct passwd *pwd;
358         struct group *grp;
359         int i;
360
361         switch (rule->type) {
362         case rPERM: printf("perm "); break;
363         case rLINK: printf("link "); break;
364         case rHIDE: printf("hide "); break;
365         case rSHOW: printf("show "); break;
366         default: errx(1, "invalid rule type");
367         }
368
369         switch (id->type) {
370         case isGROUP: printf("@"); /* FALLTHROUGH */
371         case isNAME: printf("%s", id->name); break;
372         case isTYPE:
373                 for (i = 0; devtypes[i].name != NULL; i++) {
374                         if (devtypes[i].value == id->devtype) {
375                                 printf("%s", devtypes[i].name);
376                                 break;
377                         }
378                 }
379                 break;
380         default: errx(1, "invalid id type %d", id->type);
381         }
382
383         switch (rule->type) {
384         case rPERM:
385                 pwd = getpwuid(rule->uid);
386                 grp = getgrgid(rule->gid);
387                 if (pwd && grp) {
388                         printf(" %s:%s 0%.03o",
389                                    pwd->pw_name,
390                                    grp->gr_name,
391                                    rule->mode);
392                 } else {
393                         printf(" %d:%d 0%.03o",
394                                    rule->uid,
395                                    rule->gid,
396                                    rule->mode);
397                 }
398                 break;
399         case rLINK:
400                 printf(" %s", rule->dest);
401                 break;
402         default: /* NOTHING */;
403         }
404
405         if (rule->jail)
406                 printf("\t(only affects jails)");
407
408         printf("\n");
409
410         return 0;
411 }
412
413 static int
414 rule_id_iterate(struct groupdevid *id, struct rule *rule,
415                 rule_iterate_callback_t *callback)
416 {
417         int error = 0;
418         int i;
419
420         if (id->type == isGROUP) {
421                 for (i = 0; id->list[i] != NULL; i++) {
422                         if ((error = rule_id_iterate(id->list[i], rule, callback)))
423                                 return error;
424                 }
425         } else {
426                 error = callback(rule, id);
427         }
428
429         return error;
430 }
431
432 void
433 dump_config(void)
434 {
435         struct rule *rule;
436
437         TAILQ_FOREACH(rule, &rule_list, link) {
438                 rule_id_iterate(rule->id, rule, dump_config_entry);
439         }
440 }
441
442 static int
443 rule_ioctl(unsigned long cmd, struct devfs_rule_ioctl *rule)
444 {
445         if (ioctl(dev_fd, cmd, rule) == -1)
446                 err(1, "ioctl");
447
448         return 0;
449 }
450
451 static void
452 rule_fill(struct devfs_rule_ioctl *dr, struct rule *r, struct groupdevid *id)
453 {
454         dr->rule_type = 0;
455
456         switch (id->type) {
457         default:
458                 errx(1, "invalid id type");
459         case isGROUP:
460                 errx(1, "internal error: can not fill group rule");
461                 /* NOTREACHED */
462         case isNAME:
463                 dr->rule_type |= DEVFS_RULE_NAME;
464                 strncpy(dr->name, id->name, PATH_MAX-1);
465                 break;
466         case isTYPE:
467                 dr->rule_type |= DEVFS_RULE_TYPE;
468                 dr->dev_type = id->devtype;
469                 break;
470         }
471
472         switch (r->type) {
473         case rPERM:
474                 dr->rule_cmd |= DEVFS_RULE_PERM;
475                 dr->uid = r->uid;
476                 dr->gid = r->gid;
477                 dr->mode = r->mode;
478                 break;
479         case rLINK:
480                 dr->rule_cmd |= DEVFS_RULE_LINK;
481                 strncpy(dr->linkname, r->dest, PATH_MAX-1);
482                 break;
483         case rHIDE:
484                 dr->rule_cmd |= DEVFS_RULE_HIDE;
485                 break;
486         case rSHOW:
487                 dr->rule_cmd |= DEVFS_RULE_SHOW;
488                 break;
489         }
490
491         if (r->jail)
492                 dr->rule_type |= DEVFS_RULE_JAIL;
493 }
494
495 static int
496 rule_send(struct rule *rule, struct groupdevid *id)
497 {
498         struct devfs_rule_ioctl dr;
499         int r = 0;
500
501         strncpy(dr.mntpoint, mountp, PATH_MAX-1);
502
503         rule_fill(&dr, rule, id);
504         r = rule_ioctl(DEVFS_RULE_ADD, &dr);
505
506         return r;
507 }
508
509 int
510 rule_apply(void)
511 {
512         struct devfs_rule_ioctl dr;
513         struct rule *rule;
514         int r = 0;
515
516         strncpy(dr.mntpoint, mountp, PATH_MAX-1);
517
518         TAILQ_FOREACH(rule, &rule_list, link) {
519                 r = rule_id_iterate(rule->id, rule, rule_send);
520                 if (r != 0)
521                         return (-1);
522         }
523
524         return (rule_ioctl(DEVFS_RULE_APPLY, &dr));
525 }
526
527 static int
528 rule_check_num_args(char **tokens, int num)
529 {
530         int i = 0;
531         for (i = 0; tokens[i] != NULL; i++);
532
533         if (i < num) {
534                 syntax_error("at least %d tokens were expected but only %d were found", num, i);
535                 return 1;
536         }
537         return 0;
538 }
539
540 int
541 read_config(const char *name)
542 {
543         FILE *fd;
544
545         if ((fd = fopen(name, "r")) == NULL) {
546                 printf("Error opening config file %s\n", name);
547                 perror("fopen");
548                 return 1;
549         }
550
551         if (++line_stack_depth >= RULE_MAX_STACK) {
552                 --line_stack_depth;
553                 syntax_error("Maximum include depth (%d) exceeded, "
554                                 "check for recursion.", RULE_MAX_STACK);
555         }
556
557         line_stack[line_stack_depth] = 1;
558         file_stack[line_stack_depth] = strdup(name);
559
560         while (process_line(fd) == 0)
561                 line_stack[line_stack_depth]++;
562
563         fclose(fd);
564
565         free(file_stack[line_stack_depth]);
566         --line_stack_depth;
567
568         return 0;
569 }
570
571 static int
572 process_line(FILE* fd)
573 {
574         char buffer[4096];
575         char *tokens[256];
576         int c, n, i = 0;
577         int quote = 0;
578         int ret = 0;
579         int error = 0;
580         int parsed = 0;
581
582
583         while (((c = fgetc(fd)) != EOF) && (c != '\n')) {
584                 buffer[i++] = (char)c;
585                 if (i == (sizeof(buffer) -1))
586                         break;
587         }
588         buffer[i] = '\0';
589
590         if (feof(fd) || ferror(fd))
591                 ret = 1;
592         c = 0;
593         while (((buffer[c] == ' ') || (buffer[c] == '\t')) && (c < i)) c++;
594         /*
595          * If this line effectively (after indentation) begins with the comment
596          * character #, we ignore the rest of the line.
597          */
598         if (buffer[c] == '#')
599                 return 0;
600
601         tokens[0] = &buffer[c];
602         for (n = 1; c < i; c++) {
603                 if (buffer[c] == '"') {
604                         quote = !quote;
605                         if (quote) {
606                                 if ((c >= 1) && (&buffer[c] != tokens[n-1])) {
607                                         syntax_error("stray opening quote not at beginning of token");
608                                         /* NOTREACHED */
609                                 }
610                                 tokens[n-1] = &buffer[c+1];
611                         } else {
612                                 if ((c < i-1) && (!iswhitespace(buffer[c+1]))) {
613                                         syntax_error("stray closing quote not at end of token");
614                                         /* NOTREACHED */
615                                 }
616                                 buffer[c] = '\0';
617                         }
618                 }
619
620                 if (quote) {
621                         continue;
622                 }
623
624                 if ((buffer[c] == ' ') || (buffer[c] == '\t')) {
625                         buffer[c++] = '\0';
626                         while ((iswhitespace(buffer[c])) && (c < i)) c++;
627                         tokens[n++] = &buffer[c--];
628                 }
629         }
630         tokens[n] = NULL;
631
632         /*
633          * If there are not enough arguments for any function or it is
634          * a line full of whitespaces, we just return here. Or if a
635          * quote wasn't closed.
636          */
637         if ((quote) || (n < 2) || (tokens[0][0] == '\0'))
638                 return ret;
639
640         /* Convert the command/verb to lowercase */
641         for (i = 0; tokens[0][i] != '\0'; i++)
642                 tokens[0][i] = tolower(tokens[0][i]);
643
644         for (i = 0; parsers[i].verb; i++) {
645                 if ((error = rule_check_num_args(tokens, parsers[i].min_args)))
646                         continue;
647
648                 if (!strcmp(tokens[0], parsers[i].verb)) {
649                         parsers[i].parser(tokens);
650                         parsed = 1;
651                         break;
652                 }
653         }
654         if (parsed == 0) {
655                 syntax_error("unknown verb/command %s", tokens[0]);
656         }
657         return ret;
658 }
659
660 static void
661 usage(void)
662 {
663         fprintf(stderr,
664                 "Usage: devfsctl <commands> [options]\n"
665                 "Valid commands are:\n"
666                 " -a\n"
667                 "\t Loads all read rules into the kernel and applies them\n"
668                 " -c\n"
669                 "\t Clears all rules stored in the kernel but does not reset the nodes\n"
670                 " -d\n"
671                 "\t Dumps the rules that have been loaded to the screen to verify syntax\n"
672                 " -r\n"
673                 "\t Resets all devfs_nodes but does not clear the rules stored\n"
674                 "\n"
675                 "Valid options and its arguments are:\n"
676                 " -f <config_file>\n"
677                 "\t Specifies the configuration file to be used\n"
678                 " -m <mount_point>\n"
679                 "\t Specifies a mount point to which the command will apply. Defaults to *\n"
680                 );
681
682         exit(1);
683 }
684
685 int main(int argc, char *argv[])
686 {
687         struct devfs_rule_ioctl dummy_rule;
688         int ch;
689
690         while ((ch = getopt(argc, argv, "acdf:hm:r")) != -1) {
691                 switch (ch) {
692                 case 'f':
693                         config_name = optarg;
694                         break;
695                 case 'm':
696                         mountp = optarg;
697                         break;
698                 case 'a':
699                         aflag = 1;
700                         break;
701                 case 'c':
702                         cflag = 1;
703                         break;
704                 case 'r':
705                         rflag = 1;
706                         break;
707                 case 'd':
708                         dflag = 1;
709                         break;
710                 case 'h':
711                 case '?':
712                 default:
713                         usage();
714                         /* NOT REACHED */
715                 }
716         }
717
718         argc -= optind;
719         argv += optind;
720
721         /*
722          * Check arguments:
723          * - need to use at least one mode
724          * - can not use -d with any other mode
725          */
726         if (!(aflag || rflag || cflag || dflag) ||
727             (dflag && (aflag || rflag || cflag))) {
728                 usage();
729                 /* NOT REACHED */
730         }
731
732         if (mountp == NULL)
733                 mountp = "*";
734
735         strncpy(dummy_rule.mntpoint, mountp, PATH_MAX-1);
736
737         if (config_name != NULL)
738                 read_config(config_name);
739
740         if (dflag) {
741                 dump_config();
742                 exit(0);
743         }
744
745         dev_fd = open("/dev/devfs", O_RDWR);
746         if (dev_fd == -1)
747                 err(1, "open(/dev/devfs)");
748
749         if (cflag)
750                 rule_ioctl(DEVFS_RULE_CLEAR, &dummy_rule);
751
752         if (rflag)
753                 rule_ioctl(DEVFS_RULE_RESET, &dummy_rule);
754
755         if (aflag)
756                 rule_apply();
757
758         close(dev_fd);
759
760         return 0;
761 }