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