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