/*++ /* NAME /* dict_stream 3 /* SUMMARY /* /* SYNOPSIS /* #include /* /* 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 /* * Utility library. */ #include #include #include #include #include #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 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 */