9b9287787e2cf375f1b094387edd298eb5904bb2
[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 -------------------------------------------------*/