summaryrefslogtreecommitdiffstats
path: root/src/util/mac_expand.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/mac_expand.c')
-rw-r--r--src/util/mac_expand.c693
1 files changed, 693 insertions, 0 deletions
diff --git a/src/util/mac_expand.c b/src/util/mac_expand.c
new file mode 100644
index 0000000..1679e5e
--- /dev/null
+++ b/src/util/mac_expand.c
@@ -0,0 +1,693 @@
+/*++
+/* NAME
+/* mac_expand 3
+/* SUMMARY
+/* attribute expansion
+/* SYNOPSIS
+/* #include <mac_expand.h>
+/*
+/* int mac_expand(result, pattern, flags, filter, lookup, context)
+/* VSTRING *result;
+/* const char *pattern;
+/* int flags;
+/* const char *filter;
+/* const char *lookup(const char *key, int mode, void *context)
+/* void *context;
+/* DESCRIPTION
+/* This module implements parameter-less named attribute
+/* expansions, both conditional and unconditional. As of Postfix
+/* 3.0 this code supports relational expression evaluation.
+/*
+/* In this text, an attribute is considered "undefined" when its value
+/* is a null pointer. Otherwise, the attribute is considered "defined"
+/* and is expected to have as value a null-terminated string.
+/*
+/* In the text below, the legacy form $(...) is equivalent to
+/* ${...}. The legacy form $(...) may eventually disappear
+/* from documentation. In the text below, the name in $name
+/* and ${name...} must contain only characters from the set
+/* [a-zA-Z0-9_].
+/*
+/* The following substitutions are supported:
+/* .IP "$name, ${name}"
+/* Unconditional attribute-based substition. The result is the
+/* named attribute value (empty if the attribute is not defined)
+/* after optional further named attribute substitution.
+/* .IP "${name?text}, ${name?{text}}"
+/* Conditional attribute-based substition. If the named attribute
+/* value is non-empty, the result is the given text, after
+/* named attribute expansion and relational expression evaluation.
+/* Otherwise, the result is empty. Whitespace before or after
+/* {text} is ignored.
+/* .IP "${name:text}, ${name:{text}}"
+/* Conditional attribute-based substition. If the attribute
+/* value is empty or undefined, the expansion is the given
+/* text, after named attribute expansion and relational expression
+/* evaluation. Otherwise, the result is empty. Whitespace
+/* before or after {text} is ignored.
+/* .IP "${name?{text1}:{text2}}, ${name?{text1}:text2}"
+/* Conditional attribute-based substition. If the named attribute
+/* value is non-empty, the result is text1. Otherwise, the
+/* result is text2. In both cases the result is subject to
+/* named attribute expansion and relational expression evaluation.
+/* Whitespace before or after {text1} or {text2} is ignored.
+/* .IP "${{text1} == ${text2} ? {text3} : {text4}}"
+/* Relational expression-based substition. First, the content
+/* of {text1} and ${text2} is subjected to named attribute and
+/* relational expression-based substitution. Next, the relational
+/* expression is evaluated. If it evaluates to "true", the
+/* result is the content of {text3}, otherwise it is the content
+/* of {text4}, after named attribute and relational expression-based
+/* substitution. In addition to ==, this supports !=, <, <=,
+/* >=, and >. Comparisons are numerical when both operands are
+/* all digits, otherwise the comparisons are lexicographical.
+/*
+/* Arguments:
+/* .IP result
+/* Storage for the result of expansion. By default, the result
+/* is truncated upon entry.
+/* .IP pattern
+/* The string to be expanded.
+/* .IP flags
+/* Bit-wise OR of zero or more of the following:
+/* .RS
+/* .IP MAC_EXP_FLAG_RECURSE
+/* Expand attributes in lookup results. This should never be
+/* done with data whose origin is untrusted.
+/* .IP MAC_EXP_FLAG_APPEND
+/* Append text to the result buffer without truncating it.
+/* .IP MAC_EXP_FLAG_SCAN
+/* Scan the input for named attributes, including named
+/* attributes in all conditional result values. Do not expand
+/* named attributes, and do not truncate or write to the result
+/* argument.
+/* .IP MAC_EXP_FLAG_PRINTABLE
+/* Use the printable() function instead of \fIfilter\fR.
+/* .PP
+/* The constant MAC_EXP_FLAG_NONE specifies a manifest null value.
+/* .RE
+/* .IP filter
+/* A null pointer, or a null-terminated array of characters that
+/* are allowed to appear in an expansion. Illegal characters are
+/* replaced by underscores.
+/* .IP lookup
+/* The attribute lookup routine. Arguments are: the attribute name,
+/* MAC_EXP_MODE_TEST to test the existence of the named attribute
+/* or MAC_EXP_MODE_USE to use the value of the named attribute,
+/* and the caller context that was given to mac_expand(). A null
+/* result value means that the requested attribute was not defined.
+/* .IP context
+/* Caller context that is passed on to the attribute lookup routine.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory. Warnings: syntax errors, unreasonable
+/* recursion depth.
+/*
+/* The result value is the binary OR of zero or more of the following:
+/* .IP MAC_PARSE_ERROR
+/* A syntax error was found in \fBpattern\fR, or some attribute had
+/* an unreasonable nesting depth.
+/* .IP MAC_PARSE_UNDEF
+/* An attribute was expanded but its value was not defined.
+/* SEE ALSO
+/* mac_parse(3) locate macro references in string.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <name_code.h>
+#include <mac_parse.h>
+#include <mac_expand.h>
+
+ /*
+ * Little helper structure.
+ */
+typedef struct {
+ VSTRING *result; /* result buffer */
+ int flags; /* features */
+ const char *filter; /* character filter */
+ MAC_EXP_LOOKUP_FN lookup; /* lookup routine */
+ void *context; /* caller context */
+ int status; /* findings */
+ int level; /* nesting level */
+} MAC_EXP_CONTEXT;
+
+ /*
+ * Support for relational expressions.
+ *
+ * As of Postfix 2.2, ${attr-name?result} or ${attr-name:result} return the
+ * result respectively when the parameter value is non-empty, or when the
+ * parameter value is undefined or empty; support for the ternary ?:
+ * operator was anticipated, but not implemented for 10 years.
+ *
+ * To make ${relational-expr?result} and ${relational-expr:result} work as
+ * expected without breaking the way that ? and : work, relational
+ * expressions evaluate to a non-empty or empty value. It does not matter
+ * what non-empty value we use for TRUE. However we must not use the
+ * undefined (null pointer) value for FALSE - that would raise the
+ * MAC_PARSE_UNDEF flag.
+ *
+ * The value of a relational expression can be exposed with ${relational-expr},
+ * i.e. a relational expression that is not followed by ? or : conditional
+ * expansion.
+ */
+#define MAC_EXP_BVAL_TRUE "true"
+#define MAC_EXP_BVAL_FALSE ""
+
+ /*
+ * Relational operators.
+ */
+#define MAC_EXP_OP_STR_EQ "=="
+#define MAC_EXP_OP_STR_NE "!="
+#define MAC_EXP_OP_STR_LT "<"
+#define MAC_EXP_OP_STR_LE "<="
+#define MAC_EXP_OP_STR_GE ">="
+#define MAC_EXP_OP_STR_GT ">"
+#define MAC_EXP_OP_STR_ANY "\"" MAC_EXP_OP_STR_EQ \
+ "\" or \"" MAC_EXP_OP_STR_NE "\"" \
+ "\" or \"" MAC_EXP_OP_STR_LT "\"" \
+ "\" or \"" MAC_EXP_OP_STR_LE "\"" \
+ "\" or \"" MAC_EXP_OP_STR_GE "\"" \
+ "\" or \"" MAC_EXP_OP_STR_GT "\""
+
+#define MAC_EXP_OP_TOK_NONE 0
+#define MAC_EXP_OP_TOK_EQ 1
+#define MAC_EXP_OP_TOK_NE 2
+#define MAC_EXP_OP_TOK_LT 3
+#define MAC_EXP_OP_TOK_LE 4
+#define MAC_EXP_OP_TOK_GE 5
+#define MAC_EXP_OP_TOK_GT 6
+
+static const NAME_CODE mac_exp_op_table[] =
+{
+ MAC_EXP_OP_STR_EQ, MAC_EXP_OP_TOK_EQ,
+ MAC_EXP_OP_STR_NE, MAC_EXP_OP_TOK_NE,
+ MAC_EXP_OP_STR_LT, MAC_EXP_OP_TOK_LT,
+ MAC_EXP_OP_STR_LE, MAC_EXP_OP_TOK_LE,
+ MAC_EXP_OP_STR_GE, MAC_EXP_OP_TOK_GE,
+ MAC_EXP_OP_STR_GT, MAC_EXP_OP_TOK_GT,
+ 0, MAC_EXP_OP_TOK_NONE,
+};
+
+ /*
+ * The whitespace separator set.
+ */
+#define MAC_EXP_WHITESPACE CHARS_SPACE
+
+/* atol_or_die - convert or die */
+
+static long atol_or_die(const char *strval)
+{
+ long result;
+ char *remainder;
+
+ errno = 0;
+ result = strtol(strval, &remainder, 10);
+ if (*strval == 0 /* can't happen */ || *remainder != 0 || errno == ERANGE)
+ msg_fatal("mac_exp_eval: bad conversion: %s", strval);
+ return (result);
+}
+
+/* mac_exp_eval - evaluate binary expression */
+
+static int mac_exp_eval(const char *left, int tok_val,
+ const char *rite)
+{
+ static const char myname[] = "mac_exp_eval";
+ long delta;
+
+ /*
+ * Numerical or string comparison.
+ */
+ if (alldig(left) && alldig(rite)) {
+ delta = atol_or_die(left) - atol_or_die(rite);
+ } else {
+ delta = strcmp(left, rite);
+ }
+ switch (tok_val) {
+ case MAC_EXP_OP_TOK_EQ:
+ return (delta == 0);
+ case MAC_EXP_OP_TOK_NE:
+ return (delta != 0);
+ case MAC_EXP_OP_TOK_LT:
+ return (delta < 0);
+ case MAC_EXP_OP_TOK_LE:
+ return (delta <= 0);
+ case MAC_EXP_OP_TOK_GE:
+ return (delta >= 0);
+ case MAC_EXP_OP_TOK_GT:
+ return (delta > 0);
+ default:
+ msg_panic("%s: unknown operator: %d",
+ myname, tok_val);
+ }
+}
+
+/* mac_exp_parse_error - report parse error, set error flag, return status */
+
+static int PRINTFLIKE(2, 3) mac_exp_parse_error(MAC_EXP_CONTEXT *mc,
+ const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_warn(fmt, ap);
+ va_end(ap);
+ return (mc->status |= MAC_PARSE_ERROR);
+};
+
+/* MAC_EXP_ERR_RETURN - report parse error, set error flag, return status */
+
+#define MAC_EXP_ERR_RETURN(mc, fmt, ...) do { \
+ return (mac_exp_parse_error(mc, fmt, __VA_ARGS__)); \
+ } while (0)
+
+ /*
+ * Postfix 3.0 introduces support for {text} operands. Only with these do we
+ * support the ternary ?: operator and relational operators.
+ *
+ * We cannot support operators in random text, because that would break Postfix
+ * 2.11 compatibility. For example, with the expression "${name?value}", the
+ * value is random text that may contain ':', '?', '{' and '}' characters.
+ * In particular, with Postfix 2.2 .. 2.11, "${name??foo:{b}ar}" evaluates
+ * to "?foo:{b}ar" or empty. There are explicit tests in this directory and
+ * the postconf directory to ensure that Postfix 2.11 compatibility is
+ * maintained.
+ *
+ * Ideally, future Postfix configurations enclose random text operands inside
+ * {} braces. These allow whitespace around operands, which improves
+ * readability.
+ */
+
+/* MAC_EXP_FIND_LEFT_CURLY - skip over whitespace to '{', advance read ptr */
+
+#define MAC_EXP_FIND_LEFT_CURLY(len, cp) \
+ ((cp[len = strspn(cp, MAC_EXP_WHITESPACE)] == '{') ? \
+ (cp += len) : 0)
+
+/* mac_exp_extract_curly_payload - balance {}, skip whitespace, return payload */
+
+static char *mac_exp_extract_curly_payload(MAC_EXP_CONTEXT *mc, char **bp)
+{
+ char *payload;
+ char *cp;
+ int level;
+ int ch;
+
+ /*
+ * Extract the payload and balance the {}. The caller is expected to skip
+ * leading whitespace before the {. See MAC_EXP_FIND_LEFT_CURLY().
+ */
+ for (level = 1, cp = *bp, payload = ++cp; /* see below */ ; cp++) {
+ if ((ch = *cp) == 0) {
+ mac_exp_parse_error(mc, "unbalanced {} in attribute expression: "
+ "\"%s\"",
+ *bp);
+ return (0);
+ } else if (ch == '{') {
+ level++;
+ } else if (ch == '}') {
+ if (--level <= 0)
+ break;
+ }
+ }
+ *cp++ = 0;
+
+ /*
+ * Skip trailing whitespace after }.
+ */
+ *bp = cp + strspn(cp, MAC_EXP_WHITESPACE);
+ return (payload);
+}
+
+/* mac_exp_parse_relational - parse relational expression, advance read ptr */
+
+static int mac_exp_parse_relational(MAC_EXP_CONTEXT *mc, const char **lookup,
+ char **bp)
+{
+ char *cp = *bp;
+ VSTRING *left_op_buf;
+ VSTRING *rite_op_buf;
+ const char *left_op_strval;
+ const char *rite_op_strval;
+ char *op_pos;
+ char *op_strval;
+ size_t op_len;
+ int op_tokval;
+ int op_result;
+ size_t tmp_len;
+
+ /*
+ * Left operand. The caller is expected to skip leading whitespace before
+ * the {. See MAC_EXP_FIND_LEFT_CURLY().
+ */
+ if ((left_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+
+ /*
+ * Operator. Todo: regexp operator.
+ */
+ op_pos = cp;
+ op_len = strspn(cp, "<>!=?+-*/~&|%"); /* for better diagnostics. */
+ op_strval = mystrndup(cp, op_len);
+ op_tokval = name_code(mac_exp_op_table, NAME_CODE_FLAG_NONE, op_strval);
+ myfree(op_strval);
+ if (op_tokval == MAC_EXP_OP_TOK_NONE)
+ MAC_EXP_ERR_RETURN(mc, "%s expected at: \"...%s}>>>%.20s\"",
+ MAC_EXP_OP_STR_ANY, left_op_strval, cp);
+ cp += op_len;
+
+ /*
+ * Right operand. Todo: syntax may depend on operator.
+ */
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp) == 0)
+ MAC_EXP_ERR_RETURN(mc, "\"{expression}\" expected at: "
+ "\"...{%s} %.*s>>>%.20s\"",
+ left_op_strval, (int) op_len, op_pos, cp);
+ if ((rite_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+
+ /*
+ * Evaluate the relational expression. Todo: regexp support.
+ */
+ mc->status |=
+ mac_expand(left_op_buf = vstring_alloc(100), left_op_strval,
+ mc->flags, mc->filter, mc->lookup, mc->context);
+ mc->status |=
+ mac_expand(rite_op_buf = vstring_alloc(100), rite_op_strval,
+ mc->flags, mc->filter, mc->lookup, mc->context);
+ op_result = mac_exp_eval(vstring_str(left_op_buf), op_tokval,
+ vstring_str(rite_op_buf));
+ vstring_free(left_op_buf);
+ vstring_free(rite_op_buf);
+ if (mc->status & MAC_PARSE_ERROR)
+ return (mc->status);
+
+ /*
+ * Here, we fake up a non-empty or empty parameter value lookup result,
+ * for compatibility with the historical code that looks named parameter
+ * values.
+ */
+ *lookup = (op_result ? MAC_EXP_BVAL_TRUE : MAC_EXP_BVAL_FALSE);
+ *bp = cp;
+ return (0);
+}
+
+/* mac_expand_callback - callback for mac_parse */
+
+static int mac_expand_callback(int type, VSTRING *buf, void *ptr)
+{
+ static const char myname[] = "mac_expand_callback";
+ MAC_EXP_CONTEXT *mc = (MAC_EXP_CONTEXT *) ptr;
+ int lookup_mode;
+ const char *lookup;
+ char *cp;
+ int ch;
+ ssize_t res_len;
+ ssize_t tmp_len;
+ const char *res_iftrue;
+ const char *res_iffalse;
+
+ /*
+ * Sanity check.
+ */
+ if (mc->level++ > 100)
+ mac_exp_parse_error(mc, "unreasonable macro call nesting: \"%s\"",
+ vstring_str(buf));
+ if (mc->status & MAC_PARSE_ERROR)
+ return (mc->status);
+
+ /*
+ * Named parameter or relational expression. In case of a syntax error,
+ * return without doing damage, and issue a warning instead.
+ */
+ if (type == MAC_PARSE_EXPR) {
+
+ cp = vstring_str(buf);
+
+ /*
+ * Relational expression. If recursion is disabled, perform only one
+ * level of $name expansion.
+ */
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) {
+ if (mac_exp_parse_relational(mc, &lookup, &cp) != 0)
+ return (mc->status);
+
+ /*
+ * Look for the ? or : operator.
+ */
+ if ((ch = *cp) != 0) {
+ if (ch != '?' && ch != ':')
+ MAC_EXP_ERR_RETURN(mc, "\"?\" or \":\" expected at: "
+ "\"...}>>>%.20s\"", cp);
+ cp++;
+ }
+ }
+
+ /*
+ * Named parameter.
+ */
+ else {
+ char *start;
+
+ /*
+ * Look for the ? or : operator. In case of a syntax error,
+ * return without doing damage, and issue a warning instead.
+ */
+ start = (cp += strspn(cp, MAC_EXP_WHITESPACE));
+ for ( /* void */ ; /* void */ ; cp++) {
+ if ((ch = cp[tmp_len = strspn(cp, MAC_EXP_WHITESPACE)]) == 0) {
+ *cp = 0;
+ lookup_mode = MAC_EXP_MODE_USE;
+ break;
+ }
+ if (ch == '?' || ch == ':') {
+ *cp++ = 0;
+ cp += tmp_len;
+ lookup_mode = MAC_EXP_MODE_TEST;
+ break;
+ }
+ ch = *cp;
+ if (!ISALNUM(ch) && ch != '_') {
+ MAC_EXP_ERR_RETURN(mc, "attribute name syntax error at: "
+ "\"...%.*s>>>%.20s\"",
+ (int) (cp - vstring_str(buf)),
+ vstring_str(buf), cp);
+ }
+ }
+
+ /*
+ * Look up the named parameter. Todo: allow the lookup function
+ * to specify if the result is safe for $name expanson.
+ */
+ lookup = mc->lookup(start, lookup_mode, mc->context);
+ }
+
+ /*
+ * Return the requested result. After parsing the result operand
+ * following ?, we fall through to parse the result operand following
+ * :. This is necessary with the ternary ?: operator: first, with
+ * MAC_EXP_FLAG_SCAN to parse both result operands with mac_parse(),
+ * and second, to find garbage after any result operand. Without
+ * MAC_EXP_FLAG_SCAN the content of only one of the ?: result
+ * operands will be parsed with mac_parse(); syntax errors in the
+ * other operand will be missed.
+ */
+ switch (ch) {
+ case '?':
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) {
+ if ((res_iftrue = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+ } else {
+ res_iftrue = cp;
+ cp = ""; /* no left-over text */
+ }
+ if ((lookup != 0 && *lookup != 0) || (mc->flags & MAC_EXP_FLAG_SCAN))
+ mc->status |= mac_parse(res_iftrue, mac_expand_callback,
+ (void *) mc);
+ if (*cp == 0) /* end of input, OK */
+ break;
+ if (*cp != ':') /* garbage */
+ MAC_EXP_ERR_RETURN(mc, "\":\" expected at: "
+ "\"...%s}>>>%.20s\"", res_iftrue, cp);
+ cp += 1;
+ /* FALLTHROUGH: do not remove, see comment above. */
+ case ':':
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) {
+ if ((res_iffalse = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+ } else {
+ res_iffalse = cp;
+ cp = ""; /* no left-over text */
+ }
+ if (lookup == 0 || *lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN))
+ mc->status |= mac_parse(res_iffalse, mac_expand_callback,
+ (void *) mc);
+ if (*cp != 0) /* garbage */
+ MAC_EXP_ERR_RETURN(mc, "unexpected input at: "
+ "\"...%s}>>>%.20s\"", res_iffalse, cp);
+ break;
+ case 0:
+ if (lookup == 0) {
+ mc->status |= MAC_PARSE_UNDEF;
+ } else if (*lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) {
+ /* void */ ;
+ } else if (mc->flags & MAC_EXP_FLAG_RECURSE) {
+ vstring_strcpy(buf, lookup);
+ mc->status |= mac_parse(vstring_str(buf), mac_expand_callback,
+ (void *) mc);
+ } else {
+ res_len = VSTRING_LEN(mc->result);
+ vstring_strcat(mc->result, lookup);
+ if (mc->flags & MAC_EXP_FLAG_PRINTABLE) {
+ printable(vstring_str(mc->result) + res_len, '_');
+ } else if (mc->filter) {
+ cp = vstring_str(mc->result) + res_len;
+ while (*(cp += strspn(cp, mc->filter)))
+ *cp++ = '_';
+ }
+ }
+ break;
+ default:
+ msg_panic("%s: unknown operator code %d", myname, ch);
+ }
+ }
+
+ /*
+ * Literal text.
+ */
+ else if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0) {
+ vstring_strcat(mc->result, vstring_str(buf));
+ }
+ mc->level--;
+
+ return (mc->status);
+}
+
+/* mac_expand - expand $name instances */
+
+int mac_expand(VSTRING *result, const char *pattern, int flags,
+ const char *filter,
+ MAC_EXP_LOOKUP_FN lookup, void *context)
+{
+ MAC_EXP_CONTEXT mc;
+ int status;
+
+ /*
+ * Bundle up the request and do the substitutions.
+ */
+ mc.result = result;
+ mc.flags = flags;
+ mc.filter = filter;
+ mc.lookup = lookup;
+ mc.context = context;
+ mc.status = 0;
+ mc.level = 0;
+ if ((flags & (MAC_EXP_FLAG_APPEND | MAC_EXP_FLAG_SCAN)) == 0)
+ VSTRING_RESET(result);
+ status = mac_parse(pattern, mac_expand_callback, (void *) &mc);
+ if ((flags & MAC_EXP_FLAG_SCAN) == 0)
+ VSTRING_TERMINATE(result);
+
+ return (status);
+}
+
+#ifdef TEST
+
+ /*
+ * This code certainly deserves a stand-alone test program.
+ */
+#include <stdlib.h>
+#include <stringops.h>
+#include <htable.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+static const char *lookup(const char *name, int unused_mode, void *context)
+{
+ HTABLE *table = (HTABLE *) context;
+
+ return (htable_find(table, name));
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *buf = vstring_alloc(100);
+ VSTRING *result = vstring_alloc(100);
+ char *cp;
+ char *name;
+ char *value;
+ HTABLE *table;
+ int stat;
+
+ while (!vstream_feof(VSTREAM_IN)) {
+
+ table = htable_create(0);
+
+ /*
+ * Read a block of definitions, terminated with an empty line.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ vstream_printf("<< %s\n", vstring_str(buf));
+ vstream_fflush(VSTREAM_OUT);
+ if (VSTRING_LEN(buf) == 0)
+ break;
+ cp = vstring_str(buf);
+ name = mystrtok(&cp, CHARS_SPACE "=");
+ value = mystrtok(&cp, CHARS_SPACE "=");
+ htable_enter(table, name, value ? mystrdup(value) : 0);
+ }
+
+ /*
+ * Read a block of patterns, terminated with an empty line or EOF.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ vstream_printf("<< %s\n", vstring_str(buf));
+ vstream_fflush(VSTREAM_OUT);
+ if (VSTRING_LEN(buf) == 0)
+ break;
+ cp = vstring_str(buf);
+ VSTRING_RESET(result);
+ stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE,
+ (char *) 0, lookup, (void *) table);
+ vstream_printf("stat=%d result=%s\n", stat, vstring_str(result));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ htable_free(table, myfree);
+ vstream_printf("\n");
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(buf);
+ vstring_free(result);
+ exit(0);
+}
+
+#endif