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