1 /* $NetBSD: attr_scan_plain.c,v 1.1.1.1 2009/06/23 10:08:58 tron 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 /* DESCRIPTION 22 /* attr_scan_plain() takes zero or more (name, value) request attributes 23 /* and recovers the attribute values from the byte stream that was 24 /* possibly generated by attr_print_plain(). 25 /* 26 /* attr_vscan_plain() provides an alternative interface that is convenient 27 /* for calling from within a variadic function. 28 /* 29 /* The input stream is formatted as follows, where (item)* stands 30 /* for zero or more instances of the specified item, and where 31 /* (item1 | item2) stands for choice: 32 /* 33 /* .in +5 34 /* attr-list :== simple-attr* newline 35 /* .br 36 /* simple-attr :== attr-name "=" attr-value newline 37 /* .br 38 /* attr-name :== any string without null or "=" or newline. 39 /* .br 40 /* attr-value :== any string without null or newline. 41 /* .br 42 /* newline :== the ASCII newline character 43 /* .in 44 /* 45 /* All attribute names and attribute values are sent as plain 46 /* strings. Each string must be no longer than 4*var_line_limit 47 /* characters. The formatting rules aim to make implementations in PERL 48 /* and other languages easy. 49 /* 50 /* Normally, attributes must be received in the sequence as specified 51 /* with the attr_scan_plain() argument list. The input stream may 52 /* contain additional attributes at any point in the input stream, 53 /* including additional instances of requested attributes. 54 /* 55 /* Additional input attributes or input attribute instances are silently 56 /* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified 57 /* (see below). This allows for some flexibility in the evolution of 58 /* protocols while still providing the option of being strict where 59 /* this is desirable. 60 /* 61 /* Arguments: 62 /* .IP fp 63 /* Stream to recover the input attributes from. 64 /* .IP flags 65 /* The bit-wise OR of zero or more of the following. 66 /* .RS 67 /* .IP ATTR_FLAG_MISSING 68 /* Log a warning when the input attribute list terminates before all 69 /* requested attributes are recovered. It is always an error when the 70 /* input stream ends without the newline attribute list terminator. 71 /* .IP ATTR_FLAG_EXTRA 72 /* Log a warning and stop attribute recovery when the input stream 73 /* contains an attribute that was not requested. This includes the 74 /* case of additional instances of a requested attribute. 75 /* .IP ATTR_FLAG_MORE 76 /* After recovering the requested attributes, leave the input stream 77 /* in a state that is usable for more attr_scan_plain() operations 78 /* from the same input attribute list. 79 /* By default, attr_scan_plain() skips forward past the input attribute 80 /* list terminator. 81 /* .IP ATTR_FLAG_STRICT 82 /* For convenience, this value combines both ATTR_FLAG_MISSING and 83 /* ATTR_FLAG_EXTRA. 84 /* .IP ATTR_FLAG_NONE 85 /* For convenience, this value requests none of the above. 86 /* .RE 87 /* .IP type 88 /* The type argument determines the arguments that follow. 89 /* .RS 90 /* .IP "ATTR_TYPE_INT (char *, int *)" 91 /* This argument is followed by an attribute name and an integer pointer. 92 /* .IP "ATTR_TYPE_LONG (char *, long *)" 93 /* This argument is followed by an attribute name and a long pointer. 94 /* .IP "ATTR_TYPE_STR (char *, VSTRING *)" 95 /* This argument is followed by an attribute name and a VSTRING pointer. 96 /* .IP "ATTR_TYPE_DATA (char *, VSTRING *)" 97 /* This argument is followed by an attribute name and a VSTRING pointer. 98 /* .IP "ATTR_TYPE_FUNC (ATTR_SCAN_SLAVE_FN, void *)" 99 /* This argument is followed by a function pointer and a generic data 100 /* pointer. The caller-specified function returns < 0 in case of 101 /* error. 102 /* .IP "ATTR_TYPE_HASH (HTABLE *)" 103 /* .IP "ATTR_TYPE_NAMEVAL (NVTABLE *)" 104 /* All further input attributes are processed as string attributes. 105 /* No specific attribute sequence is enforced. 106 /* All attributes up to the attribute list terminator are read, 107 /* but only the first instance of each attribute is stored. 108 /* There can be no more than 1024 attributes in a hash table. 109 /* .sp 110 /* The attribute string values are stored in the hash table under 111 /* keys equal to the attribute name (obtained from the input stream). 112 /* Values from the input stream are added to the hash table. Existing 113 /* hash table entries are not replaced. 114 /* .sp 115 /* N.B. This construct must be followed by an ATTR_TYPE_END argument. 116 /* .IP ATTR_TYPE_END 117 /* This argument terminates the requested attribute list. 118 /* .RE 119 /* BUGS 120 /* ATTR_TYPE_HASH (ATTR_TYPE_NAMEVAL) accepts attributes with arbitrary 121 /* names from possibly untrusted sources. 122 /* This is unsafe, unless the resulting table is queried only with 123 /* known to be good attribute names. 124 /* DIAGNOSTICS 125 /* attr_scan_plain() and attr_vscan_plain() return -1 when malformed input 126 /* is detected (string too long, incomplete line, missing end marker). 127 /* Otherwise, the result value is the number of attributes that were 128 /* successfully recovered from the input stream (a hash table counts 129 /* as the number of entries stored into the table). 130 /* 131 /* Panic: interface violation. All system call errors are fatal. 132 /* SEE ALSO 133 /* attr_print_plain(3) send attributes over byte stream. 134 /* LICENSE 135 /* .ad 136 /* .fi 137 /* The Secure Mailer license must be distributed with this software. 138 /* AUTHOR(S) 139 /* Wietse Venema 140 /* IBM T.J. Watson Research 141 /* P.O. Box 704 142 /* Yorktown Heights, NY 10598, USA 143 /*--*/ 144 145 /* System library. */ 146 147 #include <sys_defs.h> 148 #include <stdarg.h> 149 #include <string.h> 150 #include <stdio.h> 151 152 /* Utility library. */ 153 154 #include <msg.h> 155 #include <mymalloc.h> 156 #include <vstream.h> 157 #include <vstring.h> 158 #include <htable.h> 159 #include <base64_code.h> 160 #include <attr.h> 161 162 /* Application specific. */ 163 164 #define STR(x) vstring_str(x) 165 #define LEN(x) VSTRING_LEN(x) 166 167 /* attr_scan_plain_string - pull a string from the input stream */ 168 169 static int attr_scan_plain_string(VSTREAM *fp, VSTRING *plain_buf, 170 int terminator, const char *context) 171 { 172 #if 0 173 extern int var_line_limit; /* XXX */ 174 int limit = var_line_limit * 4; 175 176 #endif 177 int ch; 178 179 VSTRING_RESET(plain_buf); 180 while ((ch = VSTREAM_GETC(fp)) != '\n' 181 && (terminator == 0 || ch != terminator)) { 182 if (ch == VSTREAM_EOF) { 183 msg_warn("%s on %s while reading %s", 184 vstream_ftimeout(fp) ? "timeout" : "premature end-of-input", 185 VSTREAM_PATH(fp), context); 186 return (-1); 187 } 188 VSTRING_ADDCH(plain_buf, ch); 189 #if 0 190 if (LEN(plain_buf) > limit) { 191 msg_warn("string length > %d characters from %s while reading %s", 192 limit, VSTREAM_PATH(fp), context); 193 return (-1); 194 } 195 #endif 196 } 197 VSTRING_TERMINATE(plain_buf); 198 199 if (msg_verbose) 200 msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)"); 201 return (ch); 202 } 203 204 /* attr_scan_plain_data - pull a data blob from the input stream */ 205 206 static int attr_scan_plain_data(VSTREAM *fp, VSTRING *str_buf, 207 int terminator, 208 const char *context) 209 { 210 static VSTRING *base64_buf = 0; 211 int ch; 212 213 if (base64_buf == 0) 214 base64_buf = vstring_alloc(10); 215 if ((ch = attr_scan_plain_string(fp, base64_buf, terminator, context)) < 0) 216 return (-1); 217 if (base64_decode(str_buf, STR(base64_buf), LEN(base64_buf)) == 0) { 218 msg_warn("malformed base64 data from %s while reading %s: %.100s", 219 VSTREAM_PATH(fp), context, STR(base64_buf)); 220 return (-1); 221 } 222 return (ch); 223 } 224 225 /* attr_scan_plain_number - pull a number from the input stream */ 226 227 static int attr_scan_plain_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf, 228 int terminator, const char *context) 229 { 230 char junk = 0; 231 int ch; 232 233 if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0) 234 return (-1); 235 if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) { 236 msg_warn("malformed numerical data from %s while reading %s: %.100s", 237 VSTREAM_PATH(fp), context, STR(str_buf)); 238 return (-1); 239 } 240 return (ch); 241 } 242 243 /* attr_scan_plain_long_number - pull a number from the input stream */ 244 245 static int attr_scan_plain_long_number(VSTREAM *fp, unsigned long *ptr, 246 VSTRING *str_buf, 247 int terminator, 248 const char *context) 249 { 250 char junk = 0; 251 int ch; 252 253 if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0) 254 return (-1); 255 if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) { 256 msg_warn("malformed numerical data from %s while reading %s: %.100s", 257 VSTREAM_PATH(fp), context, STR(str_buf)); 258 return (-1); 259 } 260 return (ch); 261 } 262 263 /* attr_vscan_plain - receive attribute list from stream */ 264 265 int attr_vscan_plain(VSTREAM *fp, int flags, va_list ap) 266 { 267 const char *myname = "attr_scan_plain"; 268 static VSTRING *str_buf = 0; 269 static VSTRING *name_buf = 0; 270 int wanted_type = -1; 271 char *wanted_name; 272 unsigned int *number; 273 unsigned long *long_number; 274 VSTRING *string; 275 HTABLE *hash_table; 276 int ch; 277 int conversions; 278 ATTR_SCAN_SLAVE_FN scan_fn; 279 void *scan_arg; 280 281 /* 282 * Sanity check. 283 */ 284 if (flags & ~ATTR_FLAG_ALL) 285 msg_panic("%s: bad flags: 0x%x", myname, flags); 286 287 /* 288 * EOF check. 289 */ 290 if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF) 291 return (0); 292 vstream_ungetc(fp, ch); 293 294 /* 295 * Initialize. 296 */ 297 if (str_buf == 0) { 298 str_buf = vstring_alloc(10); 299 name_buf = vstring_alloc(10); 300 } 301 302 /* 303 * Iterate over all (type, name, value) triples. 304 */ 305 for (conversions = 0; /* void */ ; conversions++) { 306 307 /* 308 * Determine the next attribute type and attribute name on the 309 * caller's wish list. 310 * 311 * If we're reading into a hash table, we already know that the 312 * attribute value is string-valued, and we get the attribute name 313 * from the input stream instead. This is secure only when the 314 * resulting table is queried with known to be good attribute names. 315 */ 316 if (wanted_type != ATTR_TYPE_HASH) { 317 wanted_type = va_arg(ap, int); 318 if (wanted_type == ATTR_TYPE_END) { 319 if ((flags & ATTR_FLAG_MORE) != 0) 320 return (conversions); 321 wanted_name = "(list terminator)"; 322 } else if (wanted_type == ATTR_TYPE_HASH) { 323 wanted_name = "(any attribute name or list terminator)"; 324 hash_table = va_arg(ap, HTABLE *); 325 if (va_arg(ap, int) != ATTR_TYPE_END) 326 msg_panic("%s: ATTR_TYPE_HASH not followed by ATTR_TYPE_END", 327 myname); 328 } else if (wanted_type != ATTR_TYPE_FUNC) { 329 wanted_name = va_arg(ap, char *); 330 } 331 } 332 333 /* 334 * Locate the next attribute of interest in the input stream. 335 */ 336 while (wanted_type != ATTR_TYPE_FUNC) { 337 338 /* 339 * Get the name of the next attribute. Hitting EOF is always bad. 340 * Hitting the end-of-input early is OK if the caller is prepared 341 * to deal with missing inputs. 342 */ 343 if (msg_verbose) 344 msg_info("%s: wanted attribute: %s", 345 VSTREAM_PATH(fp), wanted_name); 346 if ((ch = attr_scan_plain_string(fp, name_buf, '=', 347 "input attribute name")) == VSTREAM_EOF) 348 return (-1); 349 if (ch == '\n' && LEN(name_buf) == 0) { 350 if (wanted_type == ATTR_TYPE_END 351 || wanted_type == ATTR_TYPE_HASH) 352 return (conversions); 353 if ((flags & ATTR_FLAG_MISSING) != 0) 354 msg_warn("missing attribute %s in input from %s", 355 wanted_name, VSTREAM_PATH(fp)); 356 return (conversions); 357 } 358 359 /* 360 * See if the caller asks for this attribute. 361 */ 362 if (wanted_type == ATTR_TYPE_HASH 363 || (wanted_type != ATTR_TYPE_END 364 && strcmp(wanted_name, STR(name_buf)) == 0)) 365 break; 366 if ((flags & ATTR_FLAG_EXTRA) != 0) { 367 msg_warn("unexpected attribute %s from %s (expecting: %s)", 368 STR(name_buf), VSTREAM_PATH(fp), wanted_name); 369 return (conversions); 370 } 371 372 /* 373 * Skip over this attribute. The caller does not ask for it. 374 */ 375 while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) 376 /* void */ ; 377 } 378 379 /* 380 * Do the requested conversion. 381 */ 382 switch (wanted_type) { 383 case ATTR_TYPE_INT: 384 if (ch != '=') { 385 msg_warn("missing value for number attribute %s from %s", 386 STR(name_buf), VSTREAM_PATH(fp)); 387 return (-1); 388 } 389 number = va_arg(ap, unsigned int *); 390 if ((ch = attr_scan_plain_number(fp, number, str_buf, 0, 391 "input attribute value")) < 0) 392 return (-1); 393 break; 394 case ATTR_TYPE_LONG: 395 if (ch != '=') { 396 msg_warn("missing value for number attribute %s from %s", 397 STR(name_buf), VSTREAM_PATH(fp)); 398 return (-1); 399 } 400 long_number = va_arg(ap, unsigned long *); 401 if ((ch = attr_scan_plain_long_number(fp, long_number, str_buf, 402 0, "input attribute value")) < 0) 403 return (-1); 404 break; 405 case ATTR_TYPE_STR: 406 if (ch != '=') { 407 msg_warn("missing value for string attribute %s from %s", 408 STR(name_buf), VSTREAM_PATH(fp)); 409 return (-1); 410 } 411 string = va_arg(ap, VSTRING *); 412 if ((ch = attr_scan_plain_string(fp, string, 0, 413 "input attribute value")) < 0) 414 return (-1); 415 break; 416 case ATTR_TYPE_DATA: 417 if (ch != '=') { 418 msg_warn("missing value for data attribute %s from %s", 419 STR(name_buf), VSTREAM_PATH(fp)); 420 return (-1); 421 } 422 string = va_arg(ap, VSTRING *); 423 if ((ch = attr_scan_plain_data(fp, string, 0, 424 "input attribute value")) < 0) 425 return (-1); 426 break; 427 case ATTR_TYPE_FUNC: 428 scan_fn = va_arg(ap, ATTR_SCAN_SLAVE_FN); 429 scan_arg = va_arg(ap, void *); 430 if (scan_fn(attr_scan_plain, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0) 431 return (-1); 432 break; 433 case ATTR_TYPE_HASH: 434 if (ch != '=') { 435 msg_warn("missing value for string attribute %s from %s", 436 STR(name_buf), VSTREAM_PATH(fp)); 437 return (-1); 438 } 439 if ((ch = attr_scan_plain_string(fp, str_buf, 0, 440 "input attribute value")) < 0) 441 return (-1); 442 if (htable_locate(hash_table, STR(name_buf)) != 0) { 443 if ((flags & ATTR_FLAG_EXTRA) != 0) { 444 msg_warn("duplicate attribute %s in input from %s", 445 STR(name_buf), VSTREAM_PATH(fp)); 446 return (conversions); 447 } 448 } else if (hash_table->used >= ATTR_HASH_LIMIT) { 449 msg_warn("attribute count exceeds limit %d in input from %s", 450 ATTR_HASH_LIMIT, VSTREAM_PATH(fp)); 451 return (conversions); 452 } else { 453 htable_enter(hash_table, STR(name_buf), 454 mystrdup(STR(str_buf))); 455 } 456 break; 457 default: 458 msg_panic("%s: unknown type code: %d", myname, wanted_type); 459 } 460 } 461 } 462 463 /* attr_scan_plain - read attribute list from stream */ 464 465 int attr_scan_plain(VSTREAM *fp, int flags,...) 466 { 467 va_list ap; 468 int ret; 469 470 va_start(ap, flags); 471 ret = attr_vscan_plain(fp, flags, ap); 472 va_end(ap); 473 return (ret); 474 } 475 476 #ifdef TEST 477 478 /* 479 * Proof of concept test program. Mirror image of the attr_scan_plain test 480 * program. 481 */ 482 #include <msg_vstream.h> 483 484 int var_line_limit = 2048; 485 486 int main(int unused_argc, char **used_argv) 487 { 488 VSTRING *data_val = vstring_alloc(1); 489 VSTRING *str_val = vstring_alloc(1); 490 HTABLE *table = htable_create(1); 491 HTABLE_INFO **ht_info_list; 492 HTABLE_INFO **ht; 493 int int_val; 494 long long_val; 495 int ret; 496 497 msg_verbose = 1; 498 msg_vstream_init(used_argv[0], VSTREAM_ERR); 499 if ((ret = attr_scan_plain(VSTREAM_IN, 500 ATTR_FLAG_STRICT, 501 ATTR_TYPE_INT, ATTR_NAME_INT, &int_val, 502 ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val, 503 ATTR_TYPE_STR, ATTR_NAME_STR, str_val, 504 ATTR_TYPE_DATA, ATTR_NAME_DATA, data_val, 505 ATTR_TYPE_HASH, table, 506 ATTR_TYPE_END)) > 4) { 507 vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); 508 vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); 509 vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); 510 vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); 511 ht_info_list = htable_list(table); 512 for (ht = ht_info_list; *ht; ht++) 513 vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value); 514 myfree((char *) ht_info_list); 515 } else { 516 vstream_printf("return: %d\n", ret); 517 } 518 if ((ret = attr_scan_plain(VSTREAM_IN, 519 ATTR_FLAG_STRICT, 520 ATTR_TYPE_INT, ATTR_NAME_INT, &int_val, 521 ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val, 522 ATTR_TYPE_STR, ATTR_NAME_STR, str_val, 523 ATTR_TYPE_DATA, ATTR_NAME_DATA, data_val, 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(data_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, ht[0]->value); 532 myfree((char *) ht_info_list); 533 } else { 534 vstream_printf("return: %d\n", ret); 535 } 536 if (vstream_fflush(VSTREAM_OUT) != 0) 537 msg_fatal("write error: %m"); 538 539 vstring_free(data_val); 540 vstring_free(str_val); 541 htable_free(table, myfree); 542 543 return (0); 544 } 545 546 #endif 547