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