diff options
Diffstat (limited to '')
-rw-r--r-- | src/util/match_list.c | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/src/util/match_list.c b/src/util/match_list.c new file mode 100644 index 0000000..520485d --- /dev/null +++ b/src/util/match_list.c @@ -0,0 +1,281 @@ +/*++ +/* NAME +/* match_list 3 +/* SUMMARY +/* generic list-based pattern matching +/* SYNOPSIS +/* #include <match_list.h> +/* +/* MATCH_LIST *match_list_init(pname, flags, pattern_list, count, func,...) +/* const char *pname; +/* int flags; +/* const char *pattern_list; +/* int count; +/* int (*func)(int flags, const char *string, const char *pattern); +/* +/* int match_list_match(list, string,...) +/* MATCH_LIST *list; +/* const char *string; +/* +/* void match_list_free(list) +/* MATCH_LIST *list; +/* DESCRIPTION +/* This module implements a framework for tests for list +/* membership. The actual tests are done by user-supplied +/* functions. +/* +/* Patterns are separated by whitespace and/or commas. A pattern +/* is either a string, a file name (in which case the contents +/* of the file are substituted for the file name) or a type:name +/* lookup table specification. In order to reverse the result +/* of a pattern match, precede a pattern with an exclamation +/* point (!). +/* +/* match_list_init() performs initializations. When the global +/* util_utf8_enable variable is non-zero, and when the code +/* is compiled with EAI support, string comparison will use +/* caseless UTF-8 mode. Otherwise, only ASCII characters will +/* be casefolded. +/* +/* match_list_match() matches strings against the specified +/* pattern list, passing the first string to the first function +/* given to match_list_init(), the second string to the second +/* function, and so on. +/* +/* match_list_free() releases storage allocated by match_list_init(). +/* +/* Arguments: +/* .IP pname +/* Parameter name or other identifying information that is +/* prepended to error messages. +/* .IP flags +/* Specifies the bit-wise OR of zero or more of the following: +/* .RS +/* .IP MATCH_FLAG_PARENT +/* The hostname pattern foo.com matches any name within the +/* domain foo.com. If this flag is cleared, foo.com matches +/* itself only, and .foo.com matches any name below the domain +/* foo.com. +/* .IP MATCH_FLAG_RETURN +/* Request that match_list_match() logs a warning and returns +/* zero (with list->error set to a non-zero dictionary error +/* code) instead of raising a fatal run-time error. +/* .RE +/* Specify MATCH_FLAG_NONE to request none of the above. +/* .IP pattern_list +/* A list of patterns. +/* .IP count +/* Specifies how many match functions follow. +/* .IP list +/* Pattern list produced by match_list_init(). +/* .IP string +/* Search string. +/* DIAGNOSTICS +/* Fatal error: unable to open or read a match_list file; invalid +/* match_list pattern; casefold error (UTF-8 mode only). +/* SEE ALSO +/* host_match(3) match hosts by name or by address +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdarg.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <stringops.h> +#include <argv.h> +#include <dict.h> +#include <match_list.h> + +/* Application-specific */ + +#define MATCH_DICTIONARY(pattern) \ + ((pattern)[0] != '[' && strchr((pattern), ':') != 0) + +/* match_list_parse - parse buffer, destroy buffer */ + +static ARGV *match_list_parse(MATCH_LIST *match_list, ARGV *pat_list, + char *string, int init_match) +{ + const char *myname = "match_list_parse"; + VSTRING *buf = vstring_alloc(10); + VSTREAM *fp; + const char *delim = CHARS_COMMA_SP; + char *bp = string; + char *start; + char *item; + char *map_type_name_flags; + int match; + + /* + * We do not use DICT_FLAG_FOLD_FIX, because we casefold the search + * string at the beginning of a search, and we use strcmp() for string + * comparison. This works because string patterns are casefolded during + * match_list initialization, and databases are supposed to fold case + * upon creation. + */ +#define OPEN_FLAGS O_RDONLY +#define DICT_FLAGS (DICT_FLAG_LOCK | DICT_FLAG_UTF8_REQUEST) +#define STR(x) vstring_str(x) + + /* + * /filename contents are expanded in-line. To support !/filename we + * prepend the negation operator to each item from the file. + * + * If there is an error, implement graceful degradation by inserting a + * pseudo table whose lookups fail with a warning message. + */ + while ((start = mystrtokq(&bp, delim, CHARS_BRACE)) != 0) { + if (*start == '#') { + msg_warn("%s: comment at end of line is not supported: %s %s", + match_list->pname, start, bp); + break; + } + for (match = init_match, item = start; *item == '!'; item++) + match = !match; + if (*item == 0) + /* No graceful degradation for this... */ + msg_fatal("%s: no pattern after '!'", match_list->pname); + if (*item == '/') { /* /file/name */ + if ((fp = vstream_fopen(item, O_RDONLY, 0)) == 0) { + /* Replace unusable pattern with pseudo table. */ + vstring_sprintf(buf, "%s:%s", DICT_TYPE_NOFILE, item); + if (dict_handle(STR(buf)) == 0) + dict_register(STR(buf), + dict_surrogate(DICT_TYPE_NOFILE, item, + OPEN_FLAGS, DICT_FLAGS, + "open file %s: %m", item)); + argv_add(pat_list, STR(buf), (char *) 0); + } else { + while (vstring_fgets(buf, fp)) + if (vstring_str(buf)[0] != '#') + pat_list = match_list_parse(match_list, pat_list, + vstring_str(buf), match); + if (vstream_fclose(fp)) + msg_fatal("%s: read file %s: %m", myname, item); + } + } else if (MATCH_DICTIONARY(item)) { /* type:table */ + vstring_sprintf(buf, "%s%s(%o,%s)", match ? "" : "!", + item, OPEN_FLAGS, dict_flags_str(DICT_FLAGS)); + map_type_name_flags = STR(buf) + (match == 0); + if (dict_handle(map_type_name_flags) == 0) + dict_register(map_type_name_flags, + dict_open(item, OPEN_FLAGS, DICT_FLAGS)); + argv_add(pat_list, STR(buf), (char *) 0); + } else { /* other pattern */ + casefold(match_list->fold_buf, match ? + item : STR(vstring_sprintf(buf, "!%s", item))); + argv_add(pat_list, STR(match_list->fold_buf), (char *) 0); + } + } + vstring_free(buf); + return (pat_list); +} + +/* match_list_init - initialize pattern list */ + +MATCH_LIST *match_list_init(const char *pname, int flags, + const char *patterns, int match_count,...) +{ + MATCH_LIST *list; + char *saved_patterns; + va_list ap; + int i; + + if (flags & ~MATCH_FLAG_ALL) + msg_panic("match_list_init: bad flags 0x%x", flags); + + list = (MATCH_LIST *) mymalloc(sizeof(*list)); + list->pname = mystrdup(pname); + list->flags = flags; + list->match_count = match_count; + list->match_func = + (MATCH_LIST_FN *) mymalloc(match_count * sizeof(MATCH_LIST_FN)); + list->match_args = + (const char **) mymalloc(match_count * sizeof(const char *)); + va_start(ap, match_count); + for (i = 0; i < match_count; i++) + list->match_func[i] = va_arg(ap, MATCH_LIST_FN); + va_end(ap); + list->error = 0; + list->fold_buf = vstring_alloc(20); + +#define DO_MATCH 1 + + saved_patterns = mystrdup(patterns); + list->patterns = match_list_parse(list, argv_alloc(1), saved_patterns, + DO_MATCH); + argv_terminate(list->patterns); + myfree(saved_patterns); + return (list); +} + +/* match_list_match - match strings against pattern list */ + +int match_list_match(MATCH_LIST *list,...) +{ + const char *myname = "match_list_match"; + char **cpp; + char *pat; + int match; + int i; + va_list ap; + + /* + * Iterate over all patterns in the list, stop at the first match. + */ + va_start(ap, list); + for (i = 0; i < list->match_count; i++) + list->match_args[i] = va_arg(ap, const char *); + va_end(ap); + + list->error = 0; + for (cpp = list->patterns->argv; (pat = *cpp) != 0; cpp++) { + for (match = 1; *pat == '!'; pat++) + match = !match; + for (i = 0; i < list->match_count; i++) { + casefold(list->fold_buf, list->match_args[i]); + if (list->match_func[i] (list, STR(list->fold_buf), pat)) + return (match); + else if (list->error != 0) + return (0); + } + } + if (msg_verbose) + for (i = 0; i < list->match_count; i++) + msg_info("%s: %s: no match", myname, list->match_args[i]); + return (0); +} + +/* match_list_free - release storage */ + +void match_list_free(MATCH_LIST *list) +{ + /* XXX Should decrement map refcounts. */ + myfree(list->pname); + argv_free(list->patterns); + myfree((void *) list->match_func); + myfree((void *) list->match_args); + vstring_free(list->fold_buf); + myfree((void *) list); +} |