Home | History | Annotate | Line # | Download | only in bounce
      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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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