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