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