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