1 /*++
2 /* NAME
3 /*	attr_scan64 3
4 /* SUMMARY
5 /*	recover attributes from byte stream
6 /* SYNOPSIS
7 /*	#include <attr.h>
8 /*
9 /*	int	attr_scan64(fp, flags, type, name, ..., ATTR_TYPE_END)
10 /*	VSTREAM	*fp;
11 /*	int	flags;
12 /*	int	type;
13 /*	char	*name;
14 /*
15 /*	int	attr_vscan64(fp, flags, ap)
16 /*	VSTREAM	*fp;
17 /*	int	flags;
18 /*	va_list	ap;
19 /*
20 /*	int	attr_scan_more64(fp)
21 /*	VSTREAM	*fp;
22 /* DESCRIPTION
23 /*	attr_scan64() 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_print64().
26 /*
27 /*	attr_vscan64() provides an alternative interface that is convenient
28 /*	for calling from within a variadic function.
29 /*
30 /*	attr_scan_more64() returns 0 when a terminator is found
31 /*	(and consumes that terminator), returns 1 when more input
32 /*	is expected (without consuming input), and returns -1
33 /*	otherwise (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)* newline
41 /* .br
42 /*	multi-attr :== "{" newline simple-attr* "}" newline
43 /* .br
44 /*	simple-attr :== attr-name colon attr-value newline
45 /* .br
46 /*	attr-name :== any base64 encoded string
47 /* .br
48 /*	attr-value :== any base64 encoded string
49 /* .br
50 /*	colon :== the ASCII colon character
51 /* .br
52 /*	newline :== the ASCII newline character
53 /* .in
54 /*
55 /*	All attribute names and attribute values are sent as base64-encoded
56 /*	strings. Each base64 encoding must be no longer than 4*var_line_limit
57 /*	characters. The formatting rules aim to make implementations in PERL
58 /*	and other languages easy.
59 /*
60 /*	Normally, attributes must be received in the sequence as specified with
61 /*	the attr_scan64() 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_scan64() operations from the
88 /*	same input attribute list.
89 /*	By default, attr_scan64() 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_scan64() and attr_vscan64() 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_print64(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 <htable.h>
183 #include <base64_code.h>
184 #include <stringops.h>
185 #include <attr.h>
186 
187 /* Application specific. */
188 
189 #define STR(x)	vstring_str(x)
190 #define LEN(x)	VSTRING_LEN(x)
191 
192 /* attr_scan64_string - pull a string from the input stream */
193 
attr_scan64_string(VSTREAM * fp,VSTRING * plain_buf,const char * context)194 static int attr_scan64_string(VSTREAM *fp, VSTRING *plain_buf, const char *context)
195 {
196     static VSTRING *base64_buf = 0;
197 
198 #if 0
199     extern int var_line_limit;		/* XXX */
200     int     limit = var_line_limit * 4;
201 
202 #endif
203     int     ch;
204 
205     if (base64_buf == 0)
206 	base64_buf = vstring_alloc(10);
207 
208     VSTRING_RESET(base64_buf);
209     while ((ch = VSTREAM_GETC(fp)) != ':' && ch != '\n') {
210 	if (ch == VSTREAM_EOF) {
211 	    msg_warn("%s on %s while reading %s",
212 		vstream_ftimeout(fp) ? "timeout" : "premature end-of-input",
213 		     VSTREAM_PATH(fp), context);
214 	    return (-1);
215 	}
216 	VSTRING_ADDCH(base64_buf, ch);
217 #if 0
218 	if (LEN(base64_buf) > limit) {
219 	    msg_warn("string length > %d characters from %s while reading %s",
220 		     limit, VSTREAM_PATH(fp), context);
221 	    return (-1);
222 	}
223 #endif
224     }
225     VSTRING_TERMINATE(base64_buf);
226     if (base64_decode(plain_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
227 	msg_warn("malformed base64 data from %s: %.100s",
228 		 VSTREAM_PATH(fp), STR(base64_buf));
229 	return (-1);
230     }
231     if (msg_verbose)
232 	msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)");
233     return (ch);
234 }
235 
236 /* attr_scan64_number - pull a number from the input stream */
237 
attr_scan64_number(VSTREAM * fp,unsigned * ptr,VSTRING * str_buf,const char * context)238 static int attr_scan64_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf,
239 			              const char *context)
240 {
241     char    junk = 0;
242     int     ch;
243 
244     if ((ch = attr_scan64_string(fp, str_buf, context)) < 0)
245 	return (-1);
246     if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) {
247 	msg_warn("malformed numerical data from %s while reading %s: %.100s",
248 		 VSTREAM_PATH(fp), context, STR(str_buf));
249 	return (-1);
250     }
251     return (ch);
252 }
253 
254 /* attr_scan64_long_number - pull a number from the input stream */
255 
attr_scan64_long_number(VSTREAM * fp,unsigned long * ptr,VSTRING * str_buf,const char * context)256 static int attr_scan64_long_number(VSTREAM *fp, unsigned long *ptr,
257 				           VSTRING *str_buf,
258 				           const char *context)
259 {
260     char    junk = 0;
261     int     ch;
262 
263     if ((ch = attr_scan64_string(fp, str_buf, context)) < 0)
264 	return (-1);
265     if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) {
266 	msg_warn("malformed numerical data from %s while reading %s: %.100s",
267 		 VSTREAM_PATH(fp), context, STR(str_buf));
268 	return (-1);
269     }
270     return (ch);
271 }
272 
273 /* attr_vscan64 - receive attribute list from stream */
274 
attr_vscan64(VSTREAM * fp,int flags,va_list ap)275 int     attr_vscan64(VSTREAM *fp, int flags, va_list ap)
276 {
277     const char *myname = "attr_scan64";
278     static VSTRING *str_buf = 0;
279     static VSTRING *name_buf = 0;
280     int     wanted_type = -1;
281     char   *wanted_name;
282     unsigned int *number;
283     unsigned long *long_number;
284     VSTRING *string;
285     HTABLE *hash_table;
286     int     ch;
287     int     conversions;
288     ATTR_SCAN_CUSTOM_FN scan_fn;
289     void   *scan_arg;
290     const char *expect_val;
291 
292     /*
293      * Sanity check.
294      */
295     if (flags & ~ATTR_FLAG_ALL)
296 	msg_panic("%s: bad flags: 0x%x", myname, flags);
297 
298     /*
299      * EOF check.
300      */
301     if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF)
302 	return (0);
303     vstream_ungetc(fp, ch);
304 
305     /*
306      * Initialize.
307      */
308     if (str_buf == 0) {
309 	str_buf = vstring_alloc(10);
310 	name_buf = vstring_alloc(10);
311     }
312 
313     /*
314      * Iterate over all (type, name, value) triples.
315      */
316     for (conversions = 0; /* void */ ; conversions++) {
317 
318 	/*
319 	 * Determine the next attribute type and attribute name on the
320 	 * caller's wish list.
321 	 *
322 	 * If we're reading into a hash table, we already know that the
323 	 * attribute value is string-valued, and we get the attribute name
324 	 * from the input stream instead. This is secure only when the
325 	 * resulting table is queried with known to be good attribute names.
326 	 */
327 	if (wanted_type != ATTR_TYPE_HASH
328 	    && wanted_type != ATTR_TYPE_CLOSE) {
329 	    wanted_type = va_arg(ap, int);
330 	    if (wanted_type == ATTR_TYPE_END) {
331 		if ((flags & ATTR_FLAG_MORE) != 0)
332 		    return (conversions);
333 		wanted_name = "(list terminator)";
334 	    } else if (wanted_type == ATTR_TYPE_HASH) {
335 		wanted_name = "(any attribute name or list terminator)";
336 		hash_table = va_arg(ap, HTABLE *);
337 	    } else if (wanted_type != ATTR_TYPE_FUNC) {
338 		wanted_name = va_arg(ap, char *);
339 	    }
340 	}
341 
342 	/*
343 	 * Locate the next attribute of interest in the input stream.
344 	 */
345 	while (wanted_type != ATTR_TYPE_FUNC) {
346 
347 	    /*
348 	     * Get the name of the next attribute. Hitting EOF is always bad.
349 	     * Hitting the end-of-input early is OK if the caller is prepared
350 	     * to deal with missing inputs.
351 	     */
352 	    if (msg_verbose)
353 		msg_info("%s: wanted attribute: %s",
354 			 VSTREAM_PATH(fp), wanted_name);
355 	    if ((ch = attr_scan64_string(fp, name_buf,
356 				    "input attribute name")) == VSTREAM_EOF)
357 		return (-1);
358 	    if (ch == '\n' && LEN(name_buf) == 0) {
359 		if (wanted_type == ATTR_TYPE_END
360 		    || wanted_type == ATTR_TYPE_HASH)
361 		    return (conversions);
362 		if ((flags & ATTR_FLAG_MISSING) != 0)
363 		    msg_warn("missing attribute %s in input from %s",
364 			     wanted_name, VSTREAM_PATH(fp));
365 		return (conversions);
366 	    }
367 
368 	    /*
369 	     * See if the caller asks for this attribute.
370 	     */
371 	    if (wanted_type == ATTR_TYPE_HASH
372 	      && ch == '\n' && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) {
373 		wanted_type = ATTR_TYPE_CLOSE;
374 		wanted_name = "(any attribute name or '}')";
375 		/* Advance in the input stream. */
376 		continue;
377 	    } else if (wanted_type == ATTR_TYPE_CLOSE
378 	     && ch == '\n' && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) {
379 		/* Advance in the argument list. */
380 		wanted_type = -1;
381 		break;
382 	    }
383 	    if (wanted_type == ATTR_TYPE_HASH
384 		|| wanted_type == ATTR_TYPE_CLOSE
385 		|| (wanted_type != ATTR_TYPE_END
386 		    && strcmp(wanted_name, STR(name_buf)) == 0))
387 		break;
388 	    if ((flags & ATTR_FLAG_EXTRA) != 0) {
389 		msg_warn("unexpected attribute %s from %s (expecting: %s)",
390 			 STR(name_buf), VSTREAM_PATH(fp), wanted_name);
391 		return (conversions);
392 	    }
393 
394 	    /*
395 	     * Skip over this attribute. The caller does not ask for it.
396 	     */
397 	    while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF)
398 		 /* void */ ;
399 	}
400 
401 	/*
402 	 * Do the requested conversion. If the target attribute is a
403 	 * non-array type, disallow sending a multi-valued attribute, and
404 	 * disallow sending no value. If the target attribute is an array
405 	 * type, allow the sender to send a zero-element array (i.e. no value
406 	 * at all). XXX Need to impose a bound on the number of array
407 	 * elements.
408 	 */
409 	switch (wanted_type) {
410 	case ATTR_TYPE_INT:
411 	    if (ch != ':') {
412 		msg_warn("missing value for number attribute %s from %s",
413 			 STR(name_buf), VSTREAM_PATH(fp));
414 		return (-1);
415 	    }
416 	    number = va_arg(ap, unsigned int *);
417 	    if ((ch = attr_scan64_number(fp, number, str_buf,
418 					 "input attribute value")) < 0)
419 		return (-1);
420 	    if (ch != '\n') {
421 		msg_warn("multiple values for attribute %s from %s",
422 			 STR(name_buf), VSTREAM_PATH(fp));
423 		return (-1);
424 	    }
425 	    break;
426 	case ATTR_TYPE_LONG:
427 	    if (ch != ':') {
428 		msg_warn("missing value for number attribute %s from %s",
429 			 STR(name_buf), VSTREAM_PATH(fp));
430 		return (-1);
431 	    }
432 	    long_number = va_arg(ap, unsigned long *);
433 	    if ((ch = attr_scan64_long_number(fp, long_number, str_buf,
434 					      "input attribute value")) < 0)
435 		return (-1);
436 	    if (ch != '\n') {
437 		msg_warn("multiple values for attribute %s from %s",
438 			 STR(name_buf), VSTREAM_PATH(fp));
439 		return (-1);
440 	    }
441 	    break;
442 	case ATTR_TYPE_STR:
443 	    if (ch != ':') {
444 		msg_warn("missing value for string attribute %s from %s",
445 			 STR(name_buf), VSTREAM_PATH(fp));
446 		return (-1);
447 	    }
448 	    string = va_arg(ap, VSTRING *);
449 	    if ((ch = attr_scan64_string(fp, string,
450 					 "input attribute value")) < 0)
451 		return (-1);
452 	    if (ch != '\n') {
453 		msg_warn("multiple values for attribute %s from %s",
454 			 STR(name_buf), VSTREAM_PATH(fp));
455 		return (-1);
456 	    }
457 	    if (flags & ATTR_FLAG_PRINTABLE)
458 		(void) printable(STR(string), '?');
459 	    break;
460 	case ATTR_TYPE_DATA:
461 	    if (ch != ':') {
462 		msg_warn("missing value for data attribute %s from %s",
463 			 STR(name_buf), VSTREAM_PATH(fp));
464 		return (-1);
465 	    }
466 	    string = va_arg(ap, VSTRING *);
467 	    if ((ch = attr_scan64_string(fp, string,
468 					 "input attribute value")) < 0)
469 		return (-1);
470 	    if (ch != '\n') {
471 		msg_warn("multiple values for attribute %s from %s",
472 			 STR(name_buf), VSTREAM_PATH(fp));
473 		return (-1);
474 	    }
475 	    break;
476 	case ATTR_TYPE_FUNC:
477 	    scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN);
478 	    scan_arg = va_arg(ap, void *);
479 	    if (scan_fn(attr_scan64, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0)
480 		return (-1);
481 	    break;
482 	case ATTR_TYPE_STREQ:
483 	    if (ch != ':') {
484 		msg_warn("missing value for string attribute %s from %s",
485 			 STR(name_buf), VSTREAM_PATH(fp));
486 		return (-1);
487 	    }
488 	    expect_val = va_arg(ap, const char *);
489 	    if ((ch = attr_scan64_string(fp, str_buf,
490 					 "input attribute value")) < 0)
491 		return (-1);
492 	    if (ch != '\n') {
493 		msg_warn("multiple values for attribute %s from %s",
494 			 STR(name_buf), VSTREAM_PATH(fp));
495 		return (-1);
496 	    }
497 	    if (strcmp(expect_val, STR(str_buf)) != 0) {
498 		msg_warn("unexpected %s %s from %s (expected: %s)",
499 			 STR(name_buf), STR(str_buf), VSTREAM_PATH(fp),
500 			 expect_val);
501 		return (-1);
502 	    }
503 	    conversions -= 1;
504 	    break;
505 	case ATTR_TYPE_HASH:
506 	case ATTR_TYPE_CLOSE:
507 	    if (ch != ':') {
508 		msg_warn("missing value for string attribute %s from %s",
509 			 STR(name_buf), VSTREAM_PATH(fp));
510 		return (-1);
511 	    }
512 	    if ((ch = attr_scan64_string(fp, str_buf,
513 					 "input attribute value")) < 0)
514 		return (-1);
515 	    if (ch != '\n') {
516 		msg_warn("multiple values for attribute %s from %s",
517 			 STR(name_buf), VSTREAM_PATH(fp));
518 		return (-1);
519 	    }
520 	    if (flags & ATTR_FLAG_PRINTABLE) {
521 		(void) printable(STR(name_buf), '?');
522 		(void) printable(STR(str_buf), '?');
523 	    }
524 	    if (htable_locate(hash_table, STR(name_buf)) != 0) {
525 		if ((flags & ATTR_FLAG_EXTRA) != 0) {
526 		    msg_warn("duplicate attribute %s in input from %s",
527 			     STR(name_buf), VSTREAM_PATH(fp));
528 		    return (conversions);
529 		}
530 	    } else if (hash_table->used >= ATTR_HASH_LIMIT) {
531 		msg_warn("attribute count exceeds limit %d in input from %s",
532 			 ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
533 		return (conversions);
534 	    } else {
535 		htable_enter(hash_table, STR(name_buf),
536 			     mystrdup(STR(str_buf)));
537 	    }
538 	    break;
539 	case -1:
540 	    conversions -= 1;
541 	    break;
542 	default:
543 	    msg_panic("%s: unknown type code: %d", myname, wanted_type);
544 	}
545     }
546 }
547 
548 /* attr_scan64 - read attribute list from stream */
549 
attr_scan64(VSTREAM * fp,int flags,...)550 int     attr_scan64(VSTREAM *fp, int flags,...)
551 {
552     va_list ap;
553     int     ret;
554 
555     va_start(ap, flags);
556     ret = attr_vscan64(fp, flags, ap);
557     va_end(ap);
558     return (ret);
559 }
560 
561 /* attr_scan_more64 - look ahead for more */
562 
attr_scan_more64(VSTREAM * fp)563 int     attr_scan_more64(VSTREAM *fp)
564 {
565     int     ch;
566 
567     switch (ch = VSTREAM_GETC(fp)) {
568     case '\n':
569 	if (msg_verbose)
570 	    msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp));
571 	return (0);
572     case VSTREAM_EOF:
573 	if (msg_verbose)
574 	    msg_info("%s: EOF", VSTREAM_PATH(fp));
575 	return (-1);
576     default:
577 	if (msg_verbose)
578 	    msg_info("%s: non-terminator '%c' (lookahead)",
579 		     VSTREAM_PATH(fp), ch);
580 	(void) vstream_ungetc(fp, ch);
581 	return (1);
582     }
583 }
584 
585 #ifdef TEST
586 
587  /*
588   * Proof of concept test program.  Mirror image of the attr_scan64 test
589   * program.
590   */
591 #include <msg_vstream.h>
592 
593 int     var_line_limit = 2048;
594 
main(int unused_argc,char ** used_argv)595 int     main(int unused_argc, char **used_argv)
596 {
597     VSTRING *data_val = vstring_alloc(1);
598     VSTRING *str_val = vstring_alloc(1);
599     HTABLE *table = htable_create(1);
600     HTABLE_INFO **ht_info_list;
601     HTABLE_INFO **ht;
602     int     int_val;
603     long    long_val;
604     long    long_val2;
605     int     ret;
606 
607     msg_verbose = 1;
608     msg_vstream_init(used_argv[0], VSTREAM_ERR);
609     if ((ret = attr_scan64(VSTREAM_IN,
610 			   ATTR_FLAG_STRICT,
611 			   RECV_ATTR_STREQ("protocol", "test"),
612 			   RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
613 			   RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
614 			   RECV_ATTR_STR(ATTR_NAME_STR, str_val),
615 			   RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
616 			   RECV_ATTR_HASH(table),
617 			   RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2),
618 			   ATTR_TYPE_END)) > 4) {
619 	vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
620 	vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
621 	vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
622 	vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
623 	ht_info_list = htable_list(table);
624 	for (ht = ht_info_list; *ht; ht++)
625 	    vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
626 	myfree((void *) ht_info_list);
627 	vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2);
628     } else {
629 	vstream_printf("return: %d\n", ret);
630     }
631     if ((ret = attr_scan64(VSTREAM_IN,
632 			   ATTR_FLAG_STRICT,
633 			   RECV_ATTR_STREQ("protocol", "test"),
634 			   RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
635 			   RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
636 			   RECV_ATTR_STR(ATTR_NAME_STR, str_val),
637 			   RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
638 			   ATTR_TYPE_END)) == 4) {
639 	vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
640 	vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
641 	vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
642 	vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
643 	ht_info_list = htable_list(table);
644 	for (ht = ht_info_list; *ht; ht++)
645 	    vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
646 	myfree((void *) ht_info_list);
647     } else {
648 	vstream_printf("return: %d\n", ret);
649     }
650     if ((ret = attr_scan64(VSTREAM_IN,
651 			   ATTR_FLAG_STRICT,
652 			   RECV_ATTR_STREQ("protocol", "test"),
653 			   ATTR_TYPE_END)) != 0)
654 	vstream_printf("return: %d\n", ret);
655     if (vstream_fflush(VSTREAM_OUT) != 0)
656 	msg_fatal("write error: %m");
657 
658     vstring_free(data_val);
659     vstring_free(str_val);
660     htable_free(table, myfree);
661 
662     return (0);
663 }
664 
665 #endif
666