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