1/*++
2/* NAME
3/*	match_list 3
4/* SUMMARY
5/*	generic list-based pattern matching
6/* SYNOPSIS
7/*	#include <match_list.h>
8/*
9/*	MATCH_LIST *match_list_init(flags, pattern_list, count, func,...)
10/*	int	flags;
11/*	const char *pattern_list;
12/*	int	count;
13/*	int	(*func)(int flags, const char *string, const char *pattern);
14/*
15/*	int	match_list_match(list, string,...)
16/*	MATCH_LIST *list;
17/*	const char *string;
18/*
19/*	void match_list_free(list)
20/*	MATCH_LIST *list;
21/* DESCRIPTION
22/*	This module implements a framework for tests for list membership.
23/*	The actual tests are done by user-supplied functions.
24/*
25/*	Patterns are separated by whitespace and/or commas. A pattern
26/*	is either a string, a file name (in which case the contents
27/*	of the file are substituted for the file name) or a type:name
28/*	lookup table specification. In order to reverse the result of
29/*	a pattern match, precede a pattern with an exclamation point (!).
30/*
31/*	match_list_init() performs initializations. The flags argument
32/*	specifies the bit-wise OR of zero or more of the following:
33/* .RS
34/* .IP MATCH_FLAG_PARENT
35/*	The hostname pattern foo.com matches any name within the domain
36/*	foo.com. If this flag is cleared, foo.com matches itself
37/*	only, and .foo.com matches any name below the domain foo.com.
38/* .IP MATCH_FLAG_RETURN
39/*	Request that match_list_match() logs a warning and returns
40/*	zero (with list->error set to a non-zero dictionary error
41/*	code) instead of raising a fatal run-time error.
42/* .RE
43/*	Specify MATCH_FLAG_NONE to request none of the above.
44/*	The pattern_list argument specifies a list of patterns.  The third
45/*	argument specifies how many match functions follow.
46/*
47/*	match_list_match() matches strings against the specified pattern
48/*	list, passing the first string to the first function given to
49/*	match_list_init(), the second string to the second function, and
50/*	so on.
51/*
52/*	match_list_free() releases storage allocated by match_list_init().
53/* DIAGNOSTICS
54/*	Fatal error: unable to open or read a match_list file; invalid
55/*	match_list pattern.
56/* SEE ALSO
57/*	host_match(3) match hosts by name or by address
58/* LICENSE
59/* .ad
60/* .fi
61/*	The Secure Mailer license must be distributed with this software.
62/* AUTHOR(S)
63/*	Wietse Venema
64/*	IBM T.J. Watson Research
65/*	P.O. Box 704
66/*	Yorktown Heights, NY 10598, USA
67/*--*/
68
69/* System library. */
70
71#include <sys_defs.h>
72#include <unistd.h>
73#include <string.h>
74#include <fcntl.h>
75#include <stdlib.h>
76#include <stdarg.h>
77
78/* Utility library. */
79
80#include <msg.h>
81#include <mymalloc.h>
82#include <vstring.h>
83#include <vstream.h>
84#include <vstring_vstream.h>
85#include <stringops.h>
86#include <argv.h>
87#include <dict.h>
88#include <match_list.h>
89
90/* Application-specific */
91
92#define MATCH_DICTIONARY(pattern) \
93    ((pattern)[0] != '[' && strchr((pattern), ':') != 0)
94
95/* match_list_parse - parse buffer, destroy buffer */
96
97static ARGV *match_list_parse(ARGV *list, char *string, int init_match)
98{
99    const char *myname = "match_list_parse";
100    VSTRING *buf = vstring_alloc(10);
101    VSTREAM *fp;
102    const char *delim = " ,\t\r\n";
103    char   *bp = string;
104    char   *start;
105    char   *item;
106    char   *map_type_name_flags;
107    int     match;
108
109#define OPEN_FLAGS	O_RDONLY
110#define DICT_FLAGS	(DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX)
111#define STR(x)		vstring_str(x)
112
113    /*
114     * /filename contents are expanded in-line. To support !/filename we
115     * prepend the negation operator to each item from the file.
116     */
117    while ((start = mystrtok(&bp, delim)) != 0) {
118	if (*start == '#') {
119	    msg_warn("%s: comment at end of line is not supported: %s %s",
120		     myname, start, bp);
121	    break;
122	}
123	for (match = init_match, item = start; *item == '!'; item++)
124	    match = !match;
125	if (*item == 0)
126	    msg_fatal("%s: no pattern after '!'", myname);
127	if (*item == '/') {			/* /file/name */
128	    if ((fp = vstream_fopen(item, O_RDONLY, 0)) == 0) {
129		vstring_sprintf(buf, "%s:%s", DICT_TYPE_NOFILE, item);
130		/* XXX Should increment existing map refcount. */
131		if (dict_handle(STR(buf)) == 0)
132		    dict_register(STR(buf),
133				  dict_surrogate(DICT_TYPE_NOFILE, item,
134						 OPEN_FLAGS, DICT_FLAGS,
135						 "open file %s: %m", item));
136		argv_add(list, STR(buf), (char *) 0);
137	    } else {
138		while (vstring_fgets(buf, fp))
139		    if (vstring_str(buf)[0] != '#')
140			list = match_list_parse(list, vstring_str(buf), match);
141		if (vstream_fclose(fp))
142		    msg_fatal("%s: read file %s: %m", myname, item);
143	    }
144	} else if (MATCH_DICTIONARY(item)) {	/* type:table */
145	    vstring_sprintf(buf, "%s%s(%o,%s)", match ? "" : "!",
146			    item, OPEN_FLAGS, dict_flags_str(DICT_FLAGS));
147	    map_type_name_flags = STR(buf) + (match == 0);
148	    /* XXX Should increment existing map refcount. */
149	    if (dict_handle(map_type_name_flags) == 0)
150		dict_register(map_type_name_flags,
151			      dict_open(item, OPEN_FLAGS, DICT_FLAGS));
152	    argv_add(list, STR(buf), (char *) 0);
153	} else {				/* other pattern */
154	    argv_add(list, match ? item :
155		     STR(vstring_sprintf(buf, "!%s", item)), (char *) 0);
156	}
157    }
158    vstring_free(buf);
159    return (list);
160}
161
162/* match_list_init - initialize pattern list */
163
164MATCH_LIST *match_list_init(int flags, const char *patterns, int match_count,...)
165{
166    MATCH_LIST *list;
167    char   *saved_patterns;
168    va_list ap;
169    int     i;
170
171    if (flags & ~MATCH_FLAG_ALL)
172	msg_panic("match_list_init: bad flags 0x%x", flags);
173
174    list = (MATCH_LIST *) mymalloc(sizeof(*list));
175    list->flags = flags;
176    list->match_count = match_count;
177    list->match_func =
178	(MATCH_LIST_FN *) mymalloc(match_count * sizeof(MATCH_LIST_FN));
179    list->match_args =
180	(const char **) mymalloc(match_count * sizeof(const char *));
181    va_start(ap, match_count);
182    for (i = 0; i < match_count; i++)
183	list->match_func[i] = va_arg(ap, MATCH_LIST_FN);
184    va_end(ap);
185    list->error = 0;
186
187#define DO_MATCH	1
188
189    saved_patterns = mystrdup(patterns);
190    list->patterns = match_list_parse(argv_alloc(1), saved_patterns, DO_MATCH);
191    argv_terminate(list->patterns);
192    myfree(saved_patterns);
193    return (list);
194}
195
196/* match_list_match - match strings against pattern list */
197
198int     match_list_match(MATCH_LIST *list,...)
199{
200    const char *myname = "match_list_match";
201    char  **cpp;
202    char   *pat;
203    int     match;
204    int     i;
205    va_list ap;
206
207    /*
208     * Iterate over all patterns in the list, stop at the first match.
209     */
210    va_start(ap, list);
211    for (i = 0; i < list->match_count; i++)
212	list->match_args[i] = va_arg(ap, const char *);
213    va_end(ap);
214
215    list->error = 0;
216    for (cpp = list->patterns->argv; (pat = *cpp) != 0; cpp++) {
217	for (match = 1; *pat == '!'; pat++)
218	    match = !match;
219	for (i = 0; i < list->match_count; i++)
220	    if (list->match_func[i] (list, list->match_args[i], pat))
221		return (match);
222	    else if (list->error != 0)
223		return (0);
224    }
225    if (msg_verbose)
226	for (i = 0; i < list->match_count; i++)
227	    msg_info("%s: %s: no match", myname, list->match_args[i]);
228    return (0);
229}
230
231/* match_list_free - release storage */
232
233void    match_list_free(MATCH_LIST *list)
234{
235    /* XXX Should decrement map refcounts. */
236    argv_free(list->patterns);
237    myfree((char *) list->match_func);
238    myfree((char *) list->match_args);
239    myfree((char *) list);
240}
241