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