1/*++
2/* NAME
3/*	mac_expand 3
4/* SUMMARY
5/*	attribute expansion
6/* SYNOPSIS
7/*	#include <mac_expand.h>
8/*
9/*	int	mac_expand(result, pattern, flags, filter, lookup, context)
10/*	VSTRING *result;
11/*	const char *pattern;
12/*	int	flags;
13/*	const char *filter;
14/*	const char *lookup(const char *key, int mode, char *context)
15/*	char	*context;
16/* DESCRIPTION
17/*	This module implements parameter-less macro expansions, both
18/*	conditional and unconditional, and both recursive and non-recursive.
19/*
20/*	In this text, an attribute is considered "undefined" when its value
21/*	is a null pointer.  Otherwise, the attribute is considered "defined"
22/*	and is expected to have as value a null-terminated string.
23/*
24/*	The following expansions are implemented:
25/* .IP "$name, ${name}, $(name)"
26/*	Unconditional expansion. If the named attribute value is non-empty, the
27/*	expansion is the value of the named attribute,  optionally subjected
28/*	to further $name expansions.  Otherwise, the expansion is empty.
29/* .IP "${name?text}, $(name?text)"
30/*	Conditional expansion. If the named attribute value is non-empty, the
31/*	expansion is the given text, subjected to another iteration of
32/*	$name expansion.  Otherwise, the expansion is empty.
33/* .IP "${name:text}, $(name:text)"
34/*	Conditional expansion. If the attribute value is empty or undefined,
35/*	the expansion is the given text, subjected to another iteration
36/*	of $name expansion.  Otherwise, the expansion is empty.
37/* .PP
38/*	Arguments:
39/* .IP result
40/*	Storage for the result of expansion. The result is truncated
41/*	upon entry.
42/* .IP pattern
43/*	The string to be expanded.
44/* .IP flags
45/*	Bit-wise OR of zero or more of the following:
46/* .RS
47/* .IP MAC_EXP_FLAG_RECURSE
48/*	Expand macros in lookup results. This should never be done with
49/*	data whose origin is untrusted.
50/* .IP MAC_EXP_FLAG_APPEND
51/*	Append text to the result buffer without truncating it.
52/* .IP MAC_EXP_FLAG_SCAN
53/*	Invoke the call-back function each macro name in the input
54/*	string, including macro names in the values of conditional
55/*	expressions.  Do not expand macros, and do not write to the
56/*	result argument.
57/* .PP
58/*	The constant MAC_EXP_FLAG_NONE specifies a manifest null value.
59/* .RE
60/* .IP filter
61/*	A null pointer, or a null-terminated array of characters that
62/*	are allowed to appear in an expansion. Illegal characters are
63/*	replaced by underscores.
64/* .IP lookup
65/*	The attribute lookup routine. Arguments are: the attribute name,
66/*	MAC_EXP_MODE_TEST to test the existence of the named attribute
67/*	or MAC_EXP_MODE_USE to use the value of the named attribute,
68/*	and the caller context that was given to mac_expand(). A null
69/*	result value means that the requested attribute was not defined.
70/* .IP context
71/*	Caller context that is passed on to the attribute lookup routine.
72/* DIAGNOSTICS
73/*	Fatal errors: out of memory.  Warnings: syntax errors, unreasonable
74/*	macro nesting.
75/*
76/*	The result value is the binary OR of zero or more of the following:
77/* .IP MAC_PARSE_ERROR
78/*	A syntax error was found in \fBpattern\fR, or some macro had
79/*	an unreasonable nesting depth.
80/* .IP MAC_PARSE_UNDEF
81/*	A macro was expanded but its value not defined.
82/* SEE ALSO
83/*	mac_parse(3) locate macro references in string.
84/* LICENSE
85/* .ad
86/* .fi
87/*	The Secure Mailer license must be distributed with this software.
88/* AUTHOR(S)
89/*	Wietse Venema
90/*	IBM T.J. Watson Research
91/*	P.O. Box 704
92/*	Yorktown Heights, NY 10598, USA
93/*--*/
94
95/* System library. */
96
97#include <sys_defs.h>
98#include <ctype.h>
99#include <string.h>
100
101/* Utility library. */
102
103#include <msg.h>
104#include <vstring.h>
105#include <mymalloc.h>
106#include <mac_parse.h>
107#include <mac_expand.h>
108
109 /*
110  * Little helper structure.
111  */
112typedef struct {
113    VSTRING *result;			/* result buffer */
114    int     flags;			/* features */
115    const char *filter;			/* character filter */
116    MAC_EXP_LOOKUP_FN lookup;		/* lookup routine */
117    char   *context;			/* caller context */
118    int     status;			/* findings */
119    int     level;			/* nesting level */
120} MAC_EXP;
121
122/* mac_expand_callback - callback for mac_parse */
123
124static int mac_expand_callback(int type, VSTRING *buf, char *ptr)
125{
126    MAC_EXP *mc = (MAC_EXP *) ptr;
127    int     lookup_mode;
128    const char *text;
129    char   *cp;
130    int     ch;
131    ssize_t len;
132
133    /*
134     * Sanity check.
135     */
136    if (mc->level++ > 100) {
137	msg_warn("unreasonable macro call nesting: \"%s\"", vstring_str(buf));
138	mc->status |= MAC_PARSE_ERROR;
139    }
140    if (mc->status & MAC_PARSE_ERROR)
141	return (mc->status);
142
143    /*
144     * $Name etc. reference.
145     *
146     * In order to support expansion of lookup results, we must save the lookup
147     * result. We use the input buffer since it will not be needed anymore.
148     */
149    if (type == MAC_PARSE_EXPR) {
150
151	/*
152	 * Look for the ? or : delimiter. In case of a syntax error, return
153	 * without doing damage, and issue a warning instead.
154	 */
155	for (cp = vstring_str(buf); /* void */ ; cp++) {
156	    if ((ch = *cp) == 0) {
157		lookup_mode = MAC_EXP_MODE_USE;
158		break;
159	    }
160	    if (ch == '?' || ch == ':') {
161		*cp++ = 0;
162		lookup_mode = MAC_EXP_MODE_TEST;
163		break;
164	    }
165	    if (!ISALNUM(ch) && ch != '_') {
166		msg_warn("macro name syntax error: \"%s\"", vstring_str(buf));
167		mc->status |= MAC_PARSE_ERROR;
168		return (mc->status);
169	    }
170	}
171
172	/*
173	 * Look up the named parameter.
174	 */
175	text = mc->lookup(vstring_str(buf), lookup_mode, mc->context);
176
177	/*
178	 * Perform the requested substitution.
179	 */
180	switch (ch) {
181	case '?':
182	    if ((text != 0 && *text != 0) || (mc->flags & MAC_EXP_FLAG_SCAN))
183		mac_parse(cp, mac_expand_callback, (char *) mc);
184	    break;
185	case ':':
186	    if (text == 0 || *text == 0 || (mc->flags & MAC_EXP_FLAG_SCAN))
187		mac_parse(cp, mac_expand_callback, (char *) mc);
188	    break;
189	default:
190	    if (text == 0) {
191		mc->status |= MAC_PARSE_UNDEF;
192	    } else if (*text == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) {
193		 /* void */ ;
194	    } else if (mc->flags & MAC_EXP_FLAG_RECURSE) {
195		vstring_strcpy(buf, text);
196		mac_parse(vstring_str(buf), mac_expand_callback, (char *) mc);
197	    } else {
198		len = VSTRING_LEN(mc->result);
199		vstring_strcat(mc->result, text);
200		if (mc->filter) {
201		    cp = vstring_str(mc->result) + len;
202		    while (*(cp += strspn(cp, mc->filter)))
203			*cp++ = '_';
204		}
205	    }
206	    break;
207	}
208    }
209
210    /*
211     * Literal text.
212     */
213    else if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0) {
214	vstring_strcat(mc->result, vstring_str(buf));
215    }
216
217    mc->level--;
218
219    return (mc->status);
220}
221
222/* mac_expand - expand $name instances */
223
224int     mac_expand(VSTRING *result, const char *pattern, int flags,
225		           const char *filter,
226		           MAC_EXP_LOOKUP_FN lookup, char *context)
227{
228    MAC_EXP mc;
229    int     status;
230
231    /*
232     * Bundle up the request and do the substitutions.
233     */
234    mc.result = result;
235    mc.flags = flags;
236    mc.filter = filter;
237    mc.lookup = lookup;
238    mc.context = context;
239    mc.status = 0;
240    mc.level = 0;
241    if ((flags & (MAC_EXP_FLAG_APPEND | MAC_EXP_FLAG_SCAN)) == 0)
242	VSTRING_RESET(result);
243    status = mac_parse(pattern, mac_expand_callback, (char *) &mc);
244    if ((flags & MAC_EXP_FLAG_SCAN) == 0)
245	VSTRING_TERMINATE(result);
246
247    return (status);
248}
249
250#ifdef TEST
251
252 /*
253  * This code certainly deserves a stand-alone test program.
254  */
255#include <stdlib.h>
256#include <stringops.h>
257#include <htable.h>
258#include <vstream.h>
259#include <vstring_vstream.h>
260
261static const char *lookup(const char *name, int unused_mode, char *context)
262{
263    HTABLE *table = (HTABLE *) context;
264
265    return (htable_find(table, name));
266}
267
268int     main(int unused_argc, char **unused_argv)
269{
270    VSTRING *buf = vstring_alloc(100);
271    VSTRING *result = vstring_alloc(100);
272    char   *cp;
273    char   *name;
274    char   *value;
275    HTABLE *table;
276    int     stat;
277
278    while (!vstream_feof(VSTREAM_IN)) {
279
280	table = htable_create(0);
281
282	/*
283	 * Read a block of definitions, terminated with an empty line.
284	 */
285	while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
286	    vstream_printf("<< %s\n", vstring_str(buf));
287	    vstream_fflush(VSTREAM_OUT);
288	    if (VSTRING_LEN(buf) == 0)
289		break;
290	    cp = vstring_str(buf);
291	    name = mystrtok(&cp, " \t\r\n=");
292	    value = mystrtok(&cp, " \t\r\n=");
293	    htable_enter(table, name, value ? mystrdup(value) : 0);
294	}
295
296	/*
297	 * Read a block of patterns, terminated with an empty line or EOF.
298	 */
299	while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
300	    vstream_printf("<< %s\n", vstring_str(buf));
301	    vstream_fflush(VSTREAM_OUT);
302	    if (VSTRING_LEN(buf) == 0)
303		break;
304	    cp = vstring_str(buf);
305	    VSTRING_RESET(result);
306	    stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE,
307			      (char *) 0, lookup, (char *) table);
308	    vstream_printf("stat=%d result=%s\n", stat, vstring_str(result));
309	    vstream_fflush(VSTREAM_OUT);
310	}
311	htable_free(table, myfree);
312	vstream_printf("\n");
313    }
314
315    /*
316     * Clean up.
317     */
318    vstring_free(buf);
319    vstring_free(result);
320    exit(0);
321}
322
323#endif
324