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