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