xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/dict_stream.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
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