summaryrefslogtreecommitdiffstats
path: root/src/util/dict_stream.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/dict_stream.c')
-rw-r--r--src/util/dict_stream.c274
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 */