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