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