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