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