1/*++ 2/* NAME 3/* attr_scan64 3 4/* SUMMARY 5/* recover attributes from byte stream 6/* SYNOPSIS 7/* #include <attr.h> 8/* 9/* int attr_scan64(fp, flags, type, name, ..., ATTR_TYPE_END) 10/* VSTREAM fp; 11/* int flags; 12/* int type; 13/* char *name; 14/* 15/* int attr_vscan64(fp, flags, ap) 16/* VSTREAM fp; 17/* int flags; 18/* va_list ap; 19/* DESCRIPTION 20/* attr_scan64() takes zero or more (name, value) request attributes 21/* and recovers the attribute values from the byte stream that was 22/* possibly generated by attr_print64(). 23/* 24/* attr_vscan64() provides an alternative interface that is convenient 25/* for calling from within a variadic function. 26/* 27/* The input stream is formatted as follows, where (item)* stands 28/* for zero or more instances of the specified item, and where 29/* (item1 | item2) stands for choice: 30/* 31/* .in +5 32/* attr-list :== simple-attr* newline 33/* .br 34/* simple-attr :== attr-name colon attr-value newline 35/* .br 36/* attr-name :== any base64 encoded string 37/* .br 38/* attr-value :== any base64 encoded string 39/* .br 40/* colon :== the ASCII colon character 41/* .br 42/* newline :== the ASCII newline character 43/* .in 44/* 45/* All attribute names and attribute values are sent as base64-encoded 46/* strings. Each base64 encoding 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 with 51/* the attr_scan64() argument list. The input stream may contain additional 52/* attributes at any point in the input stream, including additional 53/* 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_scan64() operations from the 78/* same input attribute list. 79/* By default, attr_scan64() skips forward past the input attribute list 80/* 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_scan64() and attr_vscan64() return -1 when malformed input is 126/* 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_print64(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_scan64_string - pull a string from the input stream */ 168 169static int attr_scan64_string(VSTREAM *fp, VSTRING *plain_buf, const char *context) 170{ 171 static VSTRING *base64_buf = 0; 172 173#if 0 174 extern int var_line_limit; /* XXX */ 175 int limit = var_line_limit * 4; 176 177#endif 178 int ch; 179 180 if (base64_buf == 0) 181 base64_buf = vstring_alloc(10); 182 183 VSTRING_RESET(base64_buf); 184 while ((ch = VSTREAM_GETC(fp)) != ':' && ch != '\n') { 185 if (ch == VSTREAM_EOF) { 186 msg_warn("%s on %s while reading %s", 187 vstream_ftimeout(fp) ? "timeout" : "premature end-of-input", 188 VSTREAM_PATH(fp), context); 189 return (-1); 190 } 191 VSTRING_ADDCH(base64_buf, ch); 192#if 0 193 if (LEN(base64_buf) > limit) { 194 msg_warn("string length > %d characters from %s while reading %s", 195 limit, VSTREAM_PATH(fp), context); 196 return (-1); 197 } 198#endif 199 } 200 VSTRING_TERMINATE(base64_buf); 201 if (base64_decode(plain_buf, STR(base64_buf), LEN(base64_buf)) == 0) { 202 msg_warn("malformed base64 data from %s: %.100s", 203 VSTREAM_PATH(fp), STR(base64_buf)); 204 return (-1); 205 } 206 if (msg_verbose) 207 msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)"); 208 return (ch); 209} 210 211/* attr_scan64_number - pull a number from the input stream */ 212 213static int attr_scan64_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf, 214 const char *context) 215{ 216 char junk = 0; 217 int ch; 218 219 if ((ch = attr_scan64_string(fp, str_buf, context)) < 0) 220 return (-1); 221 if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) { 222 msg_warn("malformed numerical data from %s while reading %s: %.100s", 223 VSTREAM_PATH(fp), context, STR(str_buf)); 224 return (-1); 225 } 226 return (ch); 227} 228 229/* attr_scan64_long_number - pull a number from the input stream */ 230 231static int attr_scan64_long_number(VSTREAM *fp, unsigned long *ptr, 232 VSTRING *str_buf, 233 const char *context) 234{ 235 char junk = 0; 236 int ch; 237 238 if ((ch = attr_scan64_string(fp, str_buf, context)) < 0) 239 return (-1); 240 if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) { 241 msg_warn("malformed numerical data from %s while reading %s: %.100s", 242 VSTREAM_PATH(fp), context, STR(str_buf)); 243 return (-1); 244 } 245 return (ch); 246} 247 248/* attr_vscan64 - receive attribute list from stream */ 249 250int attr_vscan64(VSTREAM *fp, int flags, va_list ap) 251{ 252 const char *myname = "attr_scan64"; 253 static VSTRING *str_buf = 0; 254 static VSTRING *name_buf = 0; 255 int wanted_type = -1; 256 char *wanted_name; 257 unsigned int *number; 258 unsigned long *long_number; 259 VSTRING *string; 260 HTABLE *hash_table; 261 int ch; 262 int conversions; 263 ATTR_SCAN_SLAVE_FN scan_fn; 264 void *scan_arg; 265 266 /* 267 * Sanity check. 268 */ 269 if (flags & ~ATTR_FLAG_ALL) 270 msg_panic("%s: bad flags: 0x%x", myname, flags); 271 272 /* 273 * EOF check. 274 */ 275 if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF) 276 return (0); 277 vstream_ungetc(fp, ch); 278 279 /* 280 * Initialize. 281 */ 282 if (str_buf == 0) { 283 str_buf = vstring_alloc(10); 284 name_buf = vstring_alloc(10); 285 } 286 287 /* 288 * Iterate over all (type, name, value) triples. 289 */ 290 for (conversions = 0; /* void */ ; conversions++) { 291 292 /* 293 * Determine the next attribute type and attribute name on the 294 * caller's wish list. 295 * 296 * If we're reading into a hash table, we already know that the 297 * attribute value is string-valued, and we get the attribute name 298 * from the input stream instead. This is secure only when the 299 * resulting table is queried with known to be good attribute names. 300 */ 301 if (wanted_type != ATTR_TYPE_HASH) { 302 wanted_type = va_arg(ap, int); 303 if (wanted_type == ATTR_TYPE_END) { 304 if ((flags & ATTR_FLAG_MORE) != 0) 305 return (conversions); 306 wanted_name = "(list terminator)"; 307 } else if (wanted_type == ATTR_TYPE_HASH) { 308 wanted_name = "(any attribute name or list terminator)"; 309 hash_table = va_arg(ap, HTABLE *); 310 if (va_arg(ap, int) != ATTR_TYPE_END) 311 msg_panic("%s: ATTR_TYPE_HASH not followed by ATTR_TYPE_END", 312 myname); 313 } else if (wanted_type != ATTR_TYPE_FUNC) { 314 wanted_name = va_arg(ap, char *); 315 } 316 } 317 318 /* 319 * Locate the next attribute of interest in the input stream. 320 */ 321 while (wanted_type != ATTR_TYPE_FUNC) { 322 323 /* 324 * Get the name of the next attribute. Hitting EOF is always bad. 325 * Hitting the end-of-input early is OK if the caller is prepared 326 * to deal with missing inputs. 327 */ 328 if (msg_verbose) 329 msg_info("%s: wanted attribute: %s", 330 VSTREAM_PATH(fp), wanted_name); 331 if ((ch = attr_scan64_string(fp, name_buf, 332 "input attribute name")) == VSTREAM_EOF) 333 return (-1); 334 if (ch == '\n' && LEN(name_buf) == 0) { 335 if (wanted_type == ATTR_TYPE_END 336 || wanted_type == ATTR_TYPE_HASH) 337 return (conversions); 338 if ((flags & ATTR_FLAG_MISSING) != 0) 339 msg_warn("missing attribute %s in input from %s", 340 wanted_name, VSTREAM_PATH(fp)); 341 return (conversions); 342 } 343 344 /* 345 * See if the caller asks for this attribute. 346 */ 347 if (wanted_type == ATTR_TYPE_HASH 348 || (wanted_type != ATTR_TYPE_END 349 && strcmp(wanted_name, STR(name_buf)) == 0)) 350 break; 351 if ((flags & ATTR_FLAG_EXTRA) != 0) { 352 msg_warn("unexpected attribute %s from %s (expecting: %s)", 353 STR(name_buf), VSTREAM_PATH(fp), wanted_name); 354 return (conversions); 355 } 356 357 /* 358 * Skip over this attribute. The caller does not ask for it. 359 */ 360 while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) 361 /* void */ ; 362 } 363 364 /* 365 * Do the requested conversion. If the target attribute is a 366 * non-array type, disallow sending a multi-valued attribute, and 367 * disallow sending no value. If the target attribute is an array 368 * type, allow the sender to send a zero-element array (i.e. no value 369 * at all). XXX Need to impose a bound on the number of array 370 * elements. 371 */ 372 switch (wanted_type) { 373 case ATTR_TYPE_INT: 374 if (ch != ':') { 375 msg_warn("missing value for number attribute %s from %s", 376 STR(name_buf), VSTREAM_PATH(fp)); 377 return (-1); 378 } 379 number = va_arg(ap, unsigned int *); 380 if ((ch = attr_scan64_number(fp, number, str_buf, 381 "input attribute value")) < 0) 382 return (-1); 383 if (ch != '\n') { 384 msg_warn("multiple values for attribute %s from %s", 385 STR(name_buf), VSTREAM_PATH(fp)); 386 return (-1); 387 } 388 break; 389 case ATTR_TYPE_LONG: 390 if (ch != ':') { 391 msg_warn("missing value for number attribute %s from %s", 392 STR(name_buf), VSTREAM_PATH(fp)); 393 return (-1); 394 } 395 long_number = va_arg(ap, unsigned long *); 396 if ((ch = attr_scan64_long_number(fp, long_number, str_buf, 397 "input attribute value")) < 0) 398 return (-1); 399 if (ch != '\n') { 400 msg_warn("multiple values for attribute %s from %s", 401 STR(name_buf), VSTREAM_PATH(fp)); 402 return (-1); 403 } 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_scan64_string(fp, string, 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_DATA: 422 if (ch != ':') { 423 msg_warn("missing value for data attribute %s from %s", 424 STR(name_buf), VSTREAM_PATH(fp)); 425 return (-1); 426 } 427 string = va_arg(ap, VSTRING *); 428 if ((ch = attr_scan64_string(fp, string, 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_FUNC: 438 scan_fn = va_arg(ap, ATTR_SCAN_SLAVE_FN); 439 scan_arg = va_arg(ap, void *); 440 if (scan_fn(attr_scan64, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0) 441 return (-1); 442 break; 443 case ATTR_TYPE_HASH: 444 if (ch != ':') { 445 msg_warn("missing value for string attribute %s from %s", 446 STR(name_buf), VSTREAM_PATH(fp)); 447 return (-1); 448 } 449 if ((ch = attr_scan64_string(fp, str_buf, 450 "input attribute value")) < 0) 451 return (-1); 452 if (ch != '\n') { 453 msg_warn("multiple values for attribute %s from %s", 454 STR(name_buf), VSTREAM_PATH(fp)); 455 return (-1); 456 } 457 if (htable_locate(hash_table, STR(name_buf)) != 0) { 458 if ((flags & ATTR_FLAG_EXTRA) != 0) { 459 msg_warn("duplicate attribute %s in input from %s", 460 STR(name_buf), VSTREAM_PATH(fp)); 461 return (conversions); 462 } 463 } else if (hash_table->used >= ATTR_HASH_LIMIT) { 464 msg_warn("attribute count exceeds limit %d in input from %s", 465 ATTR_HASH_LIMIT, VSTREAM_PATH(fp)); 466 return (conversions); 467 } else { 468 htable_enter(hash_table, STR(name_buf), 469 mystrdup(STR(str_buf))); 470 } 471 break; 472 default: 473 msg_panic("%s: unknown type code: %d", myname, wanted_type); 474 } 475 } 476} 477 478/* attr_scan64 - read attribute list from stream */ 479 480int attr_scan64(VSTREAM *fp, int flags,...) 481{ 482 va_list ap; 483 int ret; 484 485 va_start(ap, flags); 486 ret = attr_vscan64(fp, flags, ap); 487 va_end(ap); 488 return (ret); 489} 490 491#ifdef TEST 492 493 /* 494 * Proof of concept test program. Mirror image of the attr_scan64 test 495 * program. 496 */ 497#include <msg_vstream.h> 498 499int var_line_limit = 2048; 500 501int main(int unused_argc, char **used_argv) 502{ 503 VSTRING *data_val = vstring_alloc(1); 504 VSTRING *str_val = vstring_alloc(1); 505 HTABLE *table = htable_create(1); 506 HTABLE_INFO **ht_info_list; 507 HTABLE_INFO **ht; 508 int int_val; 509 long long_val; 510 int ret; 511 512 msg_verbose = 1; 513 msg_vstream_init(used_argv[0], VSTREAM_ERR); 514 if ((ret = attr_scan64(VSTREAM_IN, 515 ATTR_FLAG_STRICT, 516 ATTR_TYPE_INT, ATTR_NAME_INT, &int_val, 517 ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val, 518 ATTR_TYPE_STR, ATTR_NAME_STR, str_val, 519 ATTR_TYPE_DATA, ATTR_NAME_DATA, data_val, 520 ATTR_TYPE_HASH, table, 521 ATTR_TYPE_END)) > 4) { 522 vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); 523 vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); 524 vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); 525 vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); 526 ht_info_list = htable_list(table); 527 for (ht = ht_info_list; *ht; ht++) 528 vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value); 529 myfree((char *) ht_info_list); 530 } else { 531 vstream_printf("return: %d\n", ret); 532 } 533 if ((ret = attr_scan64(VSTREAM_IN, 534 ATTR_FLAG_STRICT, 535 ATTR_TYPE_INT, ATTR_NAME_INT, &int_val, 536 ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val, 537 ATTR_TYPE_STR, ATTR_NAME_STR, str_val, 538 ATTR_TYPE_DATA, ATTR_NAME_DATA, data_val, 539 ATTR_TYPE_END)) == 4) { 540 vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); 541 vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); 542 vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); 543 vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); 544 ht_info_list = htable_list(table); 545 for (ht = ht_info_list; *ht; ht++) 546 vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value); 547 myfree((char *) ht_info_list); 548 } else { 549 vstream_printf("return: %d\n", ret); 550 } 551 if (vstream_fflush(VSTREAM_OUT) != 0) 552 msg_fatal("write error: %m"); 553 554 vstring_free(data_val); 555 vstring_free(str_val); 556 htable_free(table, myfree); 557 558 return (0); 559} 560 561#endif 562