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