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