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