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