1 /* $NetBSD: dict_stream.c,v 1.2 2022/10/08 16:12:50 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* dict_stream 3
6 /* SUMMARY
7 /*
8 /* SYNOPSIS
9 /* #include <dict.h>
10 /*
11 /* VSTREAM *dict_stream_open(
12 /* const char *dict_type,
13 /* const char *mapname,
14 /* int open_flags,
15 /* int dict_flags,
16 /* struct stat * st,
17 /* VSTRING **why)
18 /* DESCRIPTION
19 /* dict_stream_open() opens a dictionary, which can be specified
20 /* as a file name, or as inline text enclosed with {}. If successful,
21 /* dict_stream_open() returns a non-null VSTREAM pointer. Otherwise,
22 /* it returns an error text through the why argument, allocating
23 /* storage for the error text if the why argument points to a
24 /* null pointer.
25 /*
26 /* When the dictionary file is specified inline, dict_stream_open()
27 /* removes the outer {} from the mapname value, and removes leading
28 /* or trailing comma or whitespace from the result. It then expects
29 /* to find zero or more rules enclosed in {}, separated by comma
30 /* and/or whitespace. dict_stream() writes each rule as one text
31 /* line to an in-memory stream, without its enclosing {} and without
32 /* leading or trailing whitespace. The result value is a VSTREAM
33 /* pointer for the in-memory stream that can be read as a regular
34 /* file.
35 /* .sp
36 /* inline-file = "{" 0*(0*wsp-comma rule-spec) 0*wsp-comma "}"
37 /* .sp
38 /* rule-spec = "{" 0*wsp rule-text 0*wsp "}"
39 /* .sp
40 /* rule-text = any text containing zero or more balanced {}
41 /* .sp
42 /* wsp-comma = wsp | ","
43 /* .sp
44 /* wsp = whitespace
45 /*
46 /* Arguments:
47 /* .IP dict_type
48 /* .IP open_flags
49 /* .IP dict_flags
50 /* The same as with dict_open(3).
51 /* .IP mapname
52 /* Pathname of a file with dictionary content, or inline dictionary
53 /* content as specified above.
54 /* .IP st
55 /* File metadata with the file owner, or fake metadata with the
56 /* real UID and GID of the dict_stream_open() caller. This is
57 /* used for "taint" tracking (zero=trusted, non-zero=untrusted).
58 /* IP why
59 /* Pointer to pointer to error message storage. dict_stream_open()
60 /* updates this storage when reporting an error, and allocates
61 /* memory if why points to a null pointer.
62 /* LICENSE
63 /* .ad
64 /* .fi
65 /* The Secure Mailer license must be distributed with this software.
66 /* AUTHOR(S)
67 /* Wietse Venema
68 /* Google, Inc.
69 /* 111 8th Avenue
70 /* New York, NY 10011, USA
71 /*--*/
72
73 /*
74 * System library.
75 */
76 #include <sys_defs.h>
77
78 /*
79 * Utility library.
80 */
81 #include <dict.h>
82 #include <msg.h>
83 #include <mymalloc.h>
84 #include <stringops.h>
85 #include <vstring.h>
86
87 #define STR(x) vstring_str(x)
88 #define LEN(x) VSTRING_LEN(x)
89
90 /* dict_inline_to_multiline - convert inline map spec to multiline text */
91
dict_inline_to_multiline(VSTRING * vp,const char * mapname)92 static char *dict_inline_to_multiline(VSTRING *vp, const char *mapname)
93 {
94 char *saved_name = mystrdup(mapname);
95 char *bp = saved_name;
96 char *cp;
97 char *err = 0;
98
99 VSTRING_RESET(vp);
100 /* Strip the {} from the map "name". */
101 err = extpar(&bp, CHARS_BRACE, EXTPAR_FLAG_NONE);
102 /* Extract zero or more rules inside {}. */
103 while (err == 0 && (cp = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0)
104 if ((err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) == 0)
105 /* Write rule to in-memory file. */
106 vstring_sprintf_append(vp, "%s\n", cp);
107 VSTRING_TERMINATE(vp);
108 myfree(saved_name);
109 return (err);
110 }
111
112 /* dict_stream_open - open inline configuration or configuration file */
113
dict_stream_open(const char * dict_type,const char * mapname,int open_flags,int dict_flags,struct stat * st,VSTRING ** why)114 VSTREAM *dict_stream_open(const char *dict_type, const char *mapname,
115 int open_flags, int dict_flags,
116 struct stat * st, VSTRING **why)
117 {
118 VSTRING *inline_buf = 0;
119 VSTREAM *map_fp;
120 char *err = 0;
121
122 #define RETURN_0_WITH_REASON(...) do { \
123 if (*why == 0) \
124 *why = vstring_alloc(100); \
125 vstring_sprintf(*why, __VA_ARGS__); \
126 if (inline_buf != 0) \
127 vstring_free(inline_buf); \
128 if (err != 0) \
129 myfree(err); \
130 return (0); \
131 } while (0)
132
133 if (mapname[0] == CHARS_BRACE[0]) {
134 inline_buf = vstring_alloc(100);
135 if ((err = dict_inline_to_multiline(inline_buf, mapname)) != 0)
136 RETURN_0_WITH_REASON("%s map: %s", dict_type, err);
137 map_fp = vstream_memopen(inline_buf, O_RDONLY);
138 vstream_control(map_fp, VSTREAM_CTL_OWN_VSTRING, VSTREAM_CTL_END);
139 st->st_uid = getuid(); /* geteuid()? */
140 st->st_gid = getgid(); /* getegid()? */
141 return (map_fp);
142 } else {
143 if ((map_fp = vstream_fopen(mapname, open_flags, 0)) == 0)
144 RETURN_0_WITH_REASON("open %s: %m", mapname);
145 if (fstat(vstream_fileno(map_fp), st) < 0)
146 msg_fatal("fstat %s: %m", mapname);
147 return (map_fp);
148 }
149 }
150
151 #ifdef TEST
152
153 #include <string.h>
154
main(int argc,char ** argv)155 int main(int argc, char **argv)
156 {
157 struct testcase {
158 const char *title;
159 const char *mapname; /* starts with brace */
160 const char *expect_err; /* null or message */
161 const char *expect_cont; /* null or content */
162 };
163
164 #define EXP_NOERR 0
165 #define EXP_NOCONT 0
166
167 #define STRING_OR(s, text_if_null) ((s) ? (s) : (text_if_null))
168 #define DICT_TYPE_TEST "test"
169
170 const char rule_spec_error[] = DICT_TYPE_TEST " map: "
171 "syntax error after '}' in \"{blah blah}x\"";
172 const char inline_config_error[] = DICT_TYPE_TEST " map: "
173 "syntax error after '}' in \"{{foo bar}, {blah blah}}x\"";
174 struct testcase testcases[] = {
175 {"normal",
176 "{{foo bar}, {blah blah}}", EXP_NOERR, "foo bar\nblah blah\n"
177 },
178 {"trims leading/trailing wsp around rule-text",
179 "{{ foo bar }, { blah blah }}", EXP_NOERR, "foo bar\nblah blah\n"
180 },
181 {"trims leading/trailing comma-wsp around rule-spec",
182 "{, ,{foo bar}, {blah blah}, ,}", EXP_NOERR, "foo bar\nblah blah\n"
183 },
184 {"empty inline-file",
185 "{, }", EXP_NOERR, ""
186 },
187 {"propagates extpar error for inline-file",
188 "{{foo bar}, {blah blah}}x", inline_config_error, EXP_NOCONT
189 },
190 {"propagates extpar error for rule-spec",
191 "{{foo bar}, {blah blah}x}", rule_spec_error, EXP_NOCONT
192 },
193 0,
194 };
195 struct testcase *tp;
196 VSTRING *act_err = 0;
197 VSTRING *act_cont = vstring_alloc(100);
198 VSTREAM *fp;
199 struct stat st;
200 ssize_t exp_len;
201 ssize_t act_len;
202 int pass;
203 int fail;
204
205 for (pass = fail = 0, tp = testcases; tp->title; tp++) {
206 int test_passed = 0;
207
208 msg_info("RUN test case %ld %s", (long) (tp - testcases), tp->title);
209
210 #if 0
211 msg_info("title=%s", tp->title);
212 msg_info("mapname=%s", tp->mapname);
213 msg_info("expect_err=%s", STRING_OR_NULL(tp->expect_err));
214 msg_info("expect_cont=%s", STRINGOR_NULL(tp->expect_cont));
215 #endif
216
217 if (act_err)
218 VSTRING_RESET(act_err);
219 fp = dict_stream_open(DICT_TYPE_TEST, tp->mapname, O_RDONLY,
220 0, &st, &act_err);
221 if (fp) {
222 if (tp->expect_err) {
223 msg_warn("test case %s: got stream, expected error", tp->title);
224 } else if (!tp->expect_err && act_err && LEN(act_err) > 0) {
225 msg_warn("test case %s: got error '%s', expected noerror",
226 tp->title, STR(act_err));
227 } else if (!tp->expect_cont) {
228 msg_warn("test case %s: got stream, expected nostream",
229 tp->title);
230 } else {
231 exp_len = strlen(tp->expect_cont);
232 if ((act_len = vstream_fread_buf(fp, act_cont, 2 * exp_len)) < 0) {
233 msg_warn("test case %s: content read error", tp->title);
234 } else {
235 VSTRING_TERMINATE(act_cont);
236 if (strcmp(tp->expect_cont, STR(act_cont)) != 0) {
237 msg_warn("test case %s: got content '%s', expected '%s'",
238 tp->title, STR(act_cont), tp->expect_cont);
239 } else {
240 test_passed = 1;
241 }
242 }
243 }
244 } else {
245 if (!tp->expect_err) {
246 msg_warn("test case %s: got nostream, expected noerror",
247 tp->title);
248 } else if (tp->expect_cont) {
249 msg_warn("test case %s: got nostream, expected stream",
250 tp->title);
251 } else if (strcmp(STR(act_err), tp->expect_err) != 0) {
252 msg_warn("test case %s: got error '%s', expected '%s'",
253 tp->title, STR(act_err), tp->expect_err);
254 } else {
255 test_passed = 1;
256 }
257
258 }
259 if (test_passed) {
260 msg_info("PASS test %ld", (long) (tp - testcases));
261 pass++;
262 } else {
263 msg_info("FAIL test %ld", (long) (tp - testcases));
264 fail++;
265 }
266 if (fp)
267 vstream_fclose(fp);
268 }
269 if (act_err)
270 vstring_free(act_err);
271 vstring_free(act_cont);
272 msg_info("PASS=%d FAIL=%d", pass, fail);
273 return (fail > 0);
274 }
275
276 #endif /* TEST */
277