diff options
Diffstat (limited to 'src/util/dict_stream.c')
-rw-r--r-- | src/util/dict_stream.c | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/src/util/dict_stream.c b/src/util/dict_stream.c new file mode 100644 index 0000000..e28ad71 --- /dev/null +++ b/src/util/dict_stream.c @@ -0,0 +1,274 @@ +/*++ +/* NAME +/* dict_stream 3 +/* SUMMARY +/* +/* SYNOPSIS +/* #include <dict.h> +/* +/* VSTREAM *dict_stream_open( +/* const char *dict_type, +/* const char *mapname, +/* int open_flags, +/* int dict_flags, +/* struct stat * st, +/* VSTRING **why) +/* DESCRIPTION +/* dict_stream_open() opens a dictionary, which can be specified +/* as a file name, or as inline text enclosed with {}. If successful, +/* dict_stream_open() returns a non-null VSTREAM pointer. Otherwise, +/* it returns an error text through the why argument, allocating +/* storage for the error text if the why argument points to a +/* null pointer. +/* +/* When the dictionary file is specified inline, dict_stream_open() +/* removes the outer {} from the mapname value, and removes leading +/* or trailing comma or whitespace from the result. It then expects +/* to find zero or more rules enclosed in {}, separated by comma +/* and/or whitespace. dict_stream() writes each rule as one text +/* line to an in-memory stream, without its enclosing {} and without +/* leading or trailing whitespace. The result value is a VSTREAM +/* pointer for the in-memory stream that can be read as a regular +/* file. +/* .sp +/* inline-file = "{" 0*(0*wsp-comma rule-spec) 0*wsp-comma "}" +/* .sp +/* rule-spec = "{" 0*wsp rule-text 0*wsp "}" +/* .sp +/* rule-text = any text containing zero or more balanced {} +/* .sp +/* wsp-comma = wsp | "," +/* .sp +/* wsp = whitespace +/* +/* Arguments: +/* .IP dict_type +/* .IP open_flags +/* .IP dict_flags +/* The same as with dict_open(3). +/* .IP mapname +/* Pathname of a file with dictionary content, or inline dictionary +/* content as specified above. +/* .IP st +/* File metadata with the file owner, or fake metadata with the +/* real UID and GID of the dict_stream_open() caller. This is +/* used for "taint" tracking (zero=trusted, non-zero=untrusted). +/* IP why +/* Pointer to pointer to error message storage. dict_stream_open() +/* updates this storage when reporting an error, and allocates +/* memory if why points to a null pointer. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> + + /* + * Utility library. + */ +#include <dict.h> +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> +#include <vstring.h> + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* dict_inline_to_multiline - convert inline map spec to multiline text */ + +static char *dict_inline_to_multiline(VSTRING *vp, const char *mapname) +{ + char *saved_name = mystrdup(mapname); + char *bp = saved_name; + char *cp; + char *err = 0; + + VSTRING_RESET(vp); + /* Strip the {} from the map "name". */ + err = extpar(&bp, CHARS_BRACE, EXTPAR_FLAG_NONE); + /* Extract zero or more rules inside {}. */ + while (err == 0 && (cp = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) + if ((err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) == 0) + /* Write rule to in-memory file. */ + vstring_sprintf_append(vp, "%s\n", cp); + VSTRING_TERMINATE(vp); + myfree(saved_name); + return (err); +} + +/* dict_stream_open - open inline configuration or configuration file */ + +VSTREAM *dict_stream_open(const char *dict_type, const char *mapname, + int open_flags, int dict_flags, + struct stat * st, VSTRING **why) +{ + VSTRING *inline_buf = 0; + VSTREAM *map_fp; + char *err = 0; + +#define RETURN_0_WITH_REASON(...) do { \ + if (*why == 0) \ + *why = vstring_alloc(100); \ + vstring_sprintf(*why, __VA_ARGS__); \ + if (inline_buf != 0) \ + vstring_free(inline_buf); \ + if (err != 0) \ + myfree(err); \ + return (0); \ + } while (0) + + if (mapname[0] == CHARS_BRACE[0]) { + inline_buf = vstring_alloc(100); + if ((err = dict_inline_to_multiline(inline_buf, mapname)) != 0) + RETURN_0_WITH_REASON("%s map: %s", dict_type, err); + map_fp = vstream_memopen(inline_buf, O_RDONLY); + vstream_control(map_fp, VSTREAM_CTL_OWN_VSTRING, VSTREAM_CTL_END); + st->st_uid = getuid(); /* geteuid()? */ + st->st_gid = getgid(); /* getegid()? */ + return (map_fp); + } else { + if ((map_fp = vstream_fopen(mapname, open_flags, 0)) == 0) + RETURN_0_WITH_REASON("open %s: %m", mapname); + if (fstat(vstream_fileno(map_fp), st) < 0) + msg_fatal("fstat %s: %m", mapname); + return (map_fp); + } +} + +#ifdef TEST + +#include <string.h> + +int main(int argc, char **argv) +{ + struct testcase { + const char *title; + const char *mapname; /* starts with brace */ + const char *expect_err; /* null or message */ + const char *expect_cont; /* null or content */ + }; + +#define EXP_NOERR 0 +#define EXP_NOCONT 0 + +#define STRING_OR(s, text_if_null) ((s) ? (s) : (text_if_null)) +#define DICT_TYPE_TEST "test" + + const char rule_spec_error[] = DICT_TYPE_TEST " map: " + "syntax error after '}' in \"{blah blah}x\""; + const char inline_config_error[] = DICT_TYPE_TEST " map: " + "syntax error after '}' in \"{{foo bar}, {blah blah}}x\""; + struct testcase testcases[] = { + {"normal", + "{{foo bar}, {blah blah}}", EXP_NOERR, "foo bar\nblah blah\n" + }, + {"trims leading/trailing wsp around rule-text", + "{{ foo bar }, { blah blah }}", EXP_NOERR, "foo bar\nblah blah\n" + }, + {"trims leading/trailing comma-wsp around rule-spec", + "{, ,{foo bar}, {blah blah}, ,}", EXP_NOERR, "foo bar\nblah blah\n" + }, + {"empty inline-file", + "{, }", EXP_NOERR, "" + }, + {"propagates extpar error for inline-file", + "{{foo bar}, {blah blah}}x", inline_config_error, EXP_NOCONT + }, + {"propagates extpar error for rule-spec", + "{{foo bar}, {blah blah}x}", rule_spec_error, EXP_NOCONT + }, + 0, + }; + struct testcase *tp; + VSTRING *act_err = 0; + VSTRING *act_cont = vstring_alloc(100); + VSTREAM *fp; + struct stat st; + ssize_t exp_len; + ssize_t act_len; + int pass; + int fail; + + for (pass = fail = 0, tp = testcases; tp->title; tp++) { + int test_passed = 0; + + msg_info("RUN test case %ld %s", (long) (tp - testcases), tp->title); + +#if 0 + msg_info("title=%s", tp->title); + msg_info("mapname=%s", tp->mapname); + msg_info("expect_err=%s", STRING_OR_NULL(tp->expect_err)); + msg_info("expect_cont=%s", STRINGOR_NULL(tp->expect_cont)); +#endif + + if (act_err) + VSTRING_RESET(act_err); + fp = dict_stream_open(DICT_TYPE_TEST, tp->mapname, O_RDONLY, + 0, &st, &act_err); + if (fp) { + if (tp->expect_err) { + msg_warn("test case %s: got stream, expected error", tp->title); + } else if (!tp->expect_err && act_err && LEN(act_err) > 0) { + msg_warn("test case %s: got error '%s', expected noerror", + tp->title, STR(act_err)); + } else if (!tp->expect_cont) { + msg_warn("test case %s: got stream, expected nostream", + tp->title); + } else { + exp_len = strlen(tp->expect_cont); + if ((act_len = vstream_fread_buf(fp, act_cont, 2 * exp_len)) < 0) { + msg_warn("test case %s: content read error", tp->title); + } else { + VSTRING_TERMINATE(act_cont); + if (strcmp(tp->expect_cont, STR(act_cont)) != 0) { + msg_warn("test case %s: got content '%s', expected '%s'", + tp->title, STR(act_cont), tp->expect_cont); + } else { + test_passed = 1; + } + } + } + } else { + if (!tp->expect_err) { + msg_warn("test case %s: got nostream, expected noerror", + tp->title); + } else if (tp->expect_cont) { + msg_warn("test case %s: got nostream, expected stream", + tp->title); + } else if (strcmp(STR(act_err), tp->expect_err) != 0) { + msg_warn("test case %s: got error '%s', expected '%s'", + tp->title, STR(act_err), tp->expect_err); + } else { + test_passed = 1; + } + + } + if (test_passed) { + msg_info("PASS test %ld", (long) (tp - testcases)); + pass++; + } else { + msg_info("FAIL test %ld", (long) (tp - testcases)); + fail++; + } + if (fp) + vstream_fclose(fp); + } + if (act_err) + vstring_free(act_err); + vstring_free(act_cont); + msg_info("PASS=%d FAIL=%d", pass, fail); + return (fail > 0); +} + +#endif /* TEST */ |