chiark / gitweb /
manual/Makefile.am: Install the HTML version of the manual correctly.
[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 -------------------------------------------------*/