Home | History | Annotate | Line # | Download | only in util
      1 /*	$NetBSD: attr_scan_plain.c,v 1.3 2022/10/08 16:12:50 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_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_scan_plain() and attr_vscan_plain() return -1 when malformed input
    145 /*	is 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_print_plain(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_scan_plain_string - pull a string from the input stream */
    193 
    194 static int attr_scan_plain_string(VSTREAM *fp, VSTRING *plain_buf,
    195 				        int terminator, const char *context)
    196 {
    197 #if 0
    198     extern int var_line_limit;		/* XXX */
    199     int     limit = var_line_limit * 4;
    200 
    201 #endif
    202     int     ch;
    203 
    204     VSTRING_RESET(plain_buf);
    205     while ((ch = VSTREAM_GETC(fp)) != '\n'
    206 	   && (terminator == 0 || ch != terminator)) {
    207 	if (ch == VSTREAM_EOF) {
    208 	    msg_warn("%s on %s while reading %s",
    209 		vstream_ftimeout(fp) ? "timeout" : "premature end-of-input",
    210 		     VSTREAM_PATH(fp), context);
    211 	    return (-1);
    212 	}
    213 	VSTRING_ADDCH(plain_buf, ch);
    214 #if 0
    215 	if (LEN(plain_buf) > limit) {
    216 	    msg_warn("string length > %d characters from %s while reading %s",
    217 		     limit, VSTREAM_PATH(fp), context);
    218 	    return (-1);
    219 	}
    220 #endif
    221     }
    222     VSTRING_TERMINATE(plain_buf);
    223 
    224     if (msg_verbose)
    225 	msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)");
    226     return (ch);
    227 }
    228 
    229 /* attr_scan_plain_data - pull a data blob from the input stream */
    230 
    231 static int attr_scan_plain_data(VSTREAM *fp, VSTRING *str_buf,
    232 				        int terminator,
    233 				        const char *context)
    234 {
    235     static VSTRING *base64_buf = 0;
    236     int     ch;
    237 
    238     if (base64_buf == 0)
    239 	base64_buf = vstring_alloc(10);
    240     if ((ch = attr_scan_plain_string(fp, base64_buf, terminator, context)) < 0)
    241 	return (-1);
    242     if (base64_decode(str_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
    243 	msg_warn("malformed base64 data from %s while reading %s: %.100s",
    244 		 VSTREAM_PATH(fp), context, STR(base64_buf));
    245 	return (-1);
    246     }
    247     return (ch);
    248 }
    249 
    250 /* attr_scan_plain_number - pull a number from the input stream */
    251 
    252 static int attr_scan_plain_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf,
    253 				        int terminator, const char *context)
    254 {
    255     char    junk = 0;
    256     int     ch;
    257 
    258     if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0)
    259 	return (-1);
    260     if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) {
    261 	msg_warn("malformed numerical data from %s while reading %s: %.100s",
    262 		 VSTREAM_PATH(fp), context, STR(str_buf));
    263 	return (-1);
    264     }
    265     return (ch);
    266 }
    267 
    268 /* attr_scan_plain_long_number - pull a number from the input stream */
    269 
    270 static int attr_scan_plain_long_number(VSTREAM *fp, unsigned long *ptr,
    271 				               VSTRING *str_buf,
    272 				               int terminator,
    273 				               const char *context)
    274 {
    275     char    junk = 0;
    276     int     ch;
    277 
    278     if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0)
    279 	return (-1);
    280     if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) {
    281 	msg_warn("malformed numerical data from %s while reading %s: %.100s",
    282 		 VSTREAM_PATH(fp), context, STR(str_buf));
    283 	return (-1);
    284     }
    285     return (ch);
    286 }
    287 
    288 /* attr_vscan_plain - receive attribute list from stream */
    289 
    290 int     attr_vscan_plain(VSTREAM *fp, int flags, va_list ap)
    291 {
    292     const char *myname = "attr_scan_plain";
    293     static VSTRING *str_buf = 0;
    294     static VSTRING *name_buf = 0;
    295     int     wanted_type = -1;
    296     char   *wanted_name;
    297     unsigned int *number;
    298     unsigned long *long_number;
    299     VSTRING *string;
    300     HTABLE *hash_table;
    301     int     ch;
    302     int     conversions;
    303     ATTR_SCAN_CUSTOM_FN scan_fn;
    304     void   *scan_arg;
    305     const char *expect_val;
    306 
    307     /*
    308      * Sanity check.
    309      */
    310     if (flags & ~ATTR_FLAG_ALL)
    311 	msg_panic("%s: bad flags: 0x%x", myname, flags);
    312 
    313     /*
    314      * EOF check.
    315      */
    316     if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF)
    317 	return (0);
    318     vstream_ungetc(fp, ch);
    319 
    320     /*
    321      * Initialize.
    322      */
    323     if (str_buf == 0) {
    324 	str_buf = vstring_alloc(10);
    325 	name_buf = vstring_alloc(10);
    326     }
    327 
    328     /*
    329      * Iterate over all (type, name, value) triples.
    330      */
    331     for (conversions = 0; /* void */ ; conversions++) {
    332 
    333 	/*
    334 	 * Determine the next attribute type and attribute name on the
    335 	 * caller's wish list.
    336 	 *
    337 	 * If we're reading into a hash table, we already know that the
    338 	 * attribute value is string-valued, and we get the attribute name
    339 	 * from the input stream instead. This is secure only when the
    340 	 * resulting table is queried with known to be good attribute names.
    341 	 */
    342 	if (wanted_type != ATTR_TYPE_HASH
    343 	    && wanted_type != ATTR_TYPE_CLOSE) {
    344 	    wanted_type = va_arg(ap, int);
    345 	    if (wanted_type == ATTR_TYPE_END) {
    346 		if ((flags & ATTR_FLAG_MORE) != 0)
    347 		    return (conversions);
    348 		wanted_name = "(list terminator)";
    349 	    } else if (wanted_type == ATTR_TYPE_HASH) {
    350 		wanted_name = "(any attribute name or list terminator)";
    351 		hash_table = va_arg(ap, HTABLE *);
    352 	    } else if (wanted_type != ATTR_TYPE_FUNC) {
    353 		wanted_name = va_arg(ap, char *);
    354 	    }
    355 	}
    356 
    357 	/*
    358 	 * Locate the next attribute of interest in the input stream.
    359 	 */
    360 	while (wanted_type != ATTR_TYPE_FUNC) {
    361 
    362 	    /*
    363 	     * Get the name of the next attribute. Hitting EOF is always bad.
    364 	     * Hitting the end-of-input early is OK if the caller is prepared
    365 	     * to deal with missing inputs.
    366 	     */
    367 	    if (msg_verbose)
    368 		msg_info("%s: wanted attribute: %s",
    369 			 VSTREAM_PATH(fp), wanted_name);
    370 	    if ((ch = attr_scan_plain_string(fp, name_buf, '=',
    371 				    "input attribute name")) == VSTREAM_EOF)
    372 		return (-1);
    373 	    if (ch == '\n' && LEN(name_buf) == 0) {
    374 		if (wanted_type == ATTR_TYPE_END
    375 		    || wanted_type == ATTR_TYPE_HASH)
    376 		    return (conversions);
    377 		if ((flags & ATTR_FLAG_MISSING) != 0)
    378 		    msg_warn("missing attribute %s in input from %s",
    379 			     wanted_name, VSTREAM_PATH(fp));
    380 		return (conversions);
    381 	    }
    382 
    383 	    /*
    384 	     * See if the caller asks for this attribute.
    385 	     */
    386 	    if (wanted_type == ATTR_TYPE_HASH
    387 	      && ch == '\n' && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) {
    388 		wanted_type = ATTR_TYPE_CLOSE;
    389 		wanted_name = "(any attribute name or '}')";
    390 		/* Advance in the input stream. */
    391 		continue;
    392 	    } else if (wanted_type == ATTR_TYPE_CLOSE
    393 	     && ch == '\n' && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) {
    394 		/* Advance in the argument list. */
    395 		wanted_type = -1;
    396 		break;
    397 	    }
    398 	    if (wanted_type == ATTR_TYPE_HASH
    399 		|| wanted_type == ATTR_TYPE_CLOSE
    400 		|| (wanted_type != ATTR_TYPE_END
    401 		    && strcmp(wanted_name, STR(name_buf)) == 0))
    402 		break;
    403 	    if ((flags & ATTR_FLAG_EXTRA) != 0) {
    404 		msg_warn("unexpected attribute %s from %s (expecting: %s)",
    405 			 STR(name_buf), VSTREAM_PATH(fp), wanted_name);
    406 		return (conversions);
    407 	    }
    408 
    409 	    /*
    410 	     * Skip over this attribute. The caller does not ask for it.
    411 	     */
    412 	    while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF)
    413 		 /* void */ ;
    414 	}
    415 
    416 	/*
    417 	 * Do the requested conversion.
    418 	 */
    419 	switch (wanted_type) {
    420 	case ATTR_TYPE_INT:
    421 	    if (ch != '=') {
    422 		msg_warn("missing value for number attribute %s from %s",
    423 			 STR(name_buf), VSTREAM_PATH(fp));
    424 		return (-1);
    425 	    }
    426 	    number = va_arg(ap, unsigned int *);
    427 	    if ((ch = attr_scan_plain_number(fp, number, str_buf, 0,
    428 					     "input attribute value")) < 0)
    429 		return (-1);
    430 	    break;
    431 	case ATTR_TYPE_LONG:
    432 	    if (ch != '=') {
    433 		msg_warn("missing value for number attribute %s from %s",
    434 			 STR(name_buf), VSTREAM_PATH(fp));
    435 		return (-1);
    436 	    }
    437 	    long_number = va_arg(ap, unsigned long *);
    438 	    if ((ch = attr_scan_plain_long_number(fp, long_number, str_buf,
    439 					   0, "input attribute value")) < 0)
    440 		return (-1);
    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_scan_plain_string(fp, string, 0,
    450 					     "input attribute value")) < 0)
    451 		return (-1);
    452 	    if (flags & ATTR_FLAG_PRINTABLE)
    453 		(void) printable(STR(string), '?');
    454 	    break;
    455 	case ATTR_TYPE_DATA:
    456 	    if (ch != '=') {
    457 		msg_warn("missing value for data attribute %s from %s",
    458 			 STR(name_buf), VSTREAM_PATH(fp));
    459 		return (-1);
    460 	    }
    461 	    string = va_arg(ap, VSTRING *);
    462 	    if ((ch = attr_scan_plain_data(fp, string, 0,
    463 					   "input attribute value")) < 0)
    464 		return (-1);
    465 	    break;
    466 	case ATTR_TYPE_FUNC:
    467 	    scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN);
    468 	    scan_arg = va_arg(ap, void *);
    469 	    if (scan_fn(attr_scan_plain, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0)
    470 		return (-1);
    471 	    break;
    472 	case ATTR_TYPE_STREQ:
    473 	    if (ch != '=') {
    474 		msg_warn("missing value for string attribute %s from %s",
    475 			 STR(name_buf), VSTREAM_PATH(fp));
    476 		return (-1);
    477 	    }
    478 	    expect_val = va_arg(ap, const char *);
    479 	    if ((ch = attr_scan_plain_string(fp, str_buf, 0,
    480 					     "input attribute value")) < 0)
    481 		return (-1);
    482 	    if (strcmp(expect_val, STR(str_buf)) != 0) {
    483 		msg_warn("unexpected %s %s from %s (expected: %s)",
    484 			 STR(name_buf), STR(str_buf), VSTREAM_PATH(fp),
    485 			 expect_val);
    486 		return (-1);
    487 	    }
    488 	    conversions -= 1;
    489 	    break;
    490 	case ATTR_TYPE_HASH:
    491 	case ATTR_TYPE_CLOSE:
    492 	    if (ch != '=') {
    493 		msg_warn("missing value for string attribute %s from %s",
    494 			 STR(name_buf), VSTREAM_PATH(fp));
    495 		return (-1);
    496 	    }
    497 	    if ((ch = attr_scan_plain_string(fp, str_buf, 0,
    498 					     "input attribute value")) < 0)
    499 		return (-1);
    500 	    if (flags & ATTR_FLAG_PRINTABLE) {
    501 		(void) printable(STR(name_buf), '?');
    502 		(void) printable(STR(str_buf), '?');
    503 	    }
    504 	    if (htable_locate(hash_table, STR(name_buf)) != 0) {
    505 		if ((flags & ATTR_FLAG_EXTRA) != 0) {
    506 		    msg_warn("duplicate attribute %s in input from %s",
    507 			     STR(name_buf), VSTREAM_PATH(fp));
    508 		    return (conversions);
    509 		}
    510 	    } else if (hash_table->used >= ATTR_HASH_LIMIT) {
    511 		msg_warn("attribute count exceeds limit %d in input from %s",
    512 			 ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
    513 		return (conversions);
    514 	    } else {
    515 		htable_enter(hash_table, STR(name_buf),
    516 			     mystrdup(STR(str_buf)));
    517 	    }
    518 	    break;
    519 	case -1:
    520 	    conversions -= 1;
    521 	    break;
    522 	default:
    523 	    msg_panic("%s: unknown type code: %d", myname, wanted_type);
    524 	}
    525     }
    526 }
    527 
    528 /* attr_scan_plain - read attribute list from stream */
    529 
    530 int     attr_scan_plain(VSTREAM *fp, int flags,...)
    531 {
    532     va_list ap;
    533     int     ret;
    534 
    535     va_start(ap, flags);
    536     ret = attr_vscan_plain(fp, flags, ap);
    537     va_end(ap);
    538     return (ret);
    539 }
    540 
    541 /* attr_scan_more_plain - look ahead for more */
    542 
    543 int     attr_scan_more_plain(VSTREAM *fp)
    544 {
    545     int     ch;
    546 
    547     switch (ch = VSTREAM_GETC(fp)) {
    548     case '\n':
    549 	if (msg_verbose)
    550 	    msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp));
    551 	return (0);
    552     case VSTREAM_EOF:
    553 	if (msg_verbose)
    554 	    msg_info("%s: EOF", VSTREAM_PATH(fp));
    555 	return (-1);
    556     default:
    557 	if (msg_verbose)
    558 	    msg_info("%s: non-terminator '%c' (lookahead)",
    559 		     VSTREAM_PATH(fp), ch);
    560 	(void) vstream_ungetc(fp, ch);
    561 	return (1);
    562     }
    563 }
    564 
    565 #ifdef TEST
    566 
    567  /*
    568   * Proof of concept test program.  Mirror image of the attr_scan_plain test
    569   * program.
    570   */
    571 #include <msg_vstream.h>
    572 
    573 int     var_line_limit = 2048;
    574 
    575 int     main(int unused_argc, char **used_argv)
    576 {
    577     VSTRING *data_val = vstring_alloc(1);
    578     VSTRING *str_val = vstring_alloc(1);
    579     HTABLE *table = htable_create(1);
    580     HTABLE_INFO **ht_info_list;
    581     HTABLE_INFO **ht;
    582     int     int_val;
    583     long    long_val;
    584     long    long_val2;
    585     int     ret;
    586 
    587     msg_verbose = 1;
    588     msg_vstream_init(used_argv[0], VSTREAM_ERR);
    589     if ((ret = attr_scan_plain(VSTREAM_IN,
    590 			       ATTR_FLAG_STRICT,
    591 			       RECV_ATTR_STREQ("protocol", "test"),
    592 			       RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
    593 			       RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
    594 			       RECV_ATTR_STR(ATTR_NAME_STR, str_val),
    595 			       RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
    596 			       RECV_ATTR_HASH(table),
    597 			       RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2),
    598 			       ATTR_TYPE_END)) > 4) {
    599 	vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
    600 	vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
    601 	vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
    602 	vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
    603 	ht_info_list = htable_list(table);
    604 	for (ht = ht_info_list; *ht; ht++)
    605 	    vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
    606 	myfree((void *) ht_info_list);
    607 	vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2);
    608     } else {
    609 	vstream_printf("return: %d\n", ret);
    610     }
    611     if ((ret = attr_scan_plain(VSTREAM_IN,
    612 			       ATTR_FLAG_STRICT,
    613 			       RECV_ATTR_STREQ("protocol", "test"),
    614 			       RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
    615 			       RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
    616 			       RECV_ATTR_STR(ATTR_NAME_STR, str_val),
    617 			       RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
    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     } else {
    628 	vstream_printf("return: %d\n", ret);
    629     }
    630     if ((ret = attr_scan_plain(VSTREAM_IN,
    631 			       ATTR_FLAG_STRICT,
    632 			       RECV_ATTR_STREQ("protocol", "test"),
    633 			       ATTR_TYPE_END)) != 0)
    634 	vstream_printf("return: %d\n", ret);
    635     if (vstream_fflush(VSTREAM_OUT) != 0)
    636 	msg_fatal("write error: %m");
    637 
    638     vstring_free(data_val);
    639     vstring_free(str_val);
    640     htable_free(table, myfree);
    641 
    642     return (0);
    643 }
    644 
    645 #endif
    646