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