Change to header file name for parser. See log for `parse.h' for
[become] / src / bcquery.c
1 /* -*-c-*-
2 *
3 * $Id: bcquery.c,v 1.3 1999/05/04 16:17:11 mdw Exp $
4 *
5 * Query and dump Become's configuration file
6 *
7 * (c) 1998 EBI
8 */
9
10 /*----- Licensing notice --------------------------------------------------*
11 *
12 * This file is part of `become'
13 *
14 * `Become' is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * `Become' is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with `become'; if not, write to the Free Software Foundation,
26 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 */
28
29 /*----- Revision history --------------------------------------------------*
30 *
31 * $Log: bcquery.c,v $
32 * Revision 1.3 1999/05/04 16:17:11 mdw
33 * Change to header file name for parser. See log for `parse.h' for
34 * details.
35 *
36 * Revision 1.2 1998/06/26 10:32:31 mdw
37 * Cosmetic change: use sizeof(destination) in memcpy.
38 *
39 * Revision 1.1 1998/04/23 13:20:20 mdw
40 * Added new program to verify and query Become configuration files.
41 *
42 */
43
44 /*----- Header files ------------------------------------------------------*/
45
46 /* --- ANSI headers --- */
47
48 #include <ctype.h>
49 #include <errno.h>
50 #include <limits.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <time.h>
55
56 /* --- Unix headers --- */
57
58 #include <sys/types.h>
59 #include <sys/stat.h>
60 #include <sys/socket.h>
61 #include <sys/utsname.h>
62
63 #include <netinet/in.h>
64
65 #include <arpa/inet.h>
66
67 #include <netdb.h>
68 #include <grp.h>
69 #include <pwd.h>
70 #include <syslog.h>
71 #include <unistd.h>
72
73 /* --- Local headers --- */
74
75 #include "become.h"
76 #include "class.h"
77 #include "config.h"
78 #include "daemon.h"
79 #include "lexer.h"
80 #include "mdwopt.h"
81 #include "name.h"
82 #include "netg.h"
83 #include "parse.h"
84 #include "rule.h"
85 #include "sym.h"
86 #include "utils.h"
87 #include "userdb.h"
88
89 /*----- Type definitions --------------------------------------------------*/
90
91 enum {
92 cat_where = 1u,
93 cat_from = 2u,
94 cat_to = 4u,
95 cat_what = 8u,
96 cat_and = 16u,
97 cat_or = 17u,
98 cat_not = 18u
99 };
100
101 typedef struct qnode {
102 unsigned q_cat;
103 union {
104 uid_t uid;
105 struct in_addr in;
106 const char *cmd;
107 struct {
108 struct qnode *l, *r;
109 } q;
110 } q;
111 #define q_uid q.uid
112 #define q_in q.in
113 #define q_cmd q.cmd
114 #define q_left q.q.l
115 #define q_right q.q.r
116 #define q_arg q_left
117 } qnode;
118
119 /*----- Static variables --------------------------------------------------*/
120
121 enum {
122 f_dump = 1u,
123 f_userdb = 2u,
124 f_header = 4u,
125 f_match = 8u,
126 f_single = 16u,
127 f_simple = 32u,
128 f_force = 64u,
129 f_check = 128u,
130 f_nohead = 256u
131 };
132
133 static int ac;
134 static char **av;
135 static int opt;
136 static unsigned flags;
137 static const char *cf = file_RULES;
138 static unsigned outmask = cat_where | cat_from | cat_to | cat_what;
139
140 /*----- Low-level options handling ----------------------------------------*/
141
142 /* --- @optname@ --- *
143 *
144 * Arguments: ---
145 *
146 * Returns: Pointer to a string describing the current option.
147 *
148 * Use: Creates a textual description of an option for use in
149 * error messages.
150 */
151
152 static const char *optname(void)
153 {
154 static char buf[2];
155 switch (opt) {
156 case 'H': return ("-host");
157 case 'F': return ("-from");
158 case 'T': return ("-to");
159 case 'C': return ("-command");
160 case 0: return (optarg);
161 case '(': case ')': case '&': case '|': case '!':
162 buf[0] = opt;
163 buf[1] = 0;
164 return (buf);
165 case EOF: return ("<eof>");
166 default: return ("<unknown>");
167 }
168 }
169
170 /* --- @nextopt@ --- *
171 *
172 * Arguments: ---
173 *
174 * Returns: Next option id, or @EOF@.
175 *
176 * Use: Reads the next option. Does a lot of the messy work of
177 * options parsing.
178 */
179
180 static int nextopt(void)
181 {
182 const static struct option opts[] = {
183 { "help", 0, 0, 'h' },
184
185 { "file", gFlag_argReq, 0, 'f' },
186 { "dump", 0, 0, 'd' },
187 { "check", 0, 0, 'k' },
188
189 { "output", gFlag_argReq, 0, 'o' },
190 { "columns", 0, 0, '|' },
191 { "rows", 0, 0, '-' },
192 { "nohead", 0, 0, 'n' },
193
194 { "host", gFlag_argReq, 0, 'H' },
195 { "from", gFlag_argReq, 0, 'F' },
196 { "to", gFlag_argReq, 0, 'T' },
197 { "command", gFlag_argReq, 0, 'C' },
198
199 { "and", 0, 0, '&' },
200 { "or", 0, 0, '|' },
201 { "not", 0, 0, '!' },
202
203 { 0, 0, 0, 0 }
204 };
205
206 again:
207 opt = mdwopt(ac, av, "-", opts, 0, 0, gFlag_noShorts);
208
209 switch (opt) {
210 case 'h':
211 printf(""
212 "Usage: %s [-help]\n"
213 " %s [-output COLS] [-dump] [-file FILE] [EXPR | -check]\n"
214 "\n"
215 "Reads the `become' configuration file FILE (or " file_RULES " by\n"
216 "default) and writes the rules which match the EXPR.\n"
217 "\n"
218 "EXPR may make use of the following operators: `-host HOST', `-from USER',\n"
219 "`-to USER', and `-command CMD'. You may join them together with `-and',\n"
220 "`-or' and `-not' operators (which may be spelled `&', `|' and `!' if you\n"
221 "prefer), and group subexpressions with parentheses `(' and `)'.\n",
222 quis(), quis());
223 exit(0);
224 case 'd':
225 flags |= f_dump;
226 goto again;
227 case 'f':
228 cf = optarg;
229 goto again;
230 case '|':
231 flags |= f_simple;
232 /* Drop through */
233 case '-':
234 flags |= f_force;
235 goto again;
236 case 'k':
237 flags |= f_check;
238 goto again;
239 case 'n':
240 flags |= f_nohead;
241 goto again;
242 case 'o': {
243 char *p = optarg;
244 enum { m_replace, m_add, m_remove } mode = m_replace;
245 unsigned bit;
246
247 while (*p) {
248 switch (*p) {
249 case '+':
250 mode = m_add;
251 break;
252 case '-':
253 mode = m_remove;
254 break;
255 case 'h':
256 bit = cat_where;
257 goto setbits;
258 case 'f':
259 bit = cat_from;
260 goto setbits;
261 case 't':
262 bit = cat_to;
263 goto setbits;
264 case 'c':
265 bit = cat_what;
266 goto setbits;
267 default:
268 die("unknown column specifier `%c'", *p);
269 break;
270 setbits:
271 if (mode == m_replace) {
272 outmask = 0;
273 mode = m_add;
274 }
275 if (mode == m_add)
276 outmask |= bit;
277 else if (mode == m_remove)
278 outmask &= ~bit;
279 else
280 die("bad mode while setting output mask: %u", mode);
281 break;
282 }
283 p++;
284 }
285 goto again;
286 }
287 case '?':
288 die("type `%s --help' for usage information", quis());
289 case 0:
290 if (optarg[0] && optarg[1] == 0) switch (optarg[0]) {
291 case '(': case ')':
292 case '&': case '|': case '!':
293 opt = optarg[0];
294 break;
295 }
296 if (!opt)
297 die("unexpected text `%s' found", optarg);
298 break;
299 }
300
301 return (opt);
302 }
303
304 /*----- Recursive descent query parser ------------------------------------*/
305
306 /* --- @qparse@ --- *
307 *
308 * Arguments: ---
309 *
310 * Returns: A pointer to the finished tree.
311 *
312 * Use: Scans the command line arguments and makes them into a tree.
313 */
314
315 static qnode *qparse_expr(void);
316
317 static qnode *qparse_atom(void)
318 {
319 switch (opt) {
320 case '(': {
321 qnode *q;
322 nextopt();
323 q = qparse_expr();
324 if (opt != ')')
325 die("syntax error: expected `)', found `%s'", optname());
326 nextopt();
327 return (q);
328 }
329 case 'H': {
330 struct hostent *h;
331 qnode *q = xmalloc(sizeof(*q));
332 h = gethostbyname(optarg);
333 if (!h)
334 die("unknown host `%s'", optarg);
335 q->q_cat = cat_where;
336 memcpy(&q->q_in, h->h_addr, sizeof(q->q_in));
337 nextopt();
338 return (q);
339 }
340 case 'F': case 'T': {
341 qnode *q = xmalloc(sizeof(*q));
342 q->q_cat = (opt == 'F' ? cat_from : cat_to);
343 if (isdigit((unsigned char)optarg[0]))
344 q->q_uid = atoi(optarg);
345 else {
346 struct passwd *pw;
347 if (!(flags & f_userdb)) {
348 userdb_init();
349 userdb_local();
350 userdb_yp();
351 flags |= f_userdb;
352 }
353 pw = userdb_userByName(optarg);
354 if (!pw)
355 die("unknown user `%s'", optarg);
356 q->q_uid = pw->pw_uid;
357 }
358 nextopt();
359 return (q);
360 }
361 case 'C': {
362 qnode *q = xmalloc(sizeof(*q));
363 q->q_cat = cat_what;
364 q->q_cmd = optarg;
365 nextopt();
366 return (q);
367 }
368 default:
369 die("unexpected token: `%s'", optname());
370 }
371 return (0);
372 }
373
374 static qnode *qparse_factor(void)
375 {
376 if (opt == '!') {
377 qnode *q = xmalloc(sizeof(*q));
378 nextopt();
379 q->q_cat = cat_not;
380 q->q_arg = qparse_atom();
381 return (q);
382 } else
383 return (qparse_atom());
384 }
385
386 static qnode *qparse_term(void)
387 {
388 qnode *top, *q, **qq;
389 qq = &top;
390
391 again:
392 q = qparse_factor();
393 switch (opt) {
394 case '&':
395 nextopt();
396 case 'H': case 'F': case 'T': case 'C': case '!': case '(':
397 *qq = xmalloc(sizeof(*q));
398 (*qq)->q_cat = cat_and;
399 (*qq)->q_left = q;
400 qq = &(*qq)->q_right;
401 goto again;
402 default:
403 *qq = q;
404 break;
405 }
406 return (top);
407 }
408
409 static qnode *qparse_expr(void)
410 {
411 qnode *top, *q, **qq;
412 qq = &top;
413
414 again:
415 q = qparse_term();
416 switch (opt) {
417 case '|':
418 nextopt();
419 *qq = xmalloc(sizeof(*q));
420 (*qq)->q_cat = cat_or;
421 (*qq)->q_left = q;
422 qq = &(*qq)->q_right;
423 goto again;
424 default:
425 *qq = q;
426 break;
427 }
428 return (top);
429 }
430
431 static qnode *qparse(void)
432 {
433 qnode *q;
434 nextopt();
435 if (opt == EOF)
436 return (0);
437 q = qparse_expr();
438 if (opt != EOF)
439 die("syntax error: `%s' unexpected", optname());
440 return (q);
441 }
442
443 /* --- @dumptree@ --- *
444 *
445 * Arguments: @qnode *q@ = pointer to tree to dump
446 * @int indent@ = indentation for this subtree
447 *
448 * Returns: ---
449 *
450 * Use: Dumps a tree to stdout for debugging purposes.
451 */
452
453 static void dumptree(qnode *q, int indent)
454 {
455 if (!q)
456 printf("<empty> -- magic query which matches everything\n");
457
458 again:
459 printf("%*s", indent * 2, "");
460 indent++;
461 switch (q->q_cat) {
462 case cat_where:
463 printf("host = %s\n", inet_ntoa(q->q_in));
464 break;
465 case cat_from:
466 printf("from = %u\n", (unsigned)q->q_uid);
467 break;
468 case cat_to:
469 printf("to = %u\n", (unsigned)q->q_uid);
470 break;
471 case cat_what:
472 printf("command = `%s'\n", q->q_cmd);
473 break;
474 case cat_not:
475 printf("not\n");
476 q = q->q_arg;
477 goto again;
478 case cat_and:
479 case cat_or: {
480 unsigned cat = q->q_cat;
481 printf(cat == cat_and ? "and\n" : "or\n");
482 while (q->q_cat == cat) {
483 dumptree(q->q_left, indent);
484 q = q->q_right;
485 }
486 goto again;
487 }
488 default:
489 printf("unknown type %u\n", q->q_cat);
490 }
491 }
492
493 /*----- Recursive query matching ------------------------------------------*/
494
495 /* --- @checkrule@ --- *
496 *
497 * Arguments: @rule *r@ = pointer to a rule
498 * @qnode *q@ = pointer to a query tree
499 *
500 * Returns: Nonzero if the query matches the rule.
501 *
502 * Use: Matches rules and queries.
503 */
504
505 static int checkrule(rule *r, qnode *q)
506 {
507 again:
508 switch (q->q_cat) {
509
510 /* --- Handle the compound query types --- */
511
512 case cat_not:
513 return (!checkrule(r, q->q_arg));
514
515 case cat_and:
516 if (!checkrule(r, q->q_left))
517 return (0);
518 q = q->q_right;
519 goto again;
520
521 case cat_or:
522 if (checkrule(r, q->q_left))
523 return (1);
524 q = q->q_right;
525 goto again;
526
527 /* --- And now the simple query types --- */
528
529 case cat_where:
530 return (class_matchHost(r->host, q->q_in));
531 case cat_from:
532 return (class_matchUser(r->from, q->q_uid));
533 case cat_to:
534 return (class_matchUser(r->to, q->q_uid));
535 case cat_what:
536 return (class_matchCommand(r->cmd, q->q_cmd));
537 }
538
539 /* --- Anything else is bogus (and a bug) --- */
540
541 die("unexpected cat code %u in checkrule", q->q_cat);
542 return (-1);
543 }
544
545 /*----- Rule output -------------------------------------------------------*/
546
547 /* --- @showrule@ --- *
548 *
549 * Arguments: @rule *r@ = pointer to a rule block
550 *
551 * Returns: ---
552 *
553 * Use: Writes a rule block to the output in a pleasant way.
554 */
555
556 static const char *xltuser(uid_t u)
557 {
558 static char buf[16];
559 struct passwd *pw = userdb_userById(u);
560 if (pw)
561 return (pw->pw_name);
562 sprintf(buf, "%u", (unsigned)u);
563 return (buf);
564 }
565
566 static void classfirstrow(class_node *c, const char *fmt, sym_iter *i,
567 unsigned bit, unsigned *imask)
568 {
569 switch (c->type & clNode_mask) {
570 case clNode_any:
571 printf(fmt, (c == class_all ? "ALL" :
572 c == class_none ? "NONE" :
573 "<bug>"));
574 break;
575 case clNode_immed:
576 printf(fmt, (c->type & clType_user) ? xltuser(c->v.u) : c->v.s);
577 break;
578 case clNode_hash: {
579 sym_base *b;
580 sym_createIter(i, &c->v.t);
581 b = sym_next(i);
582 if (!b) {
583 printf(fmt, "");
584 break;
585 } else if (c->type & clType_user)
586 printf(fmt, xltuser(*(uid_t *)b->name));
587 else
588 printf(fmt, b->name);
589 *imask |= bit;
590 } break;
591 default:
592 printf(fmt, "<complex>");
593 break;
594 }
595 }
596
597 static void showclass(class_node *c,
598 void (*sc)(class_node *c),
599 void (*sh)(sym_base *b))
600 {
601 const char *op;
602 unsigned type;
603
604 switch (c->type & clNode_mask) {
605 case clNode_any:
606 fputs(c == class_all ? "ALL" :
607 c == class_none ? "NONE" : "<buggy>",
608 stdout);
609 break;
610 case clNode_immed:
611 sc(c);
612 break;
613 case clNode_hash: {
614 sym_iter i;
615 sym_base *b;
616 sym_createIter(&i, &c->v.t);
617 fputc('(', stdout);
618 if ((b = sym_next(&i)) != 0) {
619 sh(b);
620 while ((b = sym_next(&i)) != 0) {
621 fputs(", ", stdout);
622 sh(b);
623 }
624 }
625 fputc(')', stdout);
626 } break;
627 case clNode_union:
628 op = " | ";
629 goto binop;
630 case clNode_diff:
631 op = " - ";
632 goto binop;
633 case clNode_isect:
634 op = " & ";
635 goto binop;
636 default:
637 fputs("<unknown node type>", stdout);
638 break;
639 binop:
640 type = c->type & clNode_mask;
641 fputc('(', stdout);
642 do {
643 showclass(c->v.c.l, sc, sh);
644 fputs(op, stdout);
645 c = c->v.c.r;
646 } while ((c->type & clNode_mask) == type);
647 showclass(c, sc, sh);
648 fputc(')', stdout);
649 break;
650 }
651 }
652
653 static void showuseri(class_node *c) { fputs(xltuser(c->v.u), stdout); }
654
655 static void showuserh(sym_base *b)
656 {
657 fputs(xltuser(*(uid_t *)b->name), stdout);
658 }
659
660 static void showstringi(class_node *c) { fputs(c->v.s, stdout); }
661
662 static void showstringh(sym_base *b) { fputs(b->name, stdout); }
663
664 static void showrule(rule *r)
665 {
666 /* --- First up: display of simple classes in columns --- */
667
668 if (flags & f_simple) {
669 sym_iter a, b, c, d;
670 sym_base *w = 0, *x = 0, *y = 0, *z = 0;
671 unsigned imask = 0;
672
673 /* --- Print the header line if necessary --- */
674
675 if (!(flags & f_header)) {
676 if (!(flags & f_nohead)) {
677 if (outmask & cat_from) printf("%-15s ", "FROM");
678 if (outmask & cat_to) printf("%-15s ", "TO");
679 if (outmask & cat_where) printf("%-24s ", "HOST");
680 if (outmask & cat_what) printf("%s", "COMMAND");
681 fputc('\n', stdout);
682 fputc('\n', stdout);
683 }
684 flags |= f_header;
685 } else
686 fputc('\n', stdout);
687
688 /* --- Print out the first row --- */
689
690 if (outmask & cat_from)
691 classfirstrow(r->from, "%-15.15s ", &a, cat_from, &imask);
692 if (outmask & cat_to)
693 classfirstrow(r->to, "%-15.15s ", &b, cat_to, &imask);
694 if (outmask & cat_where)
695 classfirstrow(r->host, "%-24.24s ", &c, cat_where, &imask);
696 if (outmask & cat_what)
697 classfirstrow(r->cmd, "%s", &d, cat_what, &imask);
698 fputc('\n', stdout);
699
700 /* --- And now for the rest --- */
701
702 for (;;) {
703 if ((imask & cat_from) && (w = sym_next(&a)) == 0)
704 imask &= ~cat_from;
705 if ((imask & cat_to) && (x = sym_next(&b)) == 0)
706 imask &= ~cat_to;
707 if ((imask & cat_where) && (y = sym_next(&c)) == 0)
708 imask &= ~cat_where;
709 if ((imask & cat_what) && (z = sym_next(&d)) == 0)
710 imask &= ~cat_what;
711
712 if (!imask)
713 break;
714
715 if (outmask & cat_from) {
716 printf("%-15.15s ",
717 !(imask & cat_from) ? "" : xltuser(*(uid_t *)w->name));
718 }
719
720 if (outmask & cat_to) {
721 printf("%-15.15s ",
722 !(imask & cat_to) ? "" : xltuser(*(uid_t *)x->name));
723 }
724
725 if (outmask & cat_where)
726 printf("%-24.24s ", !(imask & cat_where) ? "" : y->name);
727
728 if (outmask & cat_what)
729 printf("%s", !(imask & cat_what) ? "" : z->name);
730
731 fputc('\n', stdout);
732 }
733 }
734
735 /* --- Otherwise deal with complex cases --- */
736
737 else {
738 if (flags & f_header)
739 fputc('\n', stdout);
740 else
741 flags |= f_header;
742 if (outmask & cat_from) {
743 fputs(" From: ", stdout);
744 showclass(r->from, showuseri, showuserh);
745 fputc('\n', stdout);
746 }
747 if (outmask & cat_to) {
748 fputs(" To: ", stdout);
749 showclass(r->to, showuseri, showuserh);
750 fputc('\n', stdout);
751 }
752 if (outmask & cat_where) {
753 fputs(" Hosts: ", stdout);
754 showclass(r->host, showstringi, showstringh);
755 fputc('\n', stdout);
756 }
757 if (outmask & cat_what) {
758 fputs("Commands: ", stdout);
759 showclass(r->cmd, showstringi, showstringh);
760 fputc('\n', stdout);
761 }
762 }
763 }
764
765 /*----- Dummy functions ---------------------------------------------------*/
766
767 void daemon_usePort(int p) { ; }
768 void daemon_readKey(const char *f) { ; }
769
770 /*----- Main code ---------------------------------------------------------*/
771
772 /* --- @main@ --- *
773 *
774 * Arguments: @int argc@ = number of command line arguments
775 * @char *argv[]@ = pointer to command line arguments
776 *
777 * Returns: Zero if all went OK.
778 *
779 * Use: Verifies and queries the `become' configuration file.
780 */
781
782 int main(int argc, char *argv[])
783 {
784 qnode *qtree;
785
786 /* --- Initialise things --- */
787
788 ego(argv[0]);
789 ac = argc; av = argv;
790
791 /* --- Read the query tree --- */
792
793 qtree = qparse();
794
795 /* --- Dump the tree if so requested --- */
796
797 if (flags & f_dump) {
798 dumptree(qtree, 0);
799 return (0);
800 }
801
802 /* --- Check columns requested --- */
803
804 if (outmask == (outmask & (outmask - 1)))
805 flags |= f_single;
806
807 /* --- Read the ruleset --- */
808
809 if (!(flags & f_userdb)) {
810 userdb_init();
811 userdb_local();
812 userdb_yp();
813 }
814
815 netg_init();
816 name_init();
817 rule_init();
818
819 {
820 FILE *fp = fopen(cf, "r");
821 int ok;
822
823 if (!fp)
824 die("couldn't open configuration file `%s': %s", cf, strerror(errno));
825 lexer_scan(fp);
826 ok = parse();
827 if (flags & f_check)
828 exit(ok);
829 }
830
831 /* --- Now scan the query --- */
832
833 {
834 rule *rl = rule_list(), *r;
835
836 /* --- Decide on output format if not already chosen --- */
837
838 if (!(flags & f_force)) {
839 r = rl;
840 flags |= f_simple;
841 while (r) {
842 if ((!qtree || checkrule(r, qtree)) &&
843 ((r->host->type & clNode_mask) >= clNode_binop ||
844 (r->from->type & clNode_mask) >= clNode_binop ||
845 (r->to->type & clNode_mask) >= clNode_binop ||
846 (r->cmd->type & clNode_mask) >= clNode_binop)) {
847 flags &= ~f_simple;
848 break;
849 }
850 r = r->next;
851 }
852 }
853
854 /* --- Now just dump the matching items --- */
855
856 r = rl;
857 while (r) {
858 if (!qtree || checkrule(r, qtree)) {
859 flags |= f_match;
860 showrule(r);
861 }
862 r = r->next;
863 }
864 }
865
866 /* --- Done --- */
867
868 if (!(flags & f_match))
869 die("no match");
870 return (0);
871 }
872
873 /*----- That's all, folks -------------------------------------------------*/