1 /*++
2 /* NAME
3 /* attr_scan64 3
4 /* SUMMARY
5 /* recover attributes from byte stream
6 /* SYNOPSIS
7 /* #include <attr.h>
8 /*
9 /* int attr_scan64(fp, flags, type, name, ..., ATTR_TYPE_END)
10 /* VSTREAM *fp;
11 /* int flags;
12 /* int type;
13 /* char *name;
14 /*
15 /* int attr_vscan64(fp, flags, ap)
16 /* VSTREAM *fp;
17 /* int flags;
18 /* va_list ap;
19 /*
20 /* int attr_scan_more64(fp)
21 /* VSTREAM *fp;
22 /* DESCRIPTION
23 /* attr_scan64() takes zero or more (name, value) request attributes
24 /* and recovers the attribute values from the byte stream that was
25 /* possibly generated by attr_print64().
26 /*
27 /* attr_vscan64() provides an alternative interface that is convenient
28 /* for calling from within a variadic function.
29 /*
30 /* attr_scan_more64() returns 0 when a terminator is found
31 /* (and consumes that terminator), returns 1 when more input
32 /* is expected (without consuming input), and returns -1
33 /* otherwise (error).
34 /*
35 /* The input stream is formatted as follows, where (item)* stands
36 /* for zero or more instances of the specified item, and where
37 /* (item1 | item2) stands for choice:
38 /*
39 /* .in +5
40 /* attr-list :== (simple-attr | multi-attr)* newline
41 /* .br
42 /* multi-attr :== "{" newline simple-attr* "}" newline
43 /* .br
44 /* simple-attr :== attr-name colon attr-value newline
45 /* .br
46 /* attr-name :== any base64 encoded string
47 /* .br
48 /* attr-value :== any base64 encoded string
49 /* .br
50 /* colon :== the ASCII colon character
51 /* .br
52 /* newline :== the ASCII newline character
53 /* .in
54 /*
55 /* All attribute names and attribute values are sent as base64-encoded
56 /* strings. Each base64 encoding 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 with
61 /* the attr_scan64() argument list. The input stream may contain additional
62 /* attributes at any point in the input stream, including additional
63 /* 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_scan64() operations from the
88 /* same input attribute list.
89 /* By default, attr_scan64() skips forward past the input attribute list
90 /* 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_scan64() and attr_vscan64() return -1 when malformed input is
145 /* 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_print64(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_scan64_string - pull a string from the input stream */
193
attr_scan64_string(VSTREAM * fp,VSTRING * plain_buf,const char * context)194 static int attr_scan64_string(VSTREAM *fp, VSTRING *plain_buf, const char *context)
195 {
196 static VSTRING *base64_buf = 0;
197
198 #if 0
199 extern int var_line_limit; /* XXX */
200 int limit = var_line_limit * 4;
201
202 #endif
203 int ch;
204
205 if (base64_buf == 0)
206 base64_buf = vstring_alloc(10);
207
208 VSTRING_RESET(base64_buf);
209 while ((ch = VSTREAM_GETC(fp)) != ':' && ch != '\n') {
210 if (ch == VSTREAM_EOF) {
211 msg_warn("%s on %s while reading %s",
212 vstream_ftimeout(fp) ? "timeout" : "premature end-of-input",
213 VSTREAM_PATH(fp), context);
214 return (-1);
215 }
216 VSTRING_ADDCH(base64_buf, ch);
217 #if 0
218 if (LEN(base64_buf) > limit) {
219 msg_warn("string length > %d characters from %s while reading %s",
220 limit, VSTREAM_PATH(fp), context);
221 return (-1);
222 }
223 #endif
224 }
225 VSTRING_TERMINATE(base64_buf);
226 if (base64_decode(plain_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
227 msg_warn("malformed base64 data from %s: %.100s",
228 VSTREAM_PATH(fp), STR(base64_buf));
229 return (-1);
230 }
231 if (msg_verbose)
232 msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)");
233 return (ch);
234 }
235
236 /* attr_scan64_number - pull a number from the input stream */
237
attr_scan64_number(VSTREAM * fp,unsigned * ptr,VSTRING * str_buf,const char * context)238 static int attr_scan64_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf,
239 const char *context)
240 {
241 char junk = 0;
242 int ch;
243
244 if ((ch = attr_scan64_string(fp, str_buf, context)) < 0)
245 return (-1);
246 if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) {
247 msg_warn("malformed numerical data from %s while reading %s: %.100s",
248 VSTREAM_PATH(fp), context, STR(str_buf));
249 return (-1);
250 }
251 return (ch);
252 }
253
254 /* attr_scan64_long_number - pull a number from the input stream */
255
attr_scan64_long_number(VSTREAM * fp,unsigned long * ptr,VSTRING * str_buf,const char * context)256 static int attr_scan64_long_number(VSTREAM *fp, unsigned long *ptr,
257 VSTRING *str_buf,
258 const char *context)
259 {
260 char junk = 0;
261 int ch;
262
263 if ((ch = attr_scan64_string(fp, str_buf, context)) < 0)
264 return (-1);
265 if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) {
266 msg_warn("malformed numerical data from %s while reading %s: %.100s",
267 VSTREAM_PATH(fp), context, STR(str_buf));
268 return (-1);
269 }
270 return (ch);
271 }
272
273 /* attr_vscan64 - receive attribute list from stream */
274
attr_vscan64(VSTREAM * fp,int flags,va_list ap)275 int attr_vscan64(VSTREAM *fp, int flags, va_list ap)
276 {
277 const char *myname = "attr_scan64";
278 static VSTRING *str_buf = 0;
279 static VSTRING *name_buf = 0;
280 int wanted_type = -1;
281 char *wanted_name;
282 unsigned int *number;
283 unsigned long *long_number;
284 VSTRING *string;
285 HTABLE *hash_table;
286 int ch;
287 int conversions;
288 ATTR_SCAN_CUSTOM_FN scan_fn;
289 void *scan_arg;
290 const char *expect_val;
291
292 /*
293 * Sanity check.
294 */
295 if (flags & ~ATTR_FLAG_ALL)
296 msg_panic("%s: bad flags: 0x%x", myname, flags);
297
298 /*
299 * EOF check.
300 */
301 if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF)
302 return (0);
303 vstream_ungetc(fp, ch);
304
305 /*
306 * Initialize.
307 */
308 if (str_buf == 0) {
309 str_buf = vstring_alloc(10);
310 name_buf = vstring_alloc(10);
311 }
312
313 /*
314 * Iterate over all (type, name, value) triples.
315 */
316 for (conversions = 0; /* void */ ; conversions++) {
317
318 /*
319 * Determine the next attribute type and attribute name on the
320 * caller's wish list.
321 *
322 * If we're reading into a hash table, we already know that the
323 * attribute value is string-valued, and we get the attribute name
324 * from the input stream instead. This is secure only when the
325 * resulting table is queried with known to be good attribute names.
326 */
327 if (wanted_type != ATTR_TYPE_HASH
328 && wanted_type != ATTR_TYPE_CLOSE) {
329 wanted_type = va_arg(ap, int);
330 if (wanted_type == ATTR_TYPE_END) {
331 if ((flags & ATTR_FLAG_MORE) != 0)
332 return (conversions);
333 wanted_name = "(list terminator)";
334 } else if (wanted_type == ATTR_TYPE_HASH) {
335 wanted_name = "(any attribute name or list terminator)";
336 hash_table = va_arg(ap, HTABLE *);
337 } else if (wanted_type != ATTR_TYPE_FUNC) {
338 wanted_name = va_arg(ap, char *);
339 }
340 }
341
342 /*
343 * Locate the next attribute of interest in the input stream.
344 */
345 while (wanted_type != ATTR_TYPE_FUNC) {
346
347 /*
348 * Get the name of the next attribute. Hitting EOF is always bad.
349 * Hitting the end-of-input early is OK if the caller is prepared
350 * to deal with missing inputs.
351 */
352 if (msg_verbose)
353 msg_info("%s: wanted attribute: %s",
354 VSTREAM_PATH(fp), wanted_name);
355 if ((ch = attr_scan64_string(fp, name_buf,
356 "input attribute name")) == VSTREAM_EOF)
357 return (-1);
358 if (ch == '\n' && LEN(name_buf) == 0) {
359 if (wanted_type == ATTR_TYPE_END
360 || wanted_type == ATTR_TYPE_HASH)
361 return (conversions);
362 if ((flags & ATTR_FLAG_MISSING) != 0)
363 msg_warn("missing attribute %s in input from %s",
364 wanted_name, VSTREAM_PATH(fp));
365 return (conversions);
366 }
367
368 /*
369 * See if the caller asks for this attribute.
370 */
371 if (wanted_type == ATTR_TYPE_HASH
372 && ch == '\n' && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) {
373 wanted_type = ATTR_TYPE_CLOSE;
374 wanted_name = "(any attribute name or '}')";
375 /* Advance in the input stream. */
376 continue;
377 } else if (wanted_type == ATTR_TYPE_CLOSE
378 && ch == '\n' && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) {
379 /* Advance in the argument list. */
380 wanted_type = -1;
381 break;
382 }
383 if (wanted_type == ATTR_TYPE_HASH
384 || wanted_type == ATTR_TYPE_CLOSE
385 || (wanted_type != ATTR_TYPE_END
386 && strcmp(wanted_name, STR(name_buf)) == 0))
387 break;
388 if ((flags & ATTR_FLAG_EXTRA) != 0) {
389 msg_warn("unexpected attribute %s from %s (expecting: %s)",
390 STR(name_buf), VSTREAM_PATH(fp), wanted_name);
391 return (conversions);
392 }
393
394 /*
395 * Skip over this attribute. The caller does not ask for it.
396 */
397 while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF)
398 /* void */ ;
399 }
400
401 /*
402 * Do the requested conversion. If the target attribute is a
403 * non-array type, disallow sending a multi-valued attribute, and
404 * disallow sending no value. If the target attribute is an array
405 * type, allow the sender to send a zero-element array (i.e. no value
406 * at all). XXX Need to impose a bound on the number of array
407 * elements.
408 */
409 switch (wanted_type) {
410 case ATTR_TYPE_INT:
411 if (ch != ':') {
412 msg_warn("missing value for number attribute %s from %s",
413 STR(name_buf), VSTREAM_PATH(fp));
414 return (-1);
415 }
416 number = va_arg(ap, unsigned int *);
417 if ((ch = attr_scan64_number(fp, number, str_buf,
418 "input attribute value")) < 0)
419 return (-1);
420 if (ch != '\n') {
421 msg_warn("multiple values for attribute %s from %s",
422 STR(name_buf), VSTREAM_PATH(fp));
423 return (-1);
424 }
425 break;
426 case ATTR_TYPE_LONG:
427 if (ch != ':') {
428 msg_warn("missing value for number attribute %s from %s",
429 STR(name_buf), VSTREAM_PATH(fp));
430 return (-1);
431 }
432 long_number = va_arg(ap, unsigned long *);
433 if ((ch = attr_scan64_long_number(fp, long_number, str_buf,
434 "input attribute value")) < 0)
435 return (-1);
436 if (ch != '\n') {
437 msg_warn("multiple values for attribute %s from %s",
438 STR(name_buf), VSTREAM_PATH(fp));
439 return (-1);
440 }
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_scan64_string(fp, string,
450 "input attribute value")) < 0)
451 return (-1);
452 if (ch != '\n') {
453 msg_warn("multiple values for attribute %s from %s",
454 STR(name_buf), VSTREAM_PATH(fp));
455 return (-1);
456 }
457 if (flags & ATTR_FLAG_PRINTABLE)
458 (void) printable(STR(string), '?');
459 break;
460 case ATTR_TYPE_DATA:
461 if (ch != ':') {
462 msg_warn("missing value for data attribute %s from %s",
463 STR(name_buf), VSTREAM_PATH(fp));
464 return (-1);
465 }
466 string = va_arg(ap, VSTRING *);
467 if ((ch = attr_scan64_string(fp, string,
468 "input attribute value")) < 0)
469 return (-1);
470 if (ch != '\n') {
471 msg_warn("multiple values for attribute %s from %s",
472 STR(name_buf), VSTREAM_PATH(fp));
473 return (-1);
474 }
475 break;
476 case ATTR_TYPE_FUNC:
477 scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN);
478 scan_arg = va_arg(ap, void *);
479 if (scan_fn(attr_scan64, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0)
480 return (-1);
481 break;
482 case ATTR_TYPE_STREQ:
483 if (ch != ':') {
484 msg_warn("missing value for string attribute %s from %s",
485 STR(name_buf), VSTREAM_PATH(fp));
486 return (-1);
487 }
488 expect_val = va_arg(ap, const char *);
489 if ((ch = attr_scan64_string(fp, str_buf,
490 "input attribute value")) < 0)
491 return (-1);
492 if (ch != '\n') {
493 msg_warn("multiple values for attribute %s from %s",
494 STR(name_buf), VSTREAM_PATH(fp));
495 return (-1);
496 }
497 if (strcmp(expect_val, STR(str_buf)) != 0) {
498 msg_warn("unexpected %s %s from %s (expected: %s)",
499 STR(name_buf), STR(str_buf), VSTREAM_PATH(fp),
500 expect_val);
501 return (-1);
502 }
503 conversions -= 1;
504 break;
505 case ATTR_TYPE_HASH:
506 case ATTR_TYPE_CLOSE:
507 if (ch != ':') {
508 msg_warn("missing value for string attribute %s from %s",
509 STR(name_buf), VSTREAM_PATH(fp));
510 return (-1);
511 }
512 if ((ch = attr_scan64_string(fp, str_buf,
513 "input attribute value")) < 0)
514 return (-1);
515 if (ch != '\n') {
516 msg_warn("multiple values for attribute %s from %s",
517 STR(name_buf), VSTREAM_PATH(fp));
518 return (-1);
519 }
520 if (flags & ATTR_FLAG_PRINTABLE) {
521 (void) printable(STR(name_buf), '?');
522 (void) printable(STR(str_buf), '?');
523 }
524 if (htable_locate(hash_table, STR(name_buf)) != 0) {
525 if ((flags & ATTR_FLAG_EXTRA) != 0) {
526 msg_warn("duplicate attribute %s in input from %s",
527 STR(name_buf), VSTREAM_PATH(fp));
528 return (conversions);
529 }
530 } else if (hash_table->used >= ATTR_HASH_LIMIT) {
531 msg_warn("attribute count exceeds limit %d in input from %s",
532 ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
533 return (conversions);
534 } else {
535 htable_enter(hash_table, STR(name_buf),
536 mystrdup(STR(str_buf)));
537 }
538 break;
539 case -1:
540 conversions -= 1;
541 break;
542 default:
543 msg_panic("%s: unknown type code: %d", myname, wanted_type);
544 }
545 }
546 }
547
548 /* attr_scan64 - read attribute list from stream */
549
attr_scan64(VSTREAM * fp,int flags,...)550 int attr_scan64(VSTREAM *fp, int flags,...)
551 {
552 va_list ap;
553 int ret;
554
555 va_start(ap, flags);
556 ret = attr_vscan64(fp, flags, ap);
557 va_end(ap);
558 return (ret);
559 }
560
561 /* attr_scan_more64 - look ahead for more */
562
attr_scan_more64(VSTREAM * fp)563 int attr_scan_more64(VSTREAM *fp)
564 {
565 int ch;
566
567 switch (ch = VSTREAM_GETC(fp)) {
568 case '\n':
569 if (msg_verbose)
570 msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp));
571 return (0);
572 case VSTREAM_EOF:
573 if (msg_verbose)
574 msg_info("%s: EOF", VSTREAM_PATH(fp));
575 return (-1);
576 default:
577 if (msg_verbose)
578 msg_info("%s: non-terminator '%c' (lookahead)",
579 VSTREAM_PATH(fp), ch);
580 (void) vstream_ungetc(fp, ch);
581 return (1);
582 }
583 }
584
585 #ifdef TEST
586
587 /*
588 * Proof of concept test program. Mirror image of the attr_scan64 test
589 * program.
590 */
591 #include <msg_vstream.h>
592
593 int var_line_limit = 2048;
594
main(int unused_argc,char ** used_argv)595 int main(int unused_argc, char **used_argv)
596 {
597 VSTRING *data_val = vstring_alloc(1);
598 VSTRING *str_val = vstring_alloc(1);
599 HTABLE *table = htable_create(1);
600 HTABLE_INFO **ht_info_list;
601 HTABLE_INFO **ht;
602 int int_val;
603 long long_val;
604 long long_val2;
605 int ret;
606
607 msg_verbose = 1;
608 msg_vstream_init(used_argv[0], VSTREAM_ERR);
609 if ((ret = attr_scan64(VSTREAM_IN,
610 ATTR_FLAG_STRICT,
611 RECV_ATTR_STREQ("protocol", "test"),
612 RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
613 RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
614 RECV_ATTR_STR(ATTR_NAME_STR, str_val),
615 RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
616 RECV_ATTR_HASH(table),
617 RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2),
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 vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2);
628 } else {
629 vstream_printf("return: %d\n", ret);
630 }
631 if ((ret = attr_scan64(VSTREAM_IN,
632 ATTR_FLAG_STRICT,
633 RECV_ATTR_STREQ("protocol", "test"),
634 RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
635 RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
636 RECV_ATTR_STR(ATTR_NAME_STR, str_val),
637 RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
638 ATTR_TYPE_END)) == 4) {
639 vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
640 vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
641 vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
642 vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
643 ht_info_list = htable_list(table);
644 for (ht = ht_info_list; *ht; ht++)
645 vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
646 myfree((void *) ht_info_list);
647 } else {
648 vstream_printf("return: %d\n", ret);
649 }
650 if ((ret = attr_scan64(VSTREAM_IN,
651 ATTR_FLAG_STRICT,
652 RECV_ATTR_STREQ("protocol", "test"),
653 ATTR_TYPE_END)) != 0)
654 vstream_printf("return: %d\n", ret);
655 if (vstream_fflush(VSTREAM_OUT) != 0)
656 msg_fatal("write error: %m");
657
658 vstring_free(data_val);
659 vstring_free(str_val);
660 htable_free(table, myfree);
661
662 return (0);
663 }
664
665 #endif
666