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