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