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 */
189 MAC_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 */
197 typedef 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
246 static 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 */
265 static HTABLE *mac_exp_ext_table;
266 static 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
atol_or_die(const char * strval)275 static 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
mac_exp_eval(const char * left,int tok_val,const char * rite)288 static 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
mac_exp_parse_error(MAC_EXP_CONTEXT * mc,const char * fmt,...)323 static 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
mac_exp_extract_curly_payload(MAC_EXP_CONTEXT * mc,char ** bp)365 static 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
mac_exp_parse_relational(MAC_EXP_CONTEXT * mc,const char ** lookup,char ** bp)400 static 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
mac_expand_add_relop(int * tok_list,const char * suffix,MAC_EXPAND_RELOP_FN relop_eval)509 void 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
mac_expand_callback(int type,VSTRING * buf,void * ptr)542 static 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
mac_expand(VSTRING * result,const char * pattern,int flags,const char * filter,MAC_EXP_LOOKUP_FN lookup,void * context)713 int 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
lookup(const char * name,int unused_mode,void * context)749 static 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
length_relop_eval(const char * left,int relop,const char * rite)756 static 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
main(int unused_argc,char ** argv)781 int 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