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