1 /* $NetBSD: mac_expand.c,v 1.2 2017/02/14 01:16:49 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* mac_expand 3 6 /* SUMMARY 7 /* attribute expansion 8 /* SYNOPSIS 9 /* #include <mac_expand.h> 10 /* 11 /* int mac_expand(result, pattern, flags, filter, lookup, context) 12 /* VSTRING *result; 13 /* const char *pattern; 14 /* int flags; 15 /* const char *filter; 16 /* const char *lookup(const char *key, int mode, void *context) 17 /* void *context; 18 /* DESCRIPTION 19 /* This module implements parameter-less named attribute 20 /* expansions, both conditional and unconditional. As of Postfix 21 /* 3.0 this code supports logical expression evaluation. 22 /* 23 /* In this text, an attribute is considered "undefined" when its value 24 /* is a null pointer. Otherwise, the attribute is considered "defined" 25 /* and is expected to have as value a null-terminated string. 26 /* 27 /* In the text below, the legacy form $(...) is equivalent to 28 /* ${...}. The legacy form $(...) may eventually disappear 29 /* from documentation. 30 /* 31 /* The following substitutions are supported: 32 /* .IP "$name, ${name}" 33 /* Unconditional attribute-based substition. The result is the 34 /* named attribute value (empty if the attribute is not defined) 35 /* after optional further named attribute substitution. 36 /* .IP "${name?text}, ${name?{text}}" 37 /* Conditional attribute-based substition. If the named attribute 38 /* value is non-empty, the result is the given text, after 39 /* named attribute expansion and logical expression evaluation. 40 /* Otherwise, the result is empty. Whitespace before or after 41 /* {text} is ignored. 42 /* .IP "${name:text}, ${name:{text}}" 43 /* Conditional attribute-based substition. If the attribute 44 /* value is empty or undefined, the expansion is the given 45 /* text, after named attribute expansion and logical expression 46 /* evaluation. Otherwise, the result is empty. Whitespace 47 /* before or after {text} is ignored. 48 /* .IP "${name?{text1}:{text2}}, ${name?{text1}:text2}" 49 /* Conditional attribute-based substition. If the named attribute 50 /* value is non-empty, the result is text1. Otherwise, the 51 /* result is text2. In both cases the result is subject to 52 /* named attribute expansion and logical expression evaluation. 53 /* Whitespace before or after {text1} or {text2} is ignored. 54 /* .IP "${{text1} == ${text2} ? {text3} : {text4}}" 55 /* Logical expression-based substition. First, the content 56 /* of {text1} and ${text2} is subjected to named attribute and 57 /* logical expression-based substitution. Next, the logical 58 /* expression is evaluated. If it evaluates to "true", the 59 /* result is the content of {text3}, otherwise it is the content 60 /* of {text4}, after named attribute and logical expression-based 61 /* substitution. In addition to ==, this supports !=, <, <=, 62 /* >=, and >. Comparisons are numerical when both operands are 63 /* all digits, otherwise the comparisons are lexicographical. 64 /* 65 /* Arguments: 66 /* .IP result 67 /* Storage for the result of expansion. By default, the result 68 /* is truncated upon entry. 69 /* .IP pattern 70 /* The string to be expanded. 71 /* .IP flags 72 /* Bit-wise OR of zero or more of the following: 73 /* .RS 74 /* .IP MAC_EXP_FLAG_RECURSE 75 /* Expand attributes in lookup results. This should never be 76 /* done with data whose origin is untrusted. 77 /* .IP MAC_EXP_FLAG_APPEND 78 /* Append text to the result buffer without truncating it. 79 /* .IP MAC_EXP_FLAG_SCAN 80 /* Scan the input for named attributes, including named 81 /* attributes in all conditional result values. Do not expand 82 /* named attributes, and do not truncate or write to the result 83 /* argument. 84 /* .IP MAC_EXP_FLAG_PRINTABLE 85 /* Use the printable() function instead of \fIfilter\fR. 86 /* .PP 87 /* The constant MAC_EXP_FLAG_NONE specifies a manifest null value. 88 /* .RE 89 /* .IP filter 90 /* A null pointer, or a null-terminated array of characters that 91 /* are allowed to appear in an expansion. Illegal characters are 92 /* replaced by underscores. 93 /* .IP lookup 94 /* The attribute lookup routine. Arguments are: the attribute name, 95 /* MAC_EXP_MODE_TEST to test the existence of the named attribute 96 /* or MAC_EXP_MODE_USE to use the value of the named attribute, 97 /* and the caller context that was given to mac_expand(). A null 98 /* result value means that the requested attribute was not defined. 99 /* .IP context 100 /* Caller context that is passed on to the attribute lookup routine. 101 /* DIAGNOSTICS 102 /* Fatal errors: out of memory. Warnings: syntax errors, unreasonable 103 /* recursion depth. 104 /* 105 /* The result value is the binary OR of zero or more of the following: 106 /* .IP MAC_PARSE_ERROR 107 /* A syntax error was found in \fBpattern\fR, or some attribute had 108 /* an unreasonable nesting depth. 109 /* .IP MAC_PARSE_UNDEF 110 /* An attribute was expanded but its value was not defined. 111 /* SEE ALSO 112 /* mac_parse(3) locate macro references in string. 113 /* LICENSE 114 /* .ad 115 /* .fi 116 /* The Secure Mailer license must be distributed with this software. 117 /* AUTHOR(S) 118 /* Wietse Venema 119 /* IBM T.J. Watson Research 120 /* P.O. Box 704 121 /* Yorktown Heights, NY 10598, USA 122 /*--*/ 123 124 /* System library. */ 125 126 #include <sys_defs.h> 127 #include <ctype.h> 128 #include <string.h> 129 #include <stdlib.h> 130 131 /* Utility library. */ 132 133 #include <msg.h> 134 #include <vstring.h> 135 #include <mymalloc.h> 136 #include <stringops.h> 137 #include <name_code.h> 138 #include <mac_parse.h> 139 #include <mac_expand.h> 140 141 /* 142 * Little helper structure. 143 */ 144 typedef struct { 145 VSTRING *result; /* result buffer */ 146 int flags; /* features */ 147 const char *filter; /* character filter */ 148 MAC_EXP_LOOKUP_FN lookup; /* lookup routine */ 149 void *context; /* caller context */ 150 int status; /* findings */ 151 int level; /* nesting level */ 152 } MAC_EXP_CONTEXT; 153 154 /* 155 * Support for logical expressions. 156 * 157 * As of Postfix 2.2, ${attr-name?result} or ${attr-name:result} return the 158 * result respectively when the parameter value is non-empty, or when the 159 * parameter value is undefined or empty; support for the ternary ?: 160 * operator was anticipated, but not implemented for 10 years. 161 * 162 * To make ${logical-expr?result} and ${logical-expr:result} work as expected 163 * without breaking the way that ? and : work, logical expressions evaluate 164 * to a non-empty or empty value. It does not matter what non-empty value we 165 * use for TRUE. However we must not use the undefined (null pointer) value 166 * for FALSE - that would raise the MAC_PARSE_UNDEF flag. 167 * 168 * The value of a logical expression can be exposed with ${logical-expr}, i.e. 169 * a logical expression that is not followed by ? or : conditional 170 * expansion. 171 */ 172 #define MAC_EXP_BVAL_TRUE "true" 173 #define MAC_EXP_BVAL_FALSE "" 174 175 /* 176 * Relational operators. 177 */ 178 #define MAC_EXP_OP_STR_EQ "==" 179 #define MAC_EXP_OP_STR_NE "!=" 180 #define MAC_EXP_OP_STR_LT "<" 181 #define MAC_EXP_OP_STR_LE "<=" 182 #define MAC_EXP_OP_STR_GE ">=" 183 #define MAC_EXP_OP_STR_GT ">" 184 #define MAC_EXP_OP_STR_ANY "\"" MAC_EXP_OP_STR_EQ \ 185 "\" or \"" MAC_EXP_OP_STR_NE "\"" \ 186 "\" or \"" MAC_EXP_OP_STR_LT "\"" \ 187 "\" or \"" MAC_EXP_OP_STR_LE "\"" \ 188 "\" or \"" MAC_EXP_OP_STR_GE "\"" \ 189 "\" or \"" MAC_EXP_OP_STR_GT "\"" 190 191 #define MAC_EXP_OP_TOK_NONE 0 192 #define MAC_EXP_OP_TOK_EQ 1 193 #define MAC_EXP_OP_TOK_NE 2 194 #define MAC_EXP_OP_TOK_LT 3 195 #define MAC_EXP_OP_TOK_LE 4 196 #define MAC_EXP_OP_TOK_GE 5 197 #define MAC_EXP_OP_TOK_GT 6 198 199 static const NAME_CODE mac_exp_op_table[] = 200 { 201 MAC_EXP_OP_STR_EQ, MAC_EXP_OP_TOK_EQ, 202 MAC_EXP_OP_STR_NE, MAC_EXP_OP_TOK_NE, 203 MAC_EXP_OP_STR_LT, MAC_EXP_OP_TOK_LT, 204 MAC_EXP_OP_STR_LE, MAC_EXP_OP_TOK_LE, 205 MAC_EXP_OP_STR_GE, MAC_EXP_OP_TOK_GE, 206 MAC_EXP_OP_STR_GT, MAC_EXP_OP_TOK_GT, 207 0, MAC_EXP_OP_TOK_NONE, 208 }; 209 210 /* 211 * The whitespace separator set. 212 */ 213 #define MAC_EXP_WHITESPACE CHARS_SPACE 214 215 /* mac_exp_eval - evaluate binary expression */ 216 217 static int mac_exp_eval(const char *left, int tok_val, 218 const char *rite) 219 { 220 static const char myname[] = "mac_exp_eval"; 221 long delta; 222 223 /* 224 * Numerical or string comparison. 225 */ 226 if (alldig(left) && alldig(rite)) { 227 delta = atol(left) - atol(rite); 228 } else { 229 delta = strcmp(left, rite); 230 } 231 switch (tok_val) { 232 case MAC_EXP_OP_TOK_EQ: 233 return (delta == 0); 234 case MAC_EXP_OP_TOK_NE: 235 return (delta != 0); 236 case MAC_EXP_OP_TOK_LT: 237 return (delta < 0); 238 case MAC_EXP_OP_TOK_LE: 239 return (delta <= 0); 240 case MAC_EXP_OP_TOK_GE: 241 return (delta >= 0); 242 case MAC_EXP_OP_TOK_GT: 243 return (delta > 0); 244 default: 245 msg_panic("%s: unknown operator: %d", 246 myname, tok_val); 247 } 248 } 249 250 /* mac_exp_parse_error - report parse error, set error flag, return status */ 251 252 static int PRINTFLIKE(2, 3) mac_exp_parse_error(MAC_EXP_CONTEXT *mc, 253 const char *fmt,...) 254 { 255 va_list ap; 256 257 va_start(ap, fmt); 258 vmsg_warn(fmt, ap); 259 va_end(ap); 260 return (mc->status |= MAC_PARSE_ERROR); 261 }; 262 263 /* MAC_EXP_ERR_RETURN - report parse error, set error flag, return status */ 264 265 #define MAC_EXP_ERR_RETURN(mc, fmt, ...) do { \ 266 return (mac_exp_parse_error(mc, fmt, __VA_ARGS__)); \ 267 } while (0) 268 269 /* 270 * Postfix 3.0 introduces support for {text} operands. Only with these do 271 * we support the ternary ?: operator and logical operators. 272 * 273 * We cannot support operators in random text, because that would break Postfix 274 * 2.11 compatibility. For example, with the expression "${name?value}", the 275 * value is random text that may contain ':', '?', '{' and '}' characters. 276 * In particular, with Postfix 2.2 .. 2.11, "${name??foo:{b}ar}" evaluates 277 * to "??foo:{b}ar" or empty. There are explicit tests in this directory and 278 * the postconf directory to ensure that Postfix 2.11 compatibility is 279 * maintained. 280 * 281 * Ideally, future Postfix configurations enclose random text operands inside 282 * {} braces. These allow whitespace around operands, which improves 283 * readability. 284 */ 285 286 /* MAC_EXP_FIND_LEFT_CURLY - skip over whitespace to '{', advance read ptr */ 287 288 #define MAC_EXP_FIND_LEFT_CURLY(len, cp) \ 289 ((cp[len = strspn(cp, MAC_EXP_WHITESPACE)] == '{') ? \ 290 (cp += len) : 0) 291 292 /* mac_exp_extract_curly_payload - balance {}, skip whitespace, return payload */ 293 294 static char *mac_exp_extract_curly_payload(MAC_EXP_CONTEXT *mc, char **bp) 295 { 296 char *payload; 297 char *cp; 298 int level; 299 int ch; 300 301 /* 302 * Extract the payload and balance the {}. The caller is expected to skip 303 * leading whitespace before the {. See MAC_EXP_FIND_LEFT_CURLY(). 304 */ 305 for (level = 1, cp = *bp, payload = ++cp; /* see below */ ; cp++) { 306 if ((ch = *cp) == 0) { 307 mac_exp_parse_error(mc, "unbalanced {} in attribute expression: " 308 "\"%s\"", 309 *bp); 310 return (0); 311 } else if (ch == '{') { 312 level++; 313 } else if (ch == '}') { 314 if (--level <= 0) 315 break; 316 } 317 } 318 *cp++ = 0; 319 320 /* 321 * Skip trailing whitespace after }. 322 */ 323 *bp = cp + strspn(cp, MAC_EXP_WHITESPACE); 324 return (payload); 325 } 326 327 /* mac_exp_parse_logical - parse logical expression, advance read ptr */ 328 329 static int mac_exp_parse_logical(MAC_EXP_CONTEXT *mc, const char **lookup, 330 char **bp) 331 { 332 char *cp = *bp; 333 VSTRING *left_op_buf; 334 VSTRING *rite_op_buf; 335 const char *left_op_strval; 336 const char *rite_op_strval; 337 char *op_pos; 338 char *op_strval; 339 size_t op_len; 340 int op_tokval; 341 int op_result; 342 size_t tmp_len; 343 344 /* 345 * Left operand. The caller is expected to skip leading whitespace before 346 * the {. See MAC_EXP_FIND_LEFT_CURLY(). 347 */ 348 if ((left_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0) 349 return (mc->status); 350 351 /* 352 * Operator. Todo: regexp operator. 353 */ 354 op_pos = cp; 355 op_len = strspn(cp, "<>!=?+-*/~&|%"); /* for better diagnostics. */ 356 op_strval = mystrndup(cp, op_len); 357 op_tokval = name_code(mac_exp_op_table, NAME_CODE_FLAG_NONE, op_strval); 358 myfree(op_strval); 359 if (op_tokval == MAC_EXP_OP_TOK_NONE) 360 MAC_EXP_ERR_RETURN(mc, "%s expected at: \"...%s}>>>%.20s\"", 361 MAC_EXP_OP_STR_ANY, left_op_strval, cp); 362 cp += op_len; 363 364 /* 365 * Right operand. Todo: syntax may depend on operator. 366 */ 367 if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp) == 0) 368 MAC_EXP_ERR_RETURN(mc, "\"{expression}\" expected at: " 369 "\"...{%s} %.*s>>>%.20s\"", 370 left_op_strval, (int) op_len, op_pos, cp); 371 if ((rite_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0) 372 return (mc->status); 373 374 /* 375 * Evaluate the logical expression. Todo: regexp support. 376 */ 377 mc->status |= 378 mac_expand(left_op_buf = vstring_alloc(100), left_op_strval, 379 mc->flags, mc->filter, mc->lookup, mc->context); 380 mc->status |= 381 mac_expand(rite_op_buf = vstring_alloc(100), rite_op_strval, 382 mc->flags, mc->filter, mc->lookup, mc->context); 383 op_result = mac_exp_eval(vstring_str(left_op_buf), op_tokval, 384 vstring_str(rite_op_buf)); 385 vstring_free(left_op_buf); 386 vstring_free(rite_op_buf); 387 if (mc->status & MAC_PARSE_ERROR) 388 return (mc->status); 389 390 /* 391 * Here, we fake up a non-empty or empty parameter value lookup result, 392 * for compatibility with the historical code that looks named parameter 393 * values. 394 */ 395 *lookup = (op_result ? MAC_EXP_BVAL_TRUE : MAC_EXP_BVAL_FALSE); 396 *bp = cp; 397 return (0); 398 } 399 400 /* mac_expand_callback - callback for mac_parse */ 401 402 static int mac_expand_callback(int type, VSTRING *buf, void *ptr) 403 { 404 static const char myname[] = "mac_expand_callback"; 405 MAC_EXP_CONTEXT *mc = (MAC_EXP_CONTEXT *) ptr; 406 int lookup_mode; 407 const char *lookup; 408 char *cp; 409 int ch; 410 ssize_t res_len; 411 ssize_t tmp_len; 412 const char *res_iftrue; 413 const char *res_iffalse; 414 415 /* 416 * Sanity check. 417 */ 418 if (mc->level++ > 100) 419 mac_exp_parse_error(mc, "unreasonable macro call nesting: \"%s\"", 420 vstring_str(buf)); 421 if (mc->status & MAC_PARSE_ERROR) 422 return (mc->status); 423 424 /* 425 * Named parameter or logical expression. In case of a syntax error, 426 * return without doing damage, and issue a warning instead. 427 */ 428 if (type == MAC_PARSE_EXPR) { 429 430 cp = vstring_str(buf); 431 432 /* 433 * Logical expression. If recursion is disabled, perform only one 434 * level of $name expansion. 435 */ 436 if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) { 437 if (mac_exp_parse_logical(mc, &lookup, &cp) != 0) 438 return (mc->status); 439 440 /* 441 * Look for the ? or : operator. 442 */ 443 if ((ch = *cp) != 0) { 444 if (ch != '?' && ch != ':') 445 MAC_EXP_ERR_RETURN(mc, "\"?\" or \":\" expected at: " 446 "\"...}>>>%.20s\"", cp); 447 cp++; 448 } 449 } 450 451 /* 452 * Named parameter. 453 */ 454 else { 455 456 /* 457 * Look for the ? or : operator. In case of a syntax error, 458 * return without doing damage, and issue a warning instead. 459 */ 460 for ( /* void */ ; /* void */ ; cp++) { 461 if ((ch = *cp) == 0) { 462 lookup_mode = MAC_EXP_MODE_USE; 463 break; 464 } 465 if (ch == '?' || ch == ':') { 466 *cp++ = 0; 467 lookup_mode = MAC_EXP_MODE_TEST; 468 break; 469 } 470 if (!ISALNUM(ch) && ch != '_') { 471 MAC_EXP_ERR_RETURN(mc, "attribute name syntax error at: " 472 "\"...%.*s>>>%.20s\"", 473 (int) (cp - vstring_str(buf)), 474 vstring_str(buf), cp); 475 } 476 } 477 478 /* 479 * Look up the named parameter. Todo: allow the lookup function 480 * to specify if the result is safe for $name expanson. 481 */ 482 lookup = mc->lookup(vstring_str(buf), lookup_mode, mc->context); 483 } 484 485 /* 486 * Return the requested result. After parsing the result operand 487 * following ?, we fall through to parse the result operand following 488 * :. This is necessary with the ternary ?: operator: first, with 489 * MAC_EXP_FLAG_SCAN to parse both result operands with mac_parse(), 490 * and second, to find garbage after any result operand. Without 491 * MAC_EXP_FLAG_SCAN the content of only one of the ?: result 492 * operands will be parsed with mac_parse(); syntax errors in the 493 * other operand will be missed. 494 */ 495 switch (ch) { 496 case '?': 497 if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) { 498 if ((res_iftrue = mac_exp_extract_curly_payload(mc, &cp)) == 0) 499 return (mc->status); 500 } else { 501 res_iftrue = cp; 502 cp = ""; /* no left-over text */ 503 } 504 if ((lookup != 0 && *lookup != 0) || (mc->flags & MAC_EXP_FLAG_SCAN)) 505 mc->status |= mac_parse(res_iftrue, mac_expand_callback, 506 (void *) mc); 507 if (*cp == 0) /* end of input, OK */ 508 break; 509 if (*cp != ':') /* garbage */ 510 MAC_EXP_ERR_RETURN(mc, "\":\" expected at: " 511 "\"...%s}>>>%.20s\"", res_iftrue, cp); 512 cp += 1; 513 /* FALLTHROUGH: do not remove, see comment above. */ 514 case ':': 515 if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) { 516 if ((res_iffalse = mac_exp_extract_curly_payload(mc, &cp)) == 0) 517 return (mc->status); 518 } else { 519 res_iffalse = cp; 520 cp = ""; /* no left-over text */ 521 } 522 if (lookup == 0 || *lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) 523 mc->status |= mac_parse(res_iffalse, mac_expand_callback, 524 (void *) mc); 525 if (*cp != 0) /* garbage */ 526 MAC_EXP_ERR_RETURN(mc, "unexpected input at: " 527 "\"...%s}>>>%.20s\"", res_iffalse, cp); 528 break; 529 case 0: 530 if (lookup == 0) { 531 mc->status |= MAC_PARSE_UNDEF; 532 } else if (*lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) { 533 /* void */ ; 534 } else if (mc->flags & MAC_EXP_FLAG_RECURSE) { 535 vstring_strcpy(buf, lookup); 536 mc->status |= mac_parse(vstring_str(buf), mac_expand_callback, 537 (void *) mc); 538 } else { 539 res_len = VSTRING_LEN(mc->result); 540 vstring_strcat(mc->result, lookup); 541 if (mc->flags & MAC_EXP_FLAG_PRINTABLE) { 542 printable(vstring_str(mc->result) + res_len, '_'); 543 } else if (mc->filter) { 544 cp = vstring_str(mc->result) + res_len; 545 while (*(cp += strspn(cp, mc->filter))) 546 *cp++ = '_'; 547 } 548 } 549 break; 550 default: 551 msg_panic("%s: unknown operator code %d", myname, ch); 552 } 553 } 554 555 /* 556 * Literal text. 557 */ 558 else if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0) { 559 vstring_strcat(mc->result, vstring_str(buf)); 560 } 561 mc->level--; 562 563 return (mc->status); 564 } 565 566 /* mac_expand - expand $name instances */ 567 568 int mac_expand(VSTRING *result, const char *pattern, int flags, 569 const char *filter, 570 MAC_EXP_LOOKUP_FN lookup, void *context) 571 { 572 MAC_EXP_CONTEXT mc; 573 int status; 574 575 /* 576 * Bundle up the request and do the substitutions. 577 */ 578 mc.result = result; 579 mc.flags = flags; 580 mc.filter = filter; 581 mc.lookup = lookup; 582 mc.context = context; 583 mc.status = 0; 584 mc.level = 0; 585 if ((flags & (MAC_EXP_FLAG_APPEND | MAC_EXP_FLAG_SCAN)) == 0) 586 VSTRING_RESET(result); 587 status = mac_parse(pattern, mac_expand_callback, (void *) &mc); 588 if ((flags & MAC_EXP_FLAG_SCAN) == 0) 589 VSTRING_TERMINATE(result); 590 591 return (status); 592 } 593 594 #ifdef TEST 595 596 /* 597 * This code certainly deserves a stand-alone test program. 598 */ 599 #include <stdlib.h> 600 #include <stringops.h> 601 #include <htable.h> 602 #include <vstream.h> 603 #include <vstring_vstream.h> 604 605 static const char *lookup(const char *name, int unused_mode, void *context) 606 { 607 HTABLE *table = (HTABLE *) context; 608 609 return (htable_find(table, name)); 610 } 611 612 int main(int unused_argc, char **unused_argv) 613 { 614 VSTRING *buf = vstring_alloc(100); 615 VSTRING *result = vstring_alloc(100); 616 char *cp; 617 char *name; 618 char *value; 619 HTABLE *table; 620 int stat; 621 622 while (!vstream_feof(VSTREAM_IN)) { 623 624 table = htable_create(0); 625 626 /* 627 * Read a block of definitions, terminated with an empty line. 628 */ 629 while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) { 630 vstream_printf("<< %s\n", vstring_str(buf)); 631 vstream_fflush(VSTREAM_OUT); 632 if (VSTRING_LEN(buf) == 0) 633 break; 634 cp = vstring_str(buf); 635 name = mystrtok(&cp, CHARS_SPACE "="); 636 value = mystrtok(&cp, CHARS_SPACE "="); 637 htable_enter(table, name, value ? mystrdup(value) : 0); 638 } 639 640 /* 641 * Read a block of patterns, terminated with an empty line or EOF. 642 */ 643 while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) { 644 vstream_printf("<< %s\n", vstring_str(buf)); 645 vstream_fflush(VSTREAM_OUT); 646 if (VSTRING_LEN(buf) == 0) 647 break; 648 cp = vstring_str(buf); 649 VSTRING_RESET(result); 650 stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE, 651 (char *) 0, lookup, (void *) table); 652 vstream_printf("stat=%d result=%s\n", stat, vstring_str(result)); 653 vstream_fflush(VSTREAM_OUT); 654 } 655 htable_free(table, myfree); 656 vstream_printf("\n"); 657 } 658 659 /* 660 * Clean up. 661 */ 662 vstring_free(buf); 663 vstring_free(result); 664 exit(0); 665 } 666 667 #endif 668