1 /* $NetBSD: attr_scan64.c,v 1.2 2017/02/14 01:16:49 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_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_DATA(const char *name, VSTRING *vp)" 108 /* This argument is followed by an attribute name and a VSTRING pointer. 109 /* .IP "RECV_ATTR_FUNC(ATTR_SCAN_SLAVE_FN, void *data)" 110 /* This argument is followed by a function pointer and a generic data 111 /* pointer. The caller-specified function returns < 0 in case of 112 /* error. 113 /* .IP "RECV_ATTR_HASH(HTABLE *table)" 114 /* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)" 115 /* Receive a sequence of attribute names and string values. 116 /* There can be no more than 1024 attributes in a hash table. 117 /* .sp 118 /* The attribute string values are stored in the hash table under 119 /* keys equal to the attribute name (obtained from the input stream). 120 /* Values from the input stream are added to the hash table. Existing 121 /* hash table entries are not replaced. 122 /* .sp 123 /* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests 124 /* format their payload as a multi-attr sequence (see syntax 125 /* above). When the receiver's input does not start with a 126 /* multi-attr delimiter (i.e. the sender did not request 127 /* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will 128 /* store all attribute names and values up to the attribute 129 /* list terminator. In terms of code, this means that the 130 /* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed 131 /* by ATTR_TYPE_END. 132 /* .IP ATTR_TYPE_END 133 /* This argument terminates the requested attribute list. 134 /* .RE 135 /* BUGS 136 /* RECV_ATTR_HASH (RECV_ATTR_NAMEVAL) accepts attributes with arbitrary 137 /* names from possibly untrusted sources. 138 /* This is unsafe, unless the resulting table is queried only with 139 /* known to be good attribute names. 140 /* DIAGNOSTICS 141 /* attr_scan64() and attr_vscan64() return -1 when malformed input is 142 /* detected (string too long, incomplete line, missing end marker). 143 /* Otherwise, the result value is the number of attributes that were 144 /* successfully recovered from the input stream (a hash table counts 145 /* as the number of entries stored into the table). 146 /* 147 /* Panic: interface violation. All system call errors are fatal. 148 /* SEE ALSO 149 /* attr_print64(3) send attributes over byte stream. 150 /* LICENSE 151 /* .ad 152 /* .fi 153 /* The Secure Mailer license must be distributed with this software. 154 /* AUTHOR(S) 155 /* Wietse Venema 156 /* IBM T.J. Watson Research 157 /* P.O. Box 704 158 /* Yorktown Heights, NY 10598, USA 159 /* 160 /* Wietse Venema 161 /* Google, Inc. 162 /* 111 8th Avenue 163 /* New York, NY 10011, USA 164 /*--*/ 165 166 /* System library. */ 167 168 #include <sys_defs.h> 169 #include <stdarg.h> 170 #include <string.h> 171 #include <stdio.h> 172 173 /* Utility library. */ 174 175 #include <msg.h> 176 #include <mymalloc.h> 177 #include <vstream.h> 178 #include <vstring.h> 179 #include <htable.h> 180 #include <base64_code.h> 181 #include <attr.h> 182 183 /* Application specific. */ 184 185 #define STR(x) vstring_str(x) 186 #define LEN(x) VSTRING_LEN(x) 187 188 /* attr_scan64_string - pull a string from the input stream */ 189 190 static int attr_scan64_string(VSTREAM *fp, VSTRING *plain_buf, const char *context) 191 { 192 static VSTRING *base64_buf = 0; 193 194 #if 0 195 extern int var_line_limit; /* XXX */ 196 int limit = var_line_limit * 4; 197 198 #endif 199 int ch; 200 201 if (base64_buf == 0) 202 base64_buf = vstring_alloc(10); 203 204 VSTRING_RESET(base64_buf); 205 while ((ch = VSTREAM_GETC(fp)) != ':' && ch != '\n') { 206 if (ch == VSTREAM_EOF) { 207 msg_warn("%s on %s while reading %s", 208 vstream_ftimeout(fp) ? "timeout" : "premature end-of-input", 209 VSTREAM_PATH(fp), context); 210 return (-1); 211 } 212 VSTRING_ADDCH(base64_buf, ch); 213 #if 0 214 if (LEN(base64_buf) > limit) { 215 msg_warn("string length > %d characters from %s while reading %s", 216 limit, VSTREAM_PATH(fp), context); 217 return (-1); 218 } 219 #endif 220 } 221 VSTRING_TERMINATE(base64_buf); 222 if (base64_decode(plain_buf, STR(base64_buf), LEN(base64_buf)) == 0) { 223 msg_warn("malformed base64 data from %s: %.100s", 224 VSTREAM_PATH(fp), STR(base64_buf)); 225 return (-1); 226 } 227 if (msg_verbose) 228 msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)"); 229 return (ch); 230 } 231 232 /* attr_scan64_number - pull a number from the input stream */ 233 234 static int attr_scan64_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf, 235 const char *context) 236 { 237 char junk = 0; 238 int ch; 239 240 if ((ch = attr_scan64_string(fp, str_buf, context)) < 0) 241 return (-1); 242 if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) { 243 msg_warn("malformed numerical data from %s while reading %s: %.100s", 244 VSTREAM_PATH(fp), context, STR(str_buf)); 245 return (-1); 246 } 247 return (ch); 248 } 249 250 /* attr_scan64_long_number - pull a number from the input stream */ 251 252 static int attr_scan64_long_number(VSTREAM *fp, unsigned long *ptr, 253 VSTRING *str_buf, 254 const char *context) 255 { 256 char junk = 0; 257 int ch; 258 259 if ((ch = attr_scan64_string(fp, str_buf, context)) < 0) 260 return (-1); 261 if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) { 262 msg_warn("malformed numerical data from %s while reading %s: %.100s", 263 VSTREAM_PATH(fp), context, STR(str_buf)); 264 return (-1); 265 } 266 return (ch); 267 } 268 269 /* attr_vscan64 - receive attribute list from stream */ 270 271 int attr_vscan64(VSTREAM *fp, int flags, va_list ap) 272 { 273 const char *myname = "attr_scan64"; 274 static VSTRING *str_buf = 0; 275 static VSTRING *name_buf = 0; 276 int wanted_type = -1; 277 char *wanted_name; 278 unsigned int *number; 279 unsigned long *long_number; 280 VSTRING *string; 281 HTABLE *hash_table; 282 int ch; 283 int conversions; 284 ATTR_SCAN_SLAVE_FN scan_fn; 285 void *scan_arg; 286 287 /* 288 * Sanity check. 289 */ 290 if (flags & ~ATTR_FLAG_ALL) 291 msg_panic("%s: bad flags: 0x%x", myname, flags); 292 293 /* 294 * EOF check. 295 */ 296 if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF) 297 return (0); 298 vstream_ungetc(fp, ch); 299 300 /* 301 * Initialize. 302 */ 303 if (str_buf == 0) { 304 str_buf = vstring_alloc(10); 305 name_buf = vstring_alloc(10); 306 } 307 308 /* 309 * Iterate over all (type, name, value) triples. 310 */ 311 for (conversions = 0; /* void */ ; conversions++) { 312 313 /* 314 * Determine the next attribute type and attribute name on the 315 * caller's wish list. 316 * 317 * If we're reading into a hash table, we already know that the 318 * attribute value is string-valued, and we get the attribute name 319 * from the input stream instead. This is secure only when the 320 * resulting table is queried with known to be good attribute names. 321 */ 322 if (wanted_type != ATTR_TYPE_HASH 323 && wanted_type != ATTR_TYPE_CLOSE) { 324 wanted_type = va_arg(ap, int); 325 if (wanted_type == ATTR_TYPE_END) { 326 if ((flags & ATTR_FLAG_MORE) != 0) 327 return (conversions); 328 wanted_name = "(list terminator)"; 329 } else if (wanted_type == ATTR_TYPE_HASH) { 330 wanted_name = "(any attribute name or list terminator)"; 331 hash_table = va_arg(ap, HTABLE *); 332 } else if (wanted_type != ATTR_TYPE_FUNC) { 333 wanted_name = va_arg(ap, char *); 334 } 335 } 336 337 /* 338 * Locate the next attribute of interest in the input stream. 339 */ 340 while (wanted_type != ATTR_TYPE_FUNC) { 341 342 /* 343 * Get the name of the next attribute. Hitting EOF is always bad. 344 * Hitting the end-of-input early is OK if the caller is prepared 345 * to deal with missing inputs. 346 */ 347 if (msg_verbose) 348 msg_info("%s: wanted attribute: %s", 349 VSTREAM_PATH(fp), wanted_name); 350 if ((ch = attr_scan64_string(fp, name_buf, 351 "input attribute name")) == VSTREAM_EOF) 352 return (-1); 353 if (ch == '\n' && LEN(name_buf) == 0) { 354 if (wanted_type == ATTR_TYPE_END 355 || wanted_type == ATTR_TYPE_HASH) 356 return (conversions); 357 if ((flags & ATTR_FLAG_MISSING) != 0) 358 msg_warn("missing attribute %s in input from %s", 359 wanted_name, VSTREAM_PATH(fp)); 360 return (conversions); 361 } 362 363 /* 364 * See if the caller asks for this attribute. 365 */ 366 if (wanted_type == ATTR_TYPE_HASH 367 && ch == '\n' && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) { 368 wanted_type = ATTR_TYPE_CLOSE; 369 wanted_name = "(any attribute name or '}')"; 370 /* Advance in the input stream. */ 371 continue; 372 } else if (wanted_type == ATTR_TYPE_CLOSE 373 && ch == '\n' && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) { 374 /* Advance in the argument list. */ 375 wanted_type = -1; 376 break; 377 } 378 if (wanted_type == ATTR_TYPE_HASH 379 || wanted_type == ATTR_TYPE_CLOSE 380 || (wanted_type != ATTR_TYPE_END 381 && strcmp(wanted_name, STR(name_buf)) == 0)) 382 break; 383 if ((flags & ATTR_FLAG_EXTRA) != 0) { 384 msg_warn("unexpected attribute %s from %s (expecting: %s)", 385 STR(name_buf), VSTREAM_PATH(fp), wanted_name); 386 return (conversions); 387 } 388 389 /* 390 * Skip over this attribute. The caller does not ask for it. 391 */ 392 while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) 393 /* void */ ; 394 } 395 396 /* 397 * Do the requested conversion. If the target attribute is a 398 * non-array type, disallow sending a multi-valued attribute, and 399 * disallow sending no value. If the target attribute is an array 400 * type, allow the sender to send a zero-element array (i.e. no value 401 * at all). XXX Need to impose a bound on the number of array 402 * elements. 403 */ 404 switch (wanted_type) { 405 case ATTR_TYPE_INT: 406 if (ch != ':') { 407 msg_warn("missing value for number attribute %s from %s", 408 STR(name_buf), VSTREAM_PATH(fp)); 409 return (-1); 410 } 411 number = va_arg(ap, unsigned int *); 412 if ((ch = attr_scan64_number(fp, number, str_buf, 413 "input attribute value")) < 0) 414 return (-1); 415 if (ch != '\n') { 416 msg_warn("multiple values for attribute %s from %s", 417 STR(name_buf), VSTREAM_PATH(fp)); 418 return (-1); 419 } 420 break; 421 case ATTR_TYPE_LONG: 422 if (ch != ':') { 423 msg_warn("missing value for number attribute %s from %s", 424 STR(name_buf), VSTREAM_PATH(fp)); 425 return (-1); 426 } 427 long_number = va_arg(ap, unsigned long *); 428 if ((ch = attr_scan64_long_number(fp, long_number, str_buf, 429 "input attribute value")) < 0) 430 return (-1); 431 if (ch != '\n') { 432 msg_warn("multiple values for attribute %s from %s", 433 STR(name_buf), VSTREAM_PATH(fp)); 434 return (-1); 435 } 436 break; 437 case ATTR_TYPE_STR: 438 if (ch != ':') { 439 msg_warn("missing value for string attribute %s from %s", 440 STR(name_buf), VSTREAM_PATH(fp)); 441 return (-1); 442 } 443 string = va_arg(ap, VSTRING *); 444 if ((ch = attr_scan64_string(fp, string, 445 "input attribute value")) < 0) 446 return (-1); 447 if (ch != '\n') { 448 msg_warn("multiple values for attribute %s from %s", 449 STR(name_buf), VSTREAM_PATH(fp)); 450 return (-1); 451 } 452 break; 453 case ATTR_TYPE_DATA: 454 if (ch != ':') { 455 msg_warn("missing value for data attribute %s from %s", 456 STR(name_buf), VSTREAM_PATH(fp)); 457 return (-1); 458 } 459 string = va_arg(ap, VSTRING *); 460 if ((ch = attr_scan64_string(fp, string, 461 "input attribute value")) < 0) 462 return (-1); 463 if (ch != '\n') { 464 msg_warn("multiple values for attribute %s from %s", 465 STR(name_buf), VSTREAM_PATH(fp)); 466 return (-1); 467 } 468 break; 469 case ATTR_TYPE_FUNC: 470 scan_fn = va_arg(ap, ATTR_SCAN_SLAVE_FN); 471 scan_arg = va_arg(ap, void *); 472 if (scan_fn(attr_scan64, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0) 473 return (-1); 474 break; 475 case ATTR_TYPE_HASH: 476 case ATTR_TYPE_CLOSE: 477 if (ch != ':') { 478 msg_warn("missing value for string attribute %s from %s", 479 STR(name_buf), VSTREAM_PATH(fp)); 480 return (-1); 481 } 482 if ((ch = attr_scan64_string(fp, str_buf, 483 "input attribute value")) < 0) 484 return (-1); 485 if (ch != '\n') { 486 msg_warn("multiple values for attribute %s from %s", 487 STR(name_buf), VSTREAM_PATH(fp)); 488 return (-1); 489 } 490 if (htable_locate(hash_table, STR(name_buf)) != 0) { 491 if ((flags & ATTR_FLAG_EXTRA) != 0) { 492 msg_warn("duplicate attribute %s in input from %s", 493 STR(name_buf), VSTREAM_PATH(fp)); 494 return (conversions); 495 } 496 } else if (hash_table->used >= ATTR_HASH_LIMIT) { 497 msg_warn("attribute count exceeds limit %d in input from %s", 498 ATTR_HASH_LIMIT, VSTREAM_PATH(fp)); 499 return (conversions); 500 } else { 501 htable_enter(hash_table, STR(name_buf), 502 mystrdup(STR(str_buf))); 503 } 504 break; 505 case -1: 506 conversions -= 1; 507 break; 508 default: 509 msg_panic("%s: unknown type code: %d", myname, wanted_type); 510 } 511 } 512 } 513 514 /* attr_scan64 - read attribute list from stream */ 515 516 int attr_scan64(VSTREAM *fp, int flags,...) 517 { 518 va_list ap; 519 int ret; 520 521 va_start(ap, flags); 522 ret = attr_vscan64(fp, flags, ap); 523 va_end(ap); 524 return (ret); 525 } 526 527 /* attr_scan_more64 - look ahead for more */ 528 529 int attr_scan_more64(VSTREAM *fp) 530 { 531 int ch; 532 533 switch (ch = VSTREAM_GETC(fp)) { 534 case '\n': 535 if (msg_verbose) 536 msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp)); 537 return (0); 538 case VSTREAM_EOF: 539 if (msg_verbose) 540 msg_info("%s: EOF", VSTREAM_PATH(fp)); 541 return (-1); 542 default: 543 if (msg_verbose) 544 msg_info("%s: non-terminator '%c' (lookahead)", 545 VSTREAM_PATH(fp), ch); 546 (void) vstream_ungetc(fp, ch); 547 return (1); 548 } 549 } 550 551 #ifdef TEST 552 553 /* 554 * Proof of concept test program. Mirror image of the attr_scan64 test 555 * program. 556 */ 557 #include <msg_vstream.h> 558 559 int var_line_limit = 2048; 560 561 int main(int unused_argc, char **used_argv) 562 { 563 VSTRING *data_val = vstring_alloc(1); 564 VSTRING *str_val = vstring_alloc(1); 565 HTABLE *table = htable_create(1); 566 HTABLE_INFO **ht_info_list; 567 HTABLE_INFO **ht; 568 int int_val; 569 long long_val; 570 long long_val2; 571 int ret; 572 573 msg_verbose = 1; 574 msg_vstream_init(used_argv[0], VSTREAM_ERR); 575 if ((ret = attr_scan64(VSTREAM_IN, 576 ATTR_FLAG_STRICT, 577 RECV_ATTR_INT(ATTR_NAME_INT, &int_val), 578 RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), 579 RECV_ATTR_STR(ATTR_NAME_STR, str_val), 580 RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), 581 RECV_ATTR_HASH(table), 582 RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2), 583 ATTR_TYPE_END)) > 4) { 584 vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); 585 vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); 586 vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); 587 vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); 588 ht_info_list = htable_list(table); 589 for (ht = ht_info_list; *ht; ht++) 590 vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); 591 myfree((void *) ht_info_list); 592 vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2); 593 } else { 594 vstream_printf("return: %d\n", ret); 595 } 596 if ((ret = attr_scan64(VSTREAM_IN, 597 ATTR_FLAG_STRICT, 598 RECV_ATTR_INT(ATTR_NAME_INT, &int_val), 599 RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), 600 RECV_ATTR_STR(ATTR_NAME_STR, str_val), 601 RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), 602 ATTR_TYPE_END)) == 4) { 603 vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); 604 vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); 605 vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); 606 vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); 607 ht_info_list = htable_list(table); 608 for (ht = ht_info_list; *ht; ht++) 609 vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); 610 myfree((void *) ht_info_list); 611 } else { 612 vstream_printf("return: %d\n", ret); 613 } 614 if (vstream_fflush(VSTREAM_OUT) != 0) 615 msg_fatal("write error: %m"); 616 617 vstring_free(data_val); 618 vstring_free(str_val); 619 htable_free(table, myfree); 620 621 return (0); 622 } 623 624 #endif 625