chiark / gitweb /
Major overhaul. Now uses DSA signatures rather than the bogus symmetric
[become] / src / bcquery.c
CommitLineData
908a5930 1/* -*-c-*-
2 *
f60a3434 3 * $Id: bcquery.c,v 1.4 2003/10/12 00:14:55 mdw Exp $
908a5930 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 $
f60a3434 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 *
79fae27d 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
7df38e1e 41 * Cosmetic change: use sizeof(destination) in memcpy.
42 *
908a5930 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
f60a3434 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
908a5930 85/* --- Local headers --- */
86
87#include "become.h"
88#include "class.h"
89#include "config.h"
90#include "daemon.h"
91#include "lexer.h"
908a5930 92#include "name.h"
93#include "netg.h"
79fae27d 94#include "parse.h"
908a5930 95#include "rule.h"
908a5930 96#include "userdb.h"
97
98/*----- Type definitions --------------------------------------------------*/
99
100enum {
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
110typedef 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
130enum {
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
142static int ac;
143static char **av;
144static int opt;
145static unsigned flags;
146static const char *cf = file_RULES;
147static 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
161static 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
189static 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
215again:
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:
f60a3434 277 die(1, "unknown column specifier `%c'", *p);
908a5930 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
f60a3434 289 die(1, "bad mode while setting output mask: %u", mode);
908a5930 290 break;
291 }
292 p++;
293 }
294 goto again;
295 }
296 case '?':
f60a3434 297 die(1, "type `%s --help' for usage information", quis());
908a5930 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)
f60a3434 306 die(1, "unexpected text `%s' found", optarg);
908a5930 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
324static qnode *qparse_expr(void);
325
326static qnode *qparse_atom(void)
327{
328 switch (opt) {
329 case '(': {
330 qnode *q;
331 nextopt();
332 q = qparse_expr();
333 if (opt != ')')
f60a3434 334 die(1, "syntax error: expected `)', found `%s'", optname());
908a5930 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)
f60a3434 343 die(1, "unknown host `%s'", optarg);
908a5930 344 q->q_cat = cat_where;
7df38e1e 345 memcpy(&q->q_in, h->h_addr, sizeof(q->q_in));
908a5930 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)
f60a3434 364 die(1, "unknown user `%s'", optarg);
908a5930 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:
f60a3434 378 die(1, "unexpected token: `%s'", optname());
908a5930 379 }
380 return (0);
381}
382
383static 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
395static qnode *qparse_term(void)
396{
397 qnode *top, *q, **qq;
398 qq = &top;
399
400again:
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
418static qnode *qparse_expr(void)
419{
420 qnode *top, *q, **qq;
421 qq = &top;
422
423again:
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
440static qnode *qparse(void)
441{
442 qnode *q;
443 nextopt();
444 if (opt == EOF)
445 return (0);
446 q = qparse_expr();
447 if (opt != EOF)
f60a3434 448 die(1, "syntax error: `%s' unexpected", optname());
908a5930 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
462static void dumptree(qnode *q, int indent)
463{
464 if (!q)
465 printf("<empty> -- magic query which matches everything\n");
466
467again:
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
514static int checkrule(rule *r, qnode *q)
515{
516again:
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
f60a3434 550 die(1, "unexpected cat code %u in checkrule", q->q_cat);
908a5930 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
565static 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
575static 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;
f60a3434 589 sym_mkiter(i, &c->v.t);
908a5930 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
606static 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;
f60a3434 625 sym_mkiter(&i, &c->v.t);
908a5930 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
662static void showuseri(class_node *c) { fputs(xltuser(c->v.u), stdout); }
663
664static void showuserh(sym_base *b)
665{
666 fputs(xltuser(*(uid_t *)b->name), stdout);
667}
668
669static void showstringi(class_node *c) { fputs(c->v.s, stdout); }
670
671static void showstringh(sym_base *b) { fputs(b->name, stdout); }
672
673static 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
776void daemon_usePort(int p) { ; }
777void 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
791int 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)
f60a3434 833 die(1, "couldn't open configuration file `%s': %s", cf, strerror(errno));
908a5930 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))
f60a3434 878 die(1, "no match");
908a5930 879 return (0);
880}
881
882/*----- That's all, folks -------------------------------------------------*/