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