chiark / gitweb /
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 -------------------------------------------------*/