xref: /netbsd-src/external/ibm-public/postfix/dist/src/bounce/bounce_template.c (revision e89934bbf778a6d6d6894877c4da59d0c7835b0f)
1 /*	$NetBSD: bounce_template.c,v 1.2 2017/02/14 01:16:44 christos 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 #ifndef NO_EAI
123 #include <midna_domain.h>
124 #endif
125 
126 /* Global library. */
127 
128 #include <mail_params.h>
129 #include <mail_proto.h>
130 #include <mail_conf.h>
131 #include <is_header.h>
132 
133 /* Application-specific. */
134 
135 #include <bounce_template.h>
136 
137  /*
138   * The following tables implement support for bounce template expansions of
139   * $<parameter_name>_days ($<parameter_name>_hours, etc.). The expansion of
140   * these is the actual parameter value divided by the number of seconds in a
141   * day (hour, etc.), so that we can produce nicely formatted bounce messages
142   * with time values converted into the appropriate units.
143   *
144   * Ideally, the bounce template processor would strip the _days etc. suffix
145   * from the parameter name, and use the parameter name to look up the actual
146   * parameter value and its default value (the default value specifies the
147   * default time unit of that parameter (seconds, minutes, etc.)), and use
148   * this to convert the parameter string value into the corresponding number
149   * of seconds. The bounce template processor would then use the _hours etc.
150   * suffix from the bounce template to divide this number by the number of
151   * seconds in an hour, etc. and produce the number that is needed for the
152   * template.
153   *
154   * Unfortunately, there exists no code to look up default values by parameter
155   * name. If such code existed, then we could do the _days, _hours, etc.
156   * conversion with every main.cf time parameter without having to know in
157   * advance what time parameter names exist.
158   *
159   * So we have to either maintain our own table of all time related main.cf
160   * parameter names and defaults (like the postconf command does) or we make
161   * a special case for a few parameters of special interest.
162   *
163   * We go for the second solution. There are only a few parameters that need
164   * this treatment, and there will be more special cases when individual
165   * queue files get support for individual expiration times, and when other
166   * queue file information needs to be reported in bounce template messages.
167   *
168   * A really lame implementation would simply strip the optional s, h, d, etc.
169   * suffix from the actual (string) parameter value and not do any conversion
170   * at all to hours, days or weeks. But then the information in delay warning
171   * notices could be seriously incorrect.
172   */
173 typedef struct {
174     const char *suffix;			/* days, hours, etc. */
175     int     suffix_len;			/* byte count */
176     int     divisor;			/* divisor */
177 } BOUNCE_TIME_DIVISOR;
178 
179 #define STRING_AND_LEN(x) (x), (sizeof(x) - 1)
180 
181 static const BOUNCE_TIME_DIVISOR time_divisors[] = {
182     STRING_AND_LEN("seconds"), 1,
183     STRING_AND_LEN("minutes"), 60,
184     STRING_AND_LEN("hours"), 60 * 60,
185     STRING_AND_LEN("days"), 24 * 60 * 60,
186     STRING_AND_LEN("weeks"), 7 * 24 * 60 * 60,
187     0, 0,
188 };
189 
190  /*
191   * The few special-case main.cf parameters that have support for _days, etc.
192   * suffixes for automatic conversion when expanded into a bounce template.
193   */
194 typedef struct {
195     const char *param_name;		/* parameter name */
196     int     param_name_len;		/* name length */
197     int    *value;			/* parameter value */
198 } BOUNCE_TIME_PARAMETER;
199 
200 static const BOUNCE_TIME_PARAMETER time_parameter[] = {
201     STRING_AND_LEN(VAR_DELAY_WARN_TIME), &var_delay_warn_time,
202     STRING_AND_LEN(VAR_MAX_QUEUE_TIME), &var_max_queue_time,
203     0, 0,
204 };
205 
206  /*
207   * Parameters whose value may have to be converted to UTF-8 for presentation
208   * purposes.
209   */
210 typedef struct {
211     const char *param_name;		/* parameter name */
212     char  **value;			/* parameter value */
213 } BOUNCE_STR_PARAMETER;
214 
215 static const BOUNCE_STR_PARAMETER str_parameter[] = {
216     VAR_MYHOSTNAME, &var_myhostname,
217     VAR_MYDOMAIN, &var_mydomain,
218     0, 0,
219 };
220 
221  /*
222   * SLMs.
223   */
224 #define STR(x) vstring_str(x)
225 
226 /* bounce_template_create - create one template */
227 
228 BOUNCE_TEMPLATE *bounce_template_create(const BOUNCE_TEMPLATE *prototype)
229 {
230     BOUNCE_TEMPLATE *tp;
231 
232     tp = (BOUNCE_TEMPLATE *) mymalloc(sizeof(*tp));
233     *tp = *prototype;
234     return (tp);
235 }
236 
237 /* bounce_template_free - destroy one template */
238 
239 void    bounce_template_free(BOUNCE_TEMPLATE *tp)
240 {
241     if (tp->buffer) {
242 	myfree(tp->buffer);
243 	myfree((void *) tp->origin);
244     }
245     myfree((void *) tp);
246 }
247 
248 /* bounce_template_reset - reset template to default */
249 
250 static void bounce_template_reset(BOUNCE_TEMPLATE *tp)
251 {
252     myfree(tp->buffer);
253     myfree((void *) tp->origin);
254     *tp = *(tp->prototype);
255 }
256 
257 /* bounce_template_load - override one template */
258 
259 void    bounce_template_load(BOUNCE_TEMPLATE *tp, const char *origin,
260 			             const char *buffer)
261 {
262 
263     /*
264      * Clean up after a previous call.
265      */
266     if (tp->buffer)
267 	bounce_template_reset(tp);
268 
269     /*
270      * Postpone the work of template parsing until it is really needed. Most
271      * bounce service calls never need a template.
272      */
273     if (buffer && origin) {
274 	tp->flags |= BOUNCE_TMPL_FLAG_NEW_BUFFER;
275 	tp->buffer = mystrdup(buffer);
276 	tp->origin = mystrdup(origin);
277     }
278 }
279 
280 /* bounce_template_parse_buffer - initialize template */
281 
282 static void bounce_template_parse_buffer(BOUNCE_TEMPLATE *tp)
283 {
284     char   *tval = tp->buffer;
285     char   *cp;
286     char  **cpp;
287     int     cpp_len;
288     int     cpp_used;
289     int     hlen;
290     char   *hval;
291 
292     /*
293      * Sanity check.
294      */
295     if ((tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) == 0)
296 	msg_panic("bounce_template_parse_buffer: nothing to do here");
297     tp->flags &= ~BOUNCE_TMPL_FLAG_NEW_BUFFER;
298 
299     /*
300      * Discard the unusable template and use the default one instead.
301      */
302 #define CLEANUP_AND_RETURN() do { \
303 	bounce_template_reset(tp); \
304 	return; \
305     } while (0)
306 
307     /*
308      * Parse pseudo-header labels and values.
309      *
310      * XXX EAI: allow UTF8 in template headers when responding to SMTPUTF8
311      * message. Sending SMTPUTF8 in reponse to non-SMTPUTF8 mail would make
312      * no sense.
313      */
314 #define GETLINE(line, buf) \
315         (((line) = (buf)) != 0 ? ((buf) = split_at((buf), '\n'), (line)) : 0)
316 
317     while ((GETLINE(cp, tval)) != 0 && (hlen = is_header(cp)) > 0) {
318 	for (hval = cp + hlen; *hval && (*hval == ':' || ISSPACE(*hval)); hval++)
319 	    *hval = 0;
320 	if (*hval == 0) {
321 	    msg_warn("%s: empty \"%s\" header value in %s template "
322 		     "-- ignoring this template",
323 		     tp->origin, cp, tp->class);
324 	    CLEANUP_AND_RETURN();
325 	}
326 	if (!allascii(hval)) {
327 	    msg_warn("%s: non-ASCII \"%s\" header value in %s template "
328 		     "-- ignoring this template",
329 		     tp->origin, cp, tp->class);
330 	    CLEANUP_AND_RETURN();
331 	}
332 	if (strcasecmp("charset", cp) == 0) {
333 	    tp->mime_charset = hval;
334 	} else if (strcasecmp("from", cp) == 0) {
335 	    tp->from = hval;
336 	} else if (strcasecmp("subject", cp) == 0) {
337 	    tp->subject = hval;
338 	} else if (strcasecmp("postmaster-subject", cp) == 0) {
339 	    if (tp->postmaster_subject == 0) {
340 		msg_warn("%s: inapplicable \"%s\" header label in %s template "
341 			 "-- ignoring this template",
342 			 tp->origin, cp, tp->class);
343 		CLEANUP_AND_RETURN();
344 	    }
345 	    tp->postmaster_subject = hval;
346 	} else {
347 	    msg_warn("%s: unknown \"%s\" header label in %s template "
348 		     "-- ignoring this template",
349 		     tp->origin, cp, tp->class);
350 	    CLEANUP_AND_RETURN();
351 	}
352     }
353 
354     /*
355      * Skip blank lines between header and message text.
356      */
357     while (cp && (*cp == 0 || allspace(cp)))
358 	(void) GETLINE(cp, tval);
359     if (cp == 0) {
360 	msg_warn("%s: missing message text in %s template "
361 		 "-- ignoring this template",
362 		 tp->origin, tp->class);
363 	CLEANUP_AND_RETURN();
364     }
365 
366     /*
367      * Is this 7bit or 8bit text? If the character set is US-ASCII, then
368      * don't allow 8bit text. Don't assume 8bit when charset was changed.
369      */
370 #define NON_ASCII(p) ((p) && *(p) && !allascii((p)))
371 
372     if (NON_ASCII(cp) || NON_ASCII(tval)) {
373 	if (strcasecmp(tp->mime_charset, "us-ascii") == 0) {
374 	    msg_warn("%s: 8-bit message text in %s template",
375 		     tp->origin, tp->class);
376 	    msg_warn("please specify a charset value other than us-ascii");
377 	    msg_warn("-- ignoring this template for now");
378 	    CLEANUP_AND_RETURN();
379 	}
380 	tp->mime_encoding = MAIL_ATTR_ENC_8BIT;
381     }
382 
383     /*
384      * Collect the message text and null-terminate the result.
385      */
386     cpp_len = 10;
387     cpp_used = 0;
388     cpp = (char **) mymalloc(sizeof(*cpp) * cpp_len);
389     while (cp) {
390 	cpp[cpp_used++] = cp;
391 	if (cpp_used >= cpp_len) {
392 	    cpp = (char **) myrealloc((void *) cpp,
393 				      sizeof(*cpp) * 2 * cpp_len);
394 	    cpp_len *= 2;
395 	}
396 	(void) GETLINE(cp, tval);
397     }
398     cpp[cpp_used] = 0;
399     tp->message_text = (const char **) cpp;
400 }
401 
402 /* bounce_template_lookup - lookup $name value */
403 
404 static const char *bounce_template_lookup(const char *key, int unused_mode,
405 					          void *context)
406 {
407     BOUNCE_TEMPLATE *tp = (BOUNCE_TEMPLATE *) context;
408     const BOUNCE_TIME_PARAMETER *bp;
409     const BOUNCE_TIME_DIVISOR *bd;
410     const BOUNCE_STR_PARAMETER *sp;
411     static VSTRING *buf;
412     int     result;
413     const char *asc_val;
414     const char *utf8_val;
415 
416     /*
417      * Look for parameter names that can have a time unit suffix, and scale
418      * the time value according to the suffix.
419      */
420     for (bp = time_parameter; bp->param_name; bp++) {
421 	if (strncmp(key, bp->param_name, bp->param_name_len) == 0
422 	    && key[bp->param_name_len] == '_') {
423 	    for (bd = time_divisors; bd->suffix; bd++) {
424 		if (strcmp(key + bp->param_name_len + 1, bd->suffix) == 0) {
425 		    result = bp->value[0] / bd->divisor;
426 		    if (result > 999 && bd->divisor < 86400) {
427 			msg_warn("%s: excessive result \"%d\" in %s "
428 				 "template conversion of parameter \"%s\"",
429 				 tp->origin, result, tp->class, key);
430 			msg_warn("please increase time unit \"%s\" of \"%s\" "
431 			      "in %s template", bd->suffix, key, tp->class);
432 			msg_warn("for instructions see the bounce(5) manual");
433 		    } else if (result == 0 && bp->value[0] && bd->divisor > 1) {
434 			msg_warn("%s: zero result in %s template "
435 				 "conversion of parameter \"%s\"",
436 				 tp->origin, tp->class, key);
437 			msg_warn("please reduce time unit \"%s\" of \"%s\" "
438 			      "in %s template", bd->suffix, key, tp->class);
439 			msg_warn("for instructions see the bounce(5) manual");
440 		    }
441 		    if (buf == 0)
442 			buf = vstring_alloc(10);
443 		    vstring_sprintf(buf, "%d", result);
444 		    return (STR(buf));
445 		}
446 	    }
447 	    msg_fatal("%s: unrecognized suffix \"%s\" in parameter \"%s\"",
448 		      tp->origin,
449 		      key + bp->param_name_len + 1, key);
450 	}
451     }
452 
453     /*
454      * Look for parameter names that may have to be up-converted for
455      * presentation purposes.
456      */
457 #ifndef NO_EAI
458     if (var_smtputf8_enable) {
459 	for (sp = str_parameter; sp->param_name; sp++) {
460 	    if (strcmp(key, sp->param_name) == 0) {
461 		asc_val = sp->value[0];
462 		if (!allascii(asc_val)) {
463 		    msg_warn("%s: conversion \"%s\" failed: "
464 			     "non-ASCII input value: \"%s\"",
465 			     tp->origin, key, asc_val);
466 		    return (asc_val);
467 		} else if ((utf8_val = midna_domain_to_utf8(asc_val)) == 0) {
468 		    msg_warn("%s: conversion \"%s\" failed: "
469 			     "input value: \"%s\"",
470 			     tp->origin, key, asc_val);
471 		    return (asc_val);
472 		} else {
473 		    return (utf8_val);
474 		}
475 	    }
476 	}
477     }
478 #endif
479     return (mail_conf_lookup_eval(key));
480 }
481 
482 /* bounce_template_headers - send template headers */
483 
484 void    bounce_template_headers(BOUNCE_XP_PRN_FN out_fn, VSTREAM *fp,
485 				        BOUNCE_TEMPLATE *tp,
486 				        const char *rcpt,
487 				        int postmaster_copy)
488 {
489     if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER)
490 	bounce_template_parse_buffer(tp);
491 
492     out_fn(fp, "From: %s", tp->from);
493     out_fn(fp, "Subject: %s", tp->postmaster_subject && postmaster_copy ?
494 	   tp->postmaster_subject : tp->subject);
495     out_fn(fp, "To: %s", rcpt);
496 }
497 
498 /* bounce_template_expand - expand template to stream */
499 
500 void    bounce_template_expand(BOUNCE_XP_PUT_FN out_fn, VSTREAM *fp,
501 			               BOUNCE_TEMPLATE *tp)
502 {
503     VSTRING *buf = vstring_alloc(100);
504     const char **cpp;
505     int     stat;
506 
507     if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER)
508 	bounce_template_parse_buffer(tp);
509 
510     for (cpp = tp->message_text; *cpp; cpp++) {
511 	stat = mac_expand(buf, *cpp, MAC_EXP_FLAG_PRINTABLE, (char *) 0,
512 			  bounce_template_lookup, (void *) tp);
513 	if (stat & MAC_PARSE_ERROR)
514 	    msg_fatal("%s: bad $name syntax in %s template: %s",
515 		      tp->origin, tp->class, *cpp);
516 	if (stat & MAC_PARSE_UNDEF)
517 	    msg_fatal("%s: undefined $name in %s template: %s",
518 		      tp->origin, tp->class, *cpp);
519 	out_fn(fp, STR(buf));
520     }
521     vstring_free(buf);
522 }
523 
524 /* bounce_template_dump - dump template to stream */
525 
526 void    bounce_template_dump(VSTREAM *fp, BOUNCE_TEMPLATE *tp)
527 {
528     const char **cpp;
529 
530     if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER)
531 	bounce_template_parse_buffer(tp);
532 
533     vstream_fprintf(fp, "Charset: %s\n", tp->mime_charset);
534     vstream_fprintf(fp, "From: %s\n", tp->from);
535     vstream_fprintf(fp, "Subject: %s\n", tp->subject);
536     if (tp->postmaster_subject)
537 	vstream_fprintf(fp, "Postmaster-Subject: %s\n",
538 			tp->postmaster_subject);
539     vstream_fprintf(fp, "\n");
540     for (cpp = tp->message_text; *cpp; cpp++)
541 	vstream_fprintf(fp, "%s\n", *cpp);
542 }
543