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