xref: /netbsd-src/external/ibm-public/postfix/dist/src/bounce/bounce_template.c (revision 10ad5ffa714ce1a679dcc9dd8159648df2d67b5a)
1 /*	$NetBSD: bounce_template.c,v 1.1.1.1 2009/06/23 10:08:42 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	bounce_template 3
6 /* SUMMARY
7 /*	bounce template support
8 /* SYNOPSIS
9 /*	#include <bounce_template.h>
10 /*
11 /*	BOUNCE_TEMPLATE *bounce_template_create(def_template)
12 /*	const BOUNCE_TEMPLATE *def_template;
13 /*
14 /*	void	bounce_template_free(template)
15 /*	BOUNCE_TEMPLATE *template;
16 /*
17 /*	void	bounce_template_load(template, stream, buffer, origin)
18 /*	BOUNCE_TEMPLATE *template;
19 /*	VSTREAM	*stream;
20 /*	const char *buffer;
21 /*	const char *origin;
22 /*
23 /*	void	bounce_template_headers(out_fn, stream, template,
24 /*					rcpt, postmaster_copy)
25 /*	int	(*out_fn)(VSTREAM *, const char *, ...);
26 /*	VSTREAM	*stream;
27 /*	BOUNCE_TEMPLATE *template;
28 /*	const char *rcpt;
29 /*	int	postmaster_copy;
30 /*
31 /*	const char *bounce_template_encoding(template)
32 /*	BOUNCE_TEMPLATE *template;
33 /*
34 /*	const char *bounce_template_charset(template)
35 /*	BOUNCE_TEMPLATE *template;
36 /*
37 /*	void	bounce_template_expand(out_fn, stream, template)
38 /*	int	(*out_fn)(VSTREAM *, const char *);
39 /*	VSTREAM	*stream;
40 /*	BOUNCE_TEMPLATE *template;
41 /*
42 /*	void	bounce_template_dump(stream, template)
43 /*	VSTREAM	*stream;
44 /*	BOUNCE_TEMPLATE *template;
45 /*
46 /*	int	IS_FAILURE_TEMPLATE(template)
47 /*	int	IS_DELAY_TEMPLATE(template)
48 /*	int	IS_SUCCESS_TEMPLATE(template)
49 /*	int	IS_VERIFY_TEMPLATE(template)
50 /*	BOUNCE_TEMPLATE *template;
51 /* DESCRIPTION
52 /*	This module implements the built-in and external bounce
53 /*	message template support. The content of a template are
54 /*	private. To access information within a template, use
55 /*	the API described in this document.
56 /*
57 /*	bounce_template_create() creates a template, with the
58 /*	specified default settings. The template defaults are not
59 /*	copied.
60 /*
61 /*	bounce_template_free() destroys a bounce message template.
62 /*
63 /*	bounce_template_load() overrides a bounce template with the
64 /*	specified buffer from the specified origin. The buffer and
65 /*	origin are copied. Specify a null buffer and origin pointer
66 /*	to reset the template to the defaults specified with
67 /*	bounce_template_create().
68 /*
69 /*	bounce_template_headers() sends the postmaster or non-postmaster
70 /*	From/Subject/To message headers to the specified stream.
71 /*	The recipient address is expected to be in RFC822 external
72 /*	form. The postmaster_copy argument is one of POSTMASTER_COPY
73 /*	or NO_POSTMASTER_COPY.
74 /*
75 /*	bounce_template_encoding() returns the encoding (MAIL_ATTR_ENC_7BIT
76 /*	or MAIL_ATTR_ENC_8BIT) for the bounce template message text.
77 /*
78 /*	bounce_template_charset() returns the character set for the
79 /*	bounce template message text.
80 /*
81 /*	bounce_template_expand() expands the body text of the
82 /*	specified template and writes the result to the specified
83 /*	stream.
84 /*
85 /*	bounce_template_dump() dumps the specified template to the
86 /*	specified stream.
87 /*
88 /*	The IS_MUMBLE_TEMPLATE() macros are predicates that
89 /*	determine whether the template is of the specified type.
90 /* DIAGNOSTICS
91 /*	Fatal error: out of memory, undefined macro name in template.
92 /* SEE ALSO
93 /*	bounce_templates(3) bounce template group support
94 /* LICENSE
95 /* .ad
96 /* .fi
97 /*	The Secure Mailer license must be distributed with this software.
98 /* AUTHOR(S)
99 /*	Wietse Venema
100 /*	IBM T.J. Watson Research
101 /*	P.O. Box 704
102 /*	Yorktown Heights, NY 10598, USA
103 /*--*/
104 
105 /* System library. */
106 
107 #include <sys_defs.h>
108 #include <string.h>
109 #include <ctype.h>
110 
111 #ifdef STRCASECMP_IN_STRINGS_H
112 #include <strings.h>
113 #endif
114 
115 /* Utility library. */
116 
117 #include <msg.h>
118 #include <mac_expand.h>
119 #include <split_at.h>
120 #include <stringops.h>
121 #include <mymalloc.h>
122 
123 /* Global library. */
124 
125 #include <mail_params.h>
126 #include <mail_proto.h>
127 #include <mail_conf.h>
128 #include <is_header.h>
129 
130 /* Application-specific. */
131 
132 #include <bounce_template.h>
133 
134  /*
135   * The following tables implement support for bounce template expansions of
136   * $<parameter_name>_days ($<parameter_name>_hours, etc.). The expansion of
137   * these is the actual parameter value divided by the number of seconds in a
138   * day (hour, etc.), so that we can produce nicely formatted bounce messages
139   * with time values converted into the appropriate units.
140   *
141   * Ideally, the bounce template processor would strip the _days etc. suffix
142   * from the parameter name, and use the parameter name to look up the actual
143   * parameter value and its default value (the default value specifies the
144   * default time unit of that parameter (seconds, minutes, etc.)), and use
145   * this to convert the parameter string value into the corresponding number
146   * of seconds. The bounce template processor would then use the _hours etc.
147   * suffix from the bounce template to divide this number by the number of
148   * seconds in an hour, etc. and produce the number that is needed for the
149   * template.
150   *
151   * Unfortunately, there exists no code to look up default values by parameter
152   * name. If such code existed, then we could do the _days, _hours, etc.
153   * conversion with every main.cf time parameter without having to know in
154   * advance what time parameter names exist.
155   *
156   * So we have to either maintain our own table of all time related main.cf
157   * parameter names and defaults (like the postconf command does) or we make
158   * a special case for a few parameters of special interest.
159   *
160   * We go for the second solution. There are only a few parameters that need
161   * this treatment, and there will be more special cases when individual
162   * queue files get support for individual expiration times, and when other
163   * queue file information needs to be reported in bounce template messages.
164   *
165   * A really lame implementation would simply strip the optional s, h, d, etc.
166   * suffix from the actual (string) parameter value and not do any conversion
167   * at all to hours, days or weeks. But then the information in delay warning
168   * notices could be seriously incorrect.
169   */
170 typedef struct {
171     const char *suffix;			/* days, hours, etc. */
172     int     suffix_len;			/* byte count */
173     int     divisor;			/* divisor */
174 } BOUNCE_TIME_DIVISOR;
175 
176 #define STRING_AND_LEN(x) (x), (sizeof(x) - 1)
177 
178 static const BOUNCE_TIME_DIVISOR time_divisors[] = {
179     STRING_AND_LEN("seconds"), 1,
180     STRING_AND_LEN("minutes"), 60,
181     STRING_AND_LEN("hours"), 60 * 60,
182     STRING_AND_LEN("days"), 24 * 60 * 60,
183     STRING_AND_LEN("weeks"), 7 * 24 * 60 * 60,
184     0, 0,
185 };
186 
187  /*
188   * The few special-case main.cf parameters that have support for _days, etc.
189   * suffixes for automatic conversion when expanded into a bounce template.
190   */
191 typedef struct {
192     const char *param_name;		/* parameter name */
193     int     param_name_len;		/* name length */
194     int    *value;			/* parameter value */
195 } BOUNCE_TIME_PARAMETER;
196 
197 static const BOUNCE_TIME_PARAMETER time_parameter[] = {
198     STRING_AND_LEN(VAR_DELAY_WARN_TIME), &var_delay_warn_time,
199     STRING_AND_LEN(VAR_MAX_QUEUE_TIME), &var_max_queue_time,
200     0, 0,
201 };
202 
203  /*
204   * SLMs.
205   */
206 #define STR(x) vstring_str(x)
207 
208 /* bounce_template_create - create one template */
209 
210 BOUNCE_TEMPLATE *bounce_template_create(const BOUNCE_TEMPLATE *prototype)
211 {
212     BOUNCE_TEMPLATE *tp;
213 
214     tp = (BOUNCE_TEMPLATE *) mymalloc(sizeof(*tp));
215     *tp = *prototype;
216     return (tp);
217 }
218 
219 /* bounce_template_free - destroy one template */
220 
221 void    bounce_template_free(BOUNCE_TEMPLATE *tp)
222 {
223     if (tp->buffer) {
224 	myfree(tp->buffer);
225 	myfree((char *) tp->origin);
226     }
227     myfree((char *) tp);
228 }
229 
230 /* bounce_template_reset - reset template to default */
231 
232 static void bounce_template_reset(BOUNCE_TEMPLATE *tp)
233 {
234     myfree(tp->buffer);
235     myfree((char *) tp->origin);
236     *tp = *(tp->prototype);
237 }
238 
239 /* bounce_template_load - override one template */
240 
241 void    bounce_template_load(BOUNCE_TEMPLATE *tp, const char *origin,
242 			             const char *buffer)
243 {
244 
245     /*
246      * Clean up after a previous call.
247      */
248     if (tp->buffer)
249 	bounce_template_reset(tp);
250 
251     /*
252      * Postpone the work of template parsing until it is really needed. Most
253      * bounce service calls never need a template.
254      */
255     if (buffer && origin) {
256 	tp->flags |= BOUNCE_TMPL_FLAG_NEW_BUFFER;
257 	tp->buffer = mystrdup(buffer);
258 	tp->origin = mystrdup(origin);
259     }
260 }
261 
262 /* bounce_template_parse_buffer - initialize template */
263 
264 static void bounce_template_parse_buffer(BOUNCE_TEMPLATE *tp)
265 {
266     char   *tval = tp->buffer;
267     char   *cp;
268     char  **cpp;
269     int     cpp_len;
270     int     cpp_used;
271     int     hlen;
272     char   *hval;
273 
274     /*
275      * Sanity check.
276      */
277     if ((tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) == 0)
278 	msg_panic("bounce_template_parse_buffer: nothing to do here");
279     tp->flags &= ~BOUNCE_TMPL_FLAG_NEW_BUFFER;
280 
281     /*
282      * Discard the unusable template and use the default one instead.
283      */
284 #define CLEANUP_AND_RETURN() do { \
285 	bounce_template_reset(tp); \
286 	return; \
287     } while (0)
288 
289     /*
290      * Parse pseudo-header labels and values.
291      */
292 #define GETLINE(line, buf) \
293         (((line) = (buf)) != 0 ? ((buf) = split_at((buf), '\n'), (line)) : 0)
294 
295     while ((GETLINE(cp, tval)) != 0 && (hlen = is_header(cp)) > 0) {
296 	for (hval = cp + hlen; *hval && (*hval == ':' || ISSPACE(*hval)); hval++)
297 	    *hval = 0;
298 	if (*hval == 0) {
299 	    msg_warn("%s: empty \"%s\" header value in %s template "
300 		     "-- ignoring this template",
301 		     tp->origin, cp, tp->class);
302 	    CLEANUP_AND_RETURN();
303 	}
304 	if (!allascii(hval)) {
305 	    msg_warn("%s: non-ASCII \"%s\" header value in %s template "
306 		     "-- ignoring this template",
307 		     tp->origin, cp, tp->class);
308 	    CLEANUP_AND_RETURN();
309 	}
310 	if (strcasecmp("charset", cp) == 0) {
311 	    tp->mime_charset = hval;
312 	} else if (strcasecmp("from", cp) == 0) {
313 	    tp->from = hval;
314 	} else if (strcasecmp("subject", cp) == 0) {
315 	    tp->subject = hval;
316 	} else if (strcasecmp("postmaster-subject", cp) == 0) {
317 	    if (tp->postmaster_subject == 0) {
318 		msg_warn("%s: inapplicable \"%s\" header label in %s template "
319 			 "-- ignoring this template",
320 			 tp->origin, cp, tp->class);
321 		CLEANUP_AND_RETURN();
322 	    }
323 	    tp->postmaster_subject = hval;
324 	} else {
325 	    msg_warn("%s: unknown \"%s\" header label in %s template "
326 		     "-- ignoring this template",
327 		     tp->origin, cp, tp->class);
328 	    CLEANUP_AND_RETURN();
329 	}
330     }
331 
332     /*
333      * Skip blank lines between header and message text.
334      */
335     while (cp && (*cp == 0 || allspace(cp)))
336 	(void) GETLINE(cp, tval);
337     if (cp == 0) {
338 	msg_warn("%s: missing message text in %s template "
339 		 "-- ignoring this template",
340 		 tp->origin, tp->class);
341 	CLEANUP_AND_RETURN();
342     }
343 
344     /*
345      * Is this 7bit or 8bit text? If the character set is US-ASCII, then
346      * don't allow 8bit text. Don't assume 8bit when charset was changed.
347      */
348 #define NON_ASCII(p) ((p) && *(p) && !allascii((p)))
349 
350     if (NON_ASCII(cp) || NON_ASCII(tval)) {
351 	if (strcasecmp(tp->mime_charset, "us-ascii") == 0) {
352 	    msg_warn("%s: 8-bit message text in %s template",
353 		     tp->origin, tp->class);
354 	    msg_warn("please specify a charset value other than us-ascii");
355 	    msg_warn("-- ignoring this template for now");
356 	    CLEANUP_AND_RETURN();
357 	}
358 	tp->mime_encoding = MAIL_ATTR_ENC_8BIT;
359     }
360 
361     /*
362      * Collect the message text and null-terminate the result.
363      */
364     cpp_len = 10;
365     cpp_used = 0;
366     cpp = (char **) mymalloc(sizeof(*cpp) * cpp_len);
367     while (cp) {
368 	cpp[cpp_used++] = cp;
369 	if (cpp_used >= cpp_len) {
370 	    cpp = (char **) myrealloc((char *) cpp,
371 				      sizeof(*cpp) * 2 * cpp_len);
372 	    cpp_len *= 2;
373 	}
374 	(void) GETLINE(cp, tval);
375     }
376     cpp[cpp_used] = 0;
377     tp->message_text = (const char **) cpp;
378 }
379 
380 /* bounce_template_lookup - lookup $name value */
381 
382 static const char *bounce_template_lookup(const char *key, int unused_mode,
383 					          char *context)
384 {
385     BOUNCE_TEMPLATE *tp = (BOUNCE_TEMPLATE *) context;
386     const BOUNCE_TIME_PARAMETER *bp;
387     const BOUNCE_TIME_DIVISOR *bd;
388     static VSTRING *buf;
389     int     result;
390 
391     /*
392      * Look for parameter names that can have a time unit suffix, and scale
393      * the time value according to the suffix.
394      */
395     for (bp = time_parameter; bp->param_name; bp++) {
396 	if (strncmp(key, bp->param_name, bp->param_name_len) == 0
397 	    && key[bp->param_name_len] == '_') {
398 	    for (bd = time_divisors; bd->suffix; bd++) {
399 		if (strcmp(key + bp->param_name_len + 1, bd->suffix) == 0) {
400 		    result = bp->value[0] / bd->divisor;
401 		    if (result > 999 && bd->divisor < 86400) {
402 			msg_warn("%s: excessive result \"%d\" in %s "
403 				 "template conversion of parameter \"%s\"",
404 				 tp->origin, result, tp->class, key);
405 			msg_warn("please increase time unit \"%s\" of \"%s\" "
406 			      "in %s template", bd->suffix, key, tp->class);
407 			msg_warn("for instructions see the bounce(5) manual");
408 		    } else if (result == 0 && bp->value[0] && bd->divisor > 1) {
409 			msg_warn("%s: zero result in %s template "
410 				 "conversion of parameter \"%s\"",
411 				 tp->origin, tp->class, key);
412 			msg_warn("please reduce time unit \"%s\" of \"%s\" "
413 			      "in %s template", bd->suffix, key, tp->class);
414 			msg_warn("for instructions see the bounce(5) manual");
415 		    }
416 		    if (buf == 0)
417 			buf = vstring_alloc(10);
418 		    vstring_sprintf(buf, "%d", result);
419 		    return (STR(buf));
420 		}
421 	    }
422 	    msg_fatal("%s: unrecognized suffix \"%s\" in parameter \"%s\"",
423 		      tp->origin,
424 		      key + bp->param_name_len + 1, key);
425 	}
426     }
427     return (mail_conf_lookup_eval(key));
428 }
429 
430 /* bounce_template_headers - send template headers */
431 
432 void    bounce_template_headers(BOUNCE_XP_PRN_FN out_fn, VSTREAM *fp,
433 				        BOUNCE_TEMPLATE *tp,
434 				        const char *rcpt,
435 				        int postmaster_copy)
436 {
437     if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER)
438 	bounce_template_parse_buffer(tp);
439 
440     out_fn(fp, "From: %s", tp->from);
441     out_fn(fp, "Subject: %s", tp->postmaster_subject && postmaster_copy ?
442 	   tp->postmaster_subject : tp->subject);
443     out_fn(fp, "To: %s", rcpt);
444 }
445 
446 /* bounce_template_expand - expand template to stream */
447 
448 void    bounce_template_expand(BOUNCE_XP_PUT_FN out_fn, VSTREAM *fp,
449 			               BOUNCE_TEMPLATE *tp)
450 {
451     VSTRING *buf = vstring_alloc(100);
452     const char **cpp;
453     int     stat;
454     const char *filter = "\t !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
455 
456     if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER)
457 	bounce_template_parse_buffer(tp);
458 
459     for (cpp = tp->message_text; *cpp; cpp++) {
460 	stat = mac_expand(buf, *cpp, MAC_EXP_FLAG_NONE, filter,
461 			  bounce_template_lookup, (char *) tp);
462 	if (stat & MAC_PARSE_ERROR)
463 	    msg_fatal("%s: bad $name syntax in %s template: %s",
464 		      tp->origin, tp->class, *cpp);
465 	if (stat & MAC_PARSE_UNDEF)
466 	    msg_fatal("%s: undefined $name in %s template: %s",
467 		      tp->origin, tp->class, *cpp);
468 	out_fn(fp, STR(buf));
469     }
470     vstring_free(buf);
471 }
472 
473 /* bounce_template_dump - dump template to stream */
474 
475 void    bounce_template_dump(VSTREAM *fp, BOUNCE_TEMPLATE *tp)
476 {
477     const char **cpp;
478 
479     if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER)
480 	bounce_template_parse_buffer(tp);
481 
482     vstream_fprintf(fp, "Charset: %s\n", tp->mime_charset);
483     vstream_fprintf(fp, "From: %s\n", tp->from);
484     vstream_fprintf(fp, "Subject: %s\n", tp->subject);
485     if (tp->postmaster_subject)
486 	vstream_fprintf(fp, "Postmaster-Subject: %s\n",
487 			tp->postmaster_subject);
488     vstream_fprintf(fp, "\n");
489     for (cpp = tp->message_text; *cpp; cpp++)
490 	vstream_fprintf(fp, "%s\n", *cpp);
491 }
492