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 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 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 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 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