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