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