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