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