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 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 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 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