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