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
attr_scan64_string(VSTREAM * fp,VSTRING * plain_buf,const char * context)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
attr_scan64_number(VSTREAM * fp,unsigned * ptr,VSTRING * str_buf,const char * context)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
attr_scan64_long_number(VSTREAM * fp,unsigned long * ptr,VSTRING * str_buf,const char * context)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
attr_vscan64(VSTREAM * fp,int flags,va_list ap)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
attr_scan64(VSTREAM * fp,int flags,...)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
attr_scan_more64(VSTREAM * fp)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
main(int unused_argc,char ** used_argv)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