1 /* $NetBSD: attr_scan0.c,v 1.3 2022/10/08 16:12:50 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* attr_scan0 3
6 /* SUMMARY
7 /* recover attributes from byte stream
8 /* SYNOPSIS
9 /* #include <attr.h>
10 /*
11 /* int attr_scan0(fp, flags, type, name, ..., ATTR_TYPE_END)
12 /* VSTREAM *fp;
13 /* int flags;
14 /* int type;
15 /* char *name;
16 /*
17 /* int attr_vscan0(fp, flags, ap)
18 /* VSTREAM *fp;
19 /* int flags;
20 /* va_list ap;
21 /*
22 /* int attr_scan_more0(fp)
23 /* VSTREAM *fp;
24 /* DESCRIPTION
25 /* attr_scan0() takes zero or more (name, value) request attributes
26 /* and recovers the attribute values from the byte stream that was
27 /* possibly generated by attr_print0().
28 /*
29 /* attr_vscan0() provides an alternative interface that is convenient
30 /* for calling from within a variadic function.
31 /*
32 /* attr_scan_more0() returns 0 when a terminator is found (and
33 /* consumes that terminator), returns 1 when more input is
34 /* expected (without consuming input), and returns -1 otherwise
35 /* (error).
36 /*
37 /* The input stream is formatted as follows, where (item)* stands
38 /* for zero or more instances of the specified item, and where
39 /* (item1 | item2) stands for choice:
40 /*
41 /* .in +5
42 /* attr-list :== (simple-attr | multi-attr)* null
43 /* .br
44 /* multi-attr :== "{" null simple-attr* "}" null
45 /* .br
46 /* simple-attr :== attr-name null attr-value null
47 /* .br
48 /* attr-name :== any string not containing null
49 /* .br
50 /* attr-value :== any string not containing null
51 /* .br
52 /* null :== the ASCII null character
53 /* .in
54 /*
55 /* All attribute names and attribute values are sent as null terminated
56 /* strings. Each string must be no longer than 4*var_line_limit
57 /* characters including the terminator.
58 /* These formatting rules favor implementations in C.
59 /*
60 /* Normally, attributes must be received in the sequence as specified with
61 /* the attr_scan0() argument list. The input stream may contain additional
62 /* attributes at any point in the input stream, including additional
63 /* instances of requested attributes.
64 /*
65 /* Additional input attributes or input attribute instances are silently
66 /* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified
67 /* (see below). This allows for some flexibility in the evolution of
68 /* protocols while still providing the option of being strict where
69 /* this is desirable.
70 /*
71 /* Arguments:
72 /* .IP fp
73 /* Stream to recover the input attributes from.
74 /* .IP flags
75 /* The bit-wise OR of zero or more of the following.
76 /* .RS
77 /* .IP ATTR_FLAG_MISSING
78 /* Log a warning when the input attribute list terminates before all
79 /* requested attributes are recovered. It is always an error when the
80 /* input stream ends without the newline attribute list terminator.
81 /* .IP ATTR_FLAG_EXTRA
82 /* Log a warning and stop attribute recovery when the input stream
83 /* contains an attribute that was not requested. This includes the
84 /* case of additional instances of a requested attribute.
85 /* .IP ATTR_FLAG_MORE
86 /* After recovering the requested attributes, leave the input stream
87 /* in a state that is usable for more attr_scan0() operations from the
88 /* same input attribute list.
89 /* By default, attr_scan0() skips forward past the input attribute list
90 /* terminator.
91 /* .IP ATTR_FLAG_PRINTABLE
92 /* Santize received string values with printable(_, '?').
93 /* .IP ATTR_FLAG_STRICT
94 /* For convenience, this value combines both ATTR_FLAG_MISSING and
95 /* ATTR_FLAG_EXTRA.
96 /* .IP ATTR_FLAG_NONE
97 /* For convenience, this value requests none of the above.
98 /* .RE
99 /* .IP List of attributes followed by terminator:
100 /* .RS
101 /* .IP "RECV_ATTR_INT(const char *name, int *ptr)"
102 /* This argument is followed by an attribute name and an integer pointer.
103 /* .IP "RECV_ATTR_LONG(const char *name, long *ptr)"
104 /* This argument is followed by an attribute name and a long pointer.
105 /* .IP "RECV_ATTR_STR(const char *name, VSTRING *vp)"
106 /* This argument is followed by an attribute name and a VSTRING pointer.
107 /* .IP "RECV_ATTR_STREQ(const char *name, const char *value)"
108 /* The name and value must match what the client sends.
109 /* This attribute does not increment the result value.
110 /* .IP "RECV_ATTR_DATA(const char *name, VSTRING *vp)"
111 /* This argument is followed by an attribute name and a VSTRING pointer.
112 /* .IP "RECV_ATTR_FUNC(ATTR_SCAN_CUSTOM_FN, void *data)"
113 /* This argument is followed by a function pointer and a generic data
114 /* pointer. The caller-specified function returns < 0 in case of
115 /* error.
116 /* .IP "RECV_ATTR_HASH(HTABLE *table)"
117 /* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)"
118 /* Receive a sequence of attribute names and string values.
119 /* There can be no more than 1024 attributes in a hash table.
120 /* .sp
121 /* The attribute string values are stored in the hash table under
122 /* keys equal to the attribute name (obtained from the input stream).
123 /* Values from the input stream are added to the hash table. Existing
124 /* hash table entries are not replaced.
125 /* .sp
126 /* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests
127 /* format their payload as a multi-attr sequence (see syntax
128 /* above). When the receiver's input does not start with a
129 /* multi-attr delimiter (i.e. the sender did not request
130 /* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will
131 /* store all attribute names and values up to the attribute
132 /* list terminator. In terms of code, this means that the
133 /* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed
134 /* by ATTR_TYPE_END.
135 /* .IP ATTR_TYPE_END
136 /* This argument terminates the requested attribute list.
137 /* .RE
138 /* BUGS
139 /* RECV_ATTR_HASH (RECV_ATTR_NAMEVAL) accepts attributes with arbitrary
140 /* names from possibly untrusted sources.
141 /* This is unsafe, unless the resulting table is queried only with
142 /* known to be good attribute names.
143 /* DIAGNOSTICS
144 /* attr_scan0() and attr_vscan0() return -1 when malformed input is
145 /* detected (string too long, incomplete line, missing end marker).
146 /* Otherwise, the result value is the number of attributes that were
147 /* successfully recovered from the input stream (a hash table counts
148 /* as the number of entries stored into the table).
149 /*
150 /* Panic: interface violation. All system call errors are fatal.
151 /* SEE ALSO
152 /* attr_print0(3) send attributes over byte stream.
153 /* LICENSE
154 /* .ad
155 /* .fi
156 /* The Secure Mailer license must be distributed with this software.
157 /* AUTHOR(S)
158 /* Wietse Venema
159 /* IBM T.J. Watson Research
160 /* P.O. Box 704
161 /* Yorktown Heights, NY 10598, USA
162 /*
163 /* Wietse Venema
164 /* Google, Inc.
165 /* 111 8th Avenue
166 /* New York, NY 10011, USA
167 /*--*/
168
169 /* System library. */
170
171 #include <sys_defs.h>
172 #include <stdarg.h>
173 #include <string.h>
174 #include <stdio.h>
175
176 /* Utility library. */
177
178 #include <msg.h>
179 #include <mymalloc.h>
180 #include <vstream.h>
181 #include <vstring.h>
182 #include <vstring_vstream.h>
183 #include <htable.h>
184 #include <base64_code.h>
185 #include <stringops.h>
186 #include <attr.h>
187
188 /* Application specific. */
189
190 #define STR(x) vstring_str(x)
191 #define LEN(x) VSTRING_LEN(x)
192
193 /* attr_scan0_string - pull a string from the input stream */
194
attr_scan0_string(VSTREAM * fp,VSTRING * plain_buf,const char * context)195 static int attr_scan0_string(VSTREAM *fp, VSTRING *plain_buf, const char *context)
196 {
197 int ch;
198
199 if ((ch = vstring_get_null(plain_buf, fp)) == VSTREAM_EOF) {
200 msg_warn("%s on %s while reading %s",
201 vstream_ftimeout(fp) ? "timeout" : "premature end-of-input",
202 VSTREAM_PATH(fp), context);
203 return (-1);
204 }
205 if (ch != 0) {
206 msg_warn("unexpected end-of-input from %s while reading %s",
207 VSTREAM_PATH(fp), context);
208 return (-1);
209 }
210 if (msg_verbose)
211 msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)");
212 return (ch);
213 }
214
215 /* attr_scan0_data - pull a data blob from the input stream */
216
attr_scan0_data(VSTREAM * fp,VSTRING * str_buf,const char * context)217 static int attr_scan0_data(VSTREAM *fp, VSTRING *str_buf,
218 const char *context)
219 {
220 static VSTRING *base64_buf = 0;
221 int ch;
222
223 if (base64_buf == 0)
224 base64_buf = vstring_alloc(10);
225 if ((ch = attr_scan0_string(fp, base64_buf, context)) < 0)
226 return (-1);
227 if (base64_decode(str_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
228 msg_warn("malformed base64 data from %s while reading %s: %.100s",
229 VSTREAM_PATH(fp), context, STR(base64_buf));
230 return (-1);
231 }
232 return (ch);
233 }
234
235 /* attr_scan0_number - pull a number from the input stream */
236
attr_scan0_number(VSTREAM * fp,unsigned * ptr,VSTRING * str_buf,const char * context)237 static int attr_scan0_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf,
238 const char *context)
239 {
240 char junk = 0;
241 int ch;
242
243 if ((ch = attr_scan0_string(fp, str_buf, context)) < 0)
244 return (-1);
245 if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) {
246 msg_warn("malformed numerical data from %s while reading %s: %.100s",
247 VSTREAM_PATH(fp), context, STR(str_buf));
248 return (-1);
249 }
250 return (ch);
251 }
252
253 /* attr_scan0_long_number - pull a number from the input stream */
254
attr_scan0_long_number(VSTREAM * fp,unsigned long * ptr,VSTRING * str_buf,const char * context)255 static int attr_scan0_long_number(VSTREAM *fp, unsigned long *ptr,
256 VSTRING *str_buf,
257 const char *context)
258 {
259 char junk = 0;
260 int ch;
261
262 if ((ch = attr_scan0_string(fp, str_buf, context)) < 0)
263 return (-1);
264 if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) {
265 msg_warn("malformed numerical data from %s while reading %s: %.100s",
266 VSTREAM_PATH(fp), context, STR(str_buf));
267 return (-1);
268 }
269 return (ch);
270 }
271
272 /* attr_vscan0 - receive attribute list from stream */
273
attr_vscan0(VSTREAM * fp,int flags,va_list ap)274 int attr_vscan0(VSTREAM *fp, int flags, va_list ap)
275 {
276 const char *myname = "attr_scan0";
277 static VSTRING *str_buf = 0;
278 static VSTRING *name_buf = 0;
279 int wanted_type = -1;
280 char *wanted_name;
281 unsigned int *number;
282 unsigned long *long_number;
283 VSTRING *string;
284 HTABLE *hash_table;
285 int ch;
286 int conversions;
287 ATTR_SCAN_CUSTOM_FN scan_fn;
288 void *scan_arg;
289 const char *expect_val;
290
291 /*
292 * Sanity check.
293 */
294 if (flags & ~ATTR_FLAG_ALL)
295 msg_panic("%s: bad flags: 0x%x", myname, flags);
296
297 /*
298 * EOF check.
299 */
300 if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF)
301 return (0);
302 vstream_ungetc(fp, ch);
303
304 /*
305 * Initialize.
306 */
307 if (str_buf == 0) {
308 str_buf = vstring_alloc(10);
309 name_buf = vstring_alloc(10);
310 }
311
312 /*
313 * Iterate over all (type, name, value) triples.
314 */
315 for (conversions = 0; /* void */ ; conversions++) {
316
317 /*
318 * Determine the next attribute type and attribute name on the
319 * caller's wish list.
320 *
321 * If we're reading into a hash table, we already know that the
322 * attribute value is string-valued, and we get the attribute name
323 * from the input stream instead. This is secure only when the
324 * resulting table is queried with known to be good attribute names.
325 */
326 if (wanted_type != ATTR_TYPE_HASH
327 && wanted_type != ATTR_TYPE_CLOSE) {
328 wanted_type = va_arg(ap, int);
329 if (wanted_type == ATTR_TYPE_END) {
330 if ((flags & ATTR_FLAG_MORE) != 0)
331 return (conversions);
332 wanted_name = "(list terminator)";
333 } else if (wanted_type == ATTR_TYPE_HASH) {
334 wanted_name = "(any attribute name or list terminator)";
335 hash_table = va_arg(ap, HTABLE *);
336 } else if (wanted_type != ATTR_TYPE_FUNC) {
337 wanted_name = va_arg(ap, char *);
338 }
339 }
340
341 /*
342 * Locate the next attribute of interest in the input stream.
343 */
344 while (wanted_type != ATTR_TYPE_FUNC) {
345
346 /*
347 * Get the name of the next attribute. Hitting EOF is always bad.
348 * Hitting the end-of-input early is OK if the caller is prepared
349 * to deal with missing inputs.
350 */
351 if (msg_verbose)
352 msg_info("%s: wanted attribute: %s",
353 VSTREAM_PATH(fp), wanted_name);
354 if ((ch = attr_scan0_string(fp, name_buf,
355 "input attribute name")) == VSTREAM_EOF)
356 return (-1);
357 if (LEN(name_buf) == 0) {
358 if (wanted_type == ATTR_TYPE_END
359 || wanted_type == ATTR_TYPE_HASH)
360 return (conversions);
361 if ((flags & ATTR_FLAG_MISSING) != 0)
362 msg_warn("missing attribute %s in input from %s",
363 wanted_name, VSTREAM_PATH(fp));
364 return (conversions);
365 }
366
367 /*
368 * See if the caller asks for this attribute.
369 */
370 if (wanted_type == ATTR_TYPE_HASH
371 && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) {
372 wanted_type = ATTR_TYPE_CLOSE;
373 wanted_name = "(any attribute name or '}')";
374 /* Advance in the input stream. */
375 continue;
376 } else if (wanted_type == ATTR_TYPE_CLOSE
377 && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) {
378 /* Advance in the argument list. */
379 wanted_type = -1;
380 break;
381 }
382 if (wanted_type == ATTR_TYPE_HASH
383 || wanted_type == ATTR_TYPE_CLOSE
384 || (wanted_type != ATTR_TYPE_END
385 && strcmp(wanted_name, STR(name_buf)) == 0))
386 break;
387 if ((flags & ATTR_FLAG_EXTRA) != 0) {
388 msg_warn("unexpected attribute %s from %s (expecting: %s)",
389 STR(name_buf), VSTREAM_PATH(fp), wanted_name);
390 return (conversions);
391 }
392
393 /*
394 * Skip over this attribute. The caller does not ask for it.
395 */
396 (void) attr_scan0_string(fp, str_buf, "input attribute value");
397 }
398
399 /*
400 * Do the requested conversion.
401 */
402 switch (wanted_type) {
403 case ATTR_TYPE_INT:
404 number = va_arg(ap, unsigned int *);
405 if ((ch = attr_scan0_number(fp, number, str_buf,
406 "input attribute value")) < 0)
407 return (-1);
408 break;
409 case ATTR_TYPE_LONG:
410 long_number = va_arg(ap, unsigned long *);
411 if ((ch = attr_scan0_long_number(fp, long_number, str_buf,
412 "input attribute value")) < 0)
413 return (-1);
414 break;
415 case ATTR_TYPE_STR:
416 string = va_arg(ap, VSTRING *);
417 if ((ch = attr_scan0_string(fp, string,
418 "input attribute value")) < 0)
419 return (-1);
420 if (flags & ATTR_FLAG_PRINTABLE)
421 (void) printable(STR(string), '?');
422 break;
423 case ATTR_TYPE_DATA:
424 string = va_arg(ap, VSTRING *);
425 if ((ch = attr_scan0_data(fp, string,
426 "input attribute value")) < 0)
427 return (-1);
428 break;
429 case ATTR_TYPE_FUNC:
430 scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN);
431 scan_arg = va_arg(ap, void *);
432 if (scan_fn(attr_scan0, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0)
433 return (-1);
434 break;
435 case ATTR_TYPE_STREQ:
436 expect_val = va_arg(ap, const char *);
437 if ((ch = attr_scan0_string(fp, str_buf,
438 "input attribute value")) < 0)
439 return (-1);
440 if (strcmp(expect_val, STR(str_buf)) != 0) {
441 msg_warn("unexpected %s %s from %s (expected: %s)",
442 STR(name_buf), STR(str_buf), VSTREAM_PATH(fp),
443 expect_val);
444 return (-1);
445 }
446 conversions -= 1;
447 break;
448 case ATTR_TYPE_HASH:
449 case ATTR_TYPE_CLOSE:
450 if ((ch = attr_scan0_string(fp, str_buf,
451 "input attribute value")) < 0)
452 return (-1);
453 if (flags & ATTR_FLAG_PRINTABLE) {
454 (void) printable(STR(name_buf), '?');
455 (void) printable(STR(str_buf), '?');
456 }
457 if (htable_locate(hash_table, STR(name_buf)) != 0) {
458 if ((flags & ATTR_FLAG_EXTRA) != 0) {
459 msg_warn("duplicate attribute %s in input from %s",
460 STR(name_buf), VSTREAM_PATH(fp));
461 return (conversions);
462 }
463 } else if (hash_table->used >= ATTR_HASH_LIMIT) {
464 msg_warn("attribute count exceeds limit %d in input from %s",
465 ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
466 return (conversions);
467 } else {
468 htable_enter(hash_table, STR(name_buf),
469 mystrdup(STR(str_buf)));
470 }
471 break;
472 case -1:
473 conversions -= 1;
474 break;
475 default:
476 msg_panic("%s: unknown type code: %d", myname, wanted_type);
477 }
478 }
479 }
480
481 /* attr_scan0 - read attribute list from stream */
482
attr_scan0(VSTREAM * fp,int flags,...)483 int attr_scan0(VSTREAM *fp, int flags,...)
484 {
485 va_list ap;
486 int ret;
487
488 va_start(ap, flags);
489 ret = attr_vscan0(fp, flags, ap);
490 va_end(ap);
491 return (ret);
492 }
493
494 /* attr_scan_more0 - look ahead for more */
495
attr_scan_more0(VSTREAM * fp)496 int attr_scan_more0(VSTREAM *fp)
497 {
498 int ch;
499
500 switch (ch = VSTREAM_GETC(fp)) {
501 case 0:
502 if (msg_verbose)
503 msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp));
504 return (0);
505 case VSTREAM_EOF:
506 if (msg_verbose)
507 msg_info("%s: EOF", VSTREAM_PATH(fp));
508 return (-1);
509 default:
510 if (msg_verbose)
511 msg_info("%s: non-terminator '%c' (lookahead)",
512 VSTREAM_PATH(fp), ch);
513 (void) vstream_ungetc(fp, ch);
514 return (1);
515 }
516 }
517
518 #ifdef TEST
519
520 /*
521 * Proof of concept test program. Mirror image of the attr_scan0 test
522 * program.
523 */
524 #include <msg_vstream.h>
525
526 int var_line_limit = 2048;
527
main(int unused_argc,char ** used_argv)528 int main(int unused_argc, char **used_argv)
529 {
530 VSTRING *data_val = vstring_alloc(1);
531 VSTRING *str_val = vstring_alloc(1);
532 HTABLE *table = htable_create(1);
533 HTABLE_INFO **ht_info_list;
534 HTABLE_INFO **ht;
535 int int_val;
536 long long_val;
537 long long_val2;
538 int ret;
539
540 msg_verbose = 1;
541 msg_vstream_init(used_argv[0], VSTREAM_ERR);
542 if ((ret = attr_scan0(VSTREAM_IN,
543 ATTR_FLAG_STRICT,
544 RECV_ATTR_STREQ("protocol", "test"),
545 RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
546 RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
547 RECV_ATTR_STR(ATTR_NAME_STR, str_val),
548 RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
549 RECV_ATTR_HASH(table),
550 RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2),
551 ATTR_TYPE_END)) > 4) {
552 vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
553 vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
554 vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
555 vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(str_val));
556 ht_info_list = htable_list(table);
557 for (ht = ht_info_list; *ht; ht++)
558 vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
559 myfree((void *) ht_info_list);
560 vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2);
561 } else {
562 vstream_printf("return: %d\n", ret);
563 }
564 if ((ret = attr_scan0(VSTREAM_IN,
565 ATTR_FLAG_STRICT,
566 RECV_ATTR_STREQ("protocol", "test"),
567 RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
568 RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
569 RECV_ATTR_STR(ATTR_NAME_STR, str_val),
570 RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
571 ATTR_TYPE_END)) == 4) {
572 vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
573 vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
574 vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
575 vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
576 ht_info_list = htable_list(table);
577 for (ht = ht_info_list; *ht; ht++)
578 vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
579 myfree((void *) ht_info_list);
580 } else {
581 vstream_printf("return: %d\n", ret);
582 }
583 if ((ret = attr_scan0(VSTREAM_IN,
584 ATTR_FLAG_STRICT,
585 RECV_ATTR_STREQ("protocol", "test"),
586 ATTR_TYPE_END)) != 0)
587 vstream_printf("return: %d\n", ret);
588 if (vstream_fflush(VSTREAM_OUT) != 0)
589 msg_fatal("write error: %m");
590
591 vstring_free(data_val);
592 vstring_free(str_val);
593 htable_free(table, myfree);
594
595 return (0);
596 }
597
598 #endif
599