diff options
Diffstat (limited to 'src/bounce/bounce_template.c')
-rw-r--r-- | src/bounce/bounce_template.c | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/src/bounce/bounce_template.c b/src/bounce/bounce_template.c new file mode 100644 index 0000000..67a5cf7 --- /dev/null +++ b/src/bounce/bounce_template.c @@ -0,0 +1,548 @@ +/*++ +/* NAME +/* bounce_template 3 +/* SUMMARY +/* bounce template support +/* SYNOPSIS +/* #include <bounce_template.h> +/* +/* BOUNCE_TEMPLATE *bounce_template_create(def_template) +/* const BOUNCE_TEMPLATE *def_template; +/* +/* void bounce_template_free(template) +/* BOUNCE_TEMPLATE *template; +/* +/* void bounce_template_load(template, stream, buffer, origin) +/* BOUNCE_TEMPLATE *template; +/* VSTREAM *stream; +/* const char *buffer; +/* const char *origin; +/* +/* void bounce_template_headers(out_fn, stream, template, +/* rcpt, postmaster_copy) +/* int (*out_fn)(VSTREAM *, const char *, ...); +/* VSTREAM *stream; +/* BOUNCE_TEMPLATE *template; +/* const char *rcpt; +/* int postmaster_copy; +/* +/* const char *bounce_template_encoding(template) +/* BOUNCE_TEMPLATE *template; +/* +/* const char *bounce_template_charset(template) +/* BOUNCE_TEMPLATE *template; +/* +/* void bounce_template_expand(out_fn, stream, template) +/* int (*out_fn)(VSTREAM *, const char *); +/* VSTREAM *stream; +/* BOUNCE_TEMPLATE *template; +/* +/* void bounce_template_dump(stream, template) +/* VSTREAM *stream; +/* BOUNCE_TEMPLATE *template; +/* +/* int IS_FAILURE_TEMPLATE(template) +/* int IS_DELAY_TEMPLATE(template) +/* int IS_SUCCESS_TEMPLATE(template) +/* BOUNCE_TEMPLATE *template; +/* DESCRIPTION +/* This module implements the built-in and external bounce +/* message template support. The content of a template are +/* private. To access information within a template, use +/* the API described in this document. +/* +/* bounce_template_create() creates a template, with the +/* specified default settings. The template defaults are not +/* copied. +/* +/* bounce_template_free() destroys a bounce message template. +/* +/* bounce_template_load() overrides a bounce template with the +/* specified buffer from the specified origin. The buffer and +/* origin are copied. Specify a null buffer and origin pointer +/* to reset the template to the defaults specified with +/* bounce_template_create(). +/* +/* bounce_template_headers() sends the postmaster or non-postmaster +/* From/Subject/To message headers to the specified stream. +/* The recipient address is expected to be in RFC822 external +/* form. The postmaster_copy argument is one of POSTMASTER_COPY +/* or NO_POSTMASTER_COPY. +/* +/* bounce_template_encoding() returns the encoding (MAIL_ATTR_ENC_7BIT +/* or MAIL_ATTR_ENC_8BIT) for the bounce template message text. +/* +/* bounce_template_charset() returns the character set for the +/* bounce template message text. +/* +/* bounce_template_expand() expands the body text of the +/* specified template and writes the result to the specified +/* stream. +/* +/* bounce_template_dump() dumps the specified template to the +/* specified stream. +/* +/* The IS_MUMBLE_TEMPLATE() macros are predicates that +/* determine whether the template is of the specified type. +/* DIAGNOSTICS +/* Fatal error: out of memory, undefined macro name in template. +/* SEE ALSO +/* bounce_templates(3) bounce template group support +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <ctype.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mac_expand.h> +#include <split_at.h> +#include <stringops.h> +#include <mymalloc.h> +#ifndef NO_EAI +#include <midna_domain.h> +#endif + +/* Global library. */ + +#include <mail_params.h> +#include <mail_proto.h> +#include <mail_conf.h> +#include <is_header.h> +#include <hfrom_format.h> + +/* Application-specific. */ + +#include <bounce_template.h> +#include <bounce_service.h> + + /* + * The following tables implement support for bounce template expansions of + * $<parameter_name>_days ($<parameter_name>_hours, etc.). The expansion of + * these is the actual parameter value divided by the number of seconds in a + * day (hour, etc.), so that we can produce nicely formatted bounce messages + * with time values converted into the appropriate units. + * + * Ideally, the bounce template processor would strip the _days etc. suffix + * from the parameter name, and use the parameter name to look up the actual + * parameter value and its default value (the default value specifies the + * default time unit of that parameter (seconds, minutes, etc.)), and use + * this to convert the parameter string value into the corresponding number + * of seconds. The bounce template processor would then use the _hours etc. + * suffix from the bounce template to divide this number by the number of + * seconds in an hour, etc. and produce the number that is needed for the + * template. + * + * Unfortunately, there exists no code to look up default values by parameter + * name. If such code existed, then we could do the _days, _hours, etc. + * conversion with every main.cf time parameter without having to know in + * advance what time parameter names exist. + * + * So we have to either maintain our own table of all time related main.cf + * parameter names and defaults (like the postconf command does) or we make + * a special case for a few parameters of special interest. + * + * We go for the second solution. There are only a few parameters that need + * this treatment, and there will be more special cases when individual + * queue files get support for individual expiration times, and when other + * queue file information needs to be reported in bounce template messages. + * + * A really lame implementation would simply strip the optional s, h, d, etc. + * suffix from the actual (string) parameter value and not do any conversion + * at all to hours, days or weeks. But then the information in delay warning + * notices could be seriously incorrect. + */ +typedef struct { + const char *suffix; /* days, hours, etc. */ + int suffix_len; /* byte count */ + int divisor; /* divisor */ +} BOUNCE_TIME_DIVISOR; + +#define STRING_AND_LEN(x) (x), (sizeof(x) - 1) + +static const BOUNCE_TIME_DIVISOR time_divisors[] = { + STRING_AND_LEN("seconds"), 1, + STRING_AND_LEN("minutes"), 60, + STRING_AND_LEN("hours"), 60 * 60, + STRING_AND_LEN("days"), 24 * 60 * 60, + STRING_AND_LEN("weeks"), 7 * 24 * 60 * 60, + 0, 0, +}; + + /* + * The few special-case main.cf parameters that have support for _days, etc. + * suffixes for automatic conversion when expanded into a bounce template. + */ +typedef struct { + const char *param_name; /* parameter name */ + int param_name_len; /* name length */ + int *value; /* parameter value */ +} BOUNCE_TIME_PARAMETER; + +static const BOUNCE_TIME_PARAMETER time_parameter[] = { + STRING_AND_LEN(VAR_DELAY_WARN_TIME), &var_delay_warn_time, + STRING_AND_LEN(VAR_MAX_QUEUE_TIME), &var_max_queue_time, + 0, 0, +}; + + /* + * Parameters whose value may have to be converted to UTF-8 for presentation + * purposes. + */ +typedef struct { + const char *param_name; /* parameter name */ + char **value; /* parameter value */ +} BOUNCE_STR_PARAMETER; + +static const BOUNCE_STR_PARAMETER str_parameter[] = { + VAR_MYHOSTNAME, &var_myhostname, + VAR_MYDOMAIN, &var_mydomain, + 0, 0, +}; + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + +/* bounce_template_create - create one template */ + +BOUNCE_TEMPLATE *bounce_template_create(const BOUNCE_TEMPLATE *prototype) +{ + BOUNCE_TEMPLATE *tp; + + tp = (BOUNCE_TEMPLATE *) mymalloc(sizeof(*tp)); + *tp = *prototype; + return (tp); +} + +/* bounce_template_free - destroy one template */ + +void bounce_template_free(BOUNCE_TEMPLATE *tp) +{ + if (tp->buffer) { + myfree(tp->buffer); + myfree((void *) tp->origin); + } + myfree((void *) tp); +} + +/* bounce_template_reset - reset template to default */ + +static void bounce_template_reset(BOUNCE_TEMPLATE *tp) +{ + myfree(tp->buffer); + myfree((void *) tp->origin); + *tp = *(tp->prototype); +} + +/* bounce_template_load - override one template */ + +void bounce_template_load(BOUNCE_TEMPLATE *tp, const char *origin, + const char *buffer) +{ + + /* + * Clean up after a previous call. + */ + if (tp->buffer) + bounce_template_reset(tp); + + /* + * Postpone the work of template parsing until it is really needed. Most + * bounce service calls never need a template. + */ + if (buffer && origin) { + tp->flags |= BOUNCE_TMPL_FLAG_NEW_BUFFER; + tp->buffer = mystrdup(buffer); + tp->origin = mystrdup(origin); + } +} + +/* bounce_template_parse_buffer - initialize template */ + +static void bounce_template_parse_buffer(BOUNCE_TEMPLATE *tp) +{ + char *tval = tp->buffer; + char *cp; + char **cpp; + int cpp_len; + int cpp_used; + int hlen; + char *hval; + + /* + * Sanity check. + */ + if ((tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) == 0) + msg_panic("bounce_template_parse_buffer: nothing to do here"); + tp->flags &= ~BOUNCE_TMPL_FLAG_NEW_BUFFER; + + /* + * Discard the unusable template and use the default one instead. + */ +#define CLEANUP_AND_RETURN() do { \ + bounce_template_reset(tp); \ + return; \ + } while (0) + + /* + * Parse pseudo-header labels and values. + * + * XXX EAI: allow UTF8 in template headers when responding to SMTPUTF8 + * message. Sending SMTPUTF8 in response to non-SMTPUTF8 mail would make + * no sense. + */ +#define GETLINE(line, buf) \ + (((line) = (buf)) != 0 ? ((buf) = split_at((buf), '\n'), (line)) : 0) + + while ((GETLINE(cp, tval)) != 0 && (hlen = is_header(cp)) > 0) { + for (hval = cp + hlen; *hval && (*hval == ':' || ISSPACE(*hval)); hval++) + *hval = 0; + if (*hval == 0) { + msg_warn("%s: empty \"%s\" header value in %s template " + "-- ignoring this template", + tp->origin, cp, tp->class); + CLEANUP_AND_RETURN(); + } + if (!allascii(hval)) { + msg_warn("%s: non-ASCII \"%s\" header value in %s template " + "-- ignoring this template", + tp->origin, cp, tp->class); + CLEANUP_AND_RETURN(); + } + if (strcasecmp("charset", cp) == 0) { + tp->mime_charset = hval; + } else if (strcasecmp("from", cp) == 0) { + tp->std_from = tp->obs_from = hval; + } else if (strcasecmp("subject", cp) == 0) { + tp->subject = hval; + } else if (strcasecmp("postmaster-subject", cp) == 0) { + if (tp->postmaster_subject == 0) { + msg_warn("%s: inapplicable \"%s\" header label in %s template " + "-- ignoring this template", + tp->origin, cp, tp->class); + CLEANUP_AND_RETURN(); + } + tp->postmaster_subject = hval; + } else { + msg_warn("%s: unknown \"%s\" header label in %s template " + "-- ignoring this template", + tp->origin, cp, tp->class); + CLEANUP_AND_RETURN(); + } + } + + /* + * Skip blank lines between header and message text. + */ + while (cp && (*cp == 0 || allspace(cp))) + (void) GETLINE(cp, tval); + if (cp == 0) { + msg_warn("%s: missing message text in %s template " + "-- ignoring this template", + tp->origin, tp->class); + CLEANUP_AND_RETURN(); + } + + /* + * Is this 7bit or 8bit text? If the character set is US-ASCII, then + * don't allow 8bit text. Don't assume 8bit when charset was changed. + */ +#define NON_ASCII(p) ((p) && *(p) && !allascii((p))) + + if (NON_ASCII(cp) || NON_ASCII(tval)) { + if (strcasecmp(tp->mime_charset, "us-ascii") == 0) { + msg_warn("%s: 8-bit message text in %s template", + tp->origin, tp->class); + msg_warn("please specify a charset value other than us-ascii"); + msg_warn("-- ignoring this template for now"); + CLEANUP_AND_RETURN(); + } + tp->mime_encoding = MAIL_ATTR_ENC_8BIT; + } + + /* + * Collect the message text and null-terminate the result. + */ + cpp_len = 10; + cpp_used = 0; + cpp = (char **) mymalloc(sizeof(*cpp) * cpp_len); + while (cp) { + cpp[cpp_used++] = cp; + if (cpp_used >= cpp_len) { + cpp = (char **) myrealloc((void *) cpp, + sizeof(*cpp) * 2 * cpp_len); + cpp_len *= 2; + } + (void) GETLINE(cp, tval); + } + cpp[cpp_used] = 0; + tp->message_text = (const char **) cpp; +} + +/* bounce_template_lookup - lookup $name value */ + +static const char *bounce_template_lookup(const char *key, int unused_mode, + void *context) +{ + BOUNCE_TEMPLATE *tp = (BOUNCE_TEMPLATE *) context; + const BOUNCE_TIME_PARAMETER *bp; + const BOUNCE_TIME_DIVISOR *bd; + const BOUNCE_STR_PARAMETER *sp; + static VSTRING *buf; + int result; + const char *asc_val; + const char *utf8_val; + + /* + * Look for parameter names that can have a time unit suffix, and scale + * the time value according to the suffix. + */ + for (bp = time_parameter; bp->param_name; bp++) { + if (strncmp(key, bp->param_name, bp->param_name_len) == 0 + && key[bp->param_name_len] == '_') { + for (bd = time_divisors; bd->suffix; bd++) { + if (strcmp(key + bp->param_name_len + 1, bd->suffix) == 0) { + result = bp->value[0] / bd->divisor; + if (result > 999 && bd->divisor < 86400) { + msg_warn("%s: excessive result \"%d\" in %s " + "template conversion of parameter \"%s\"", + tp->origin, result, tp->class, key); + msg_warn("please increase time unit \"%s\" of \"%s\" " + "in %s template", bd->suffix, key, tp->class); + msg_warn("for instructions see the bounce(5) manual"); + } else if (result == 0 && bp->value[0] && bd->divisor > 1) { + msg_warn("%s: zero result in %s template " + "conversion of parameter \"%s\"", + tp->origin, tp->class, key); + msg_warn("please reduce time unit \"%s\" of \"%s\" " + "in %s template", bd->suffix, key, tp->class); + msg_warn("for instructions see the bounce(5) manual"); + } + if (buf == 0) + buf = vstring_alloc(10); + vstring_sprintf(buf, "%d", result); + return (STR(buf)); + } + } + msg_fatal("%s: unrecognized suffix \"%s\" in parameter \"%s\"", + tp->origin, + key + bp->param_name_len + 1, key); + } + } + + /* + * Look for parameter names that may have to be up-converted for + * presentation purposes. + */ +#ifndef NO_EAI + if (var_smtputf8_enable) { + for (sp = str_parameter; sp->param_name; sp++) { + if (strcmp(key, sp->param_name) == 0) { + asc_val = sp->value[0]; + if (!allascii(asc_val)) { + msg_warn("%s: conversion \"%s\" failed: " + "non-ASCII input value: \"%s\"", + tp->origin, key, asc_val); + return (asc_val); + } else if ((utf8_val = midna_domain_to_utf8(asc_val)) == 0) { + msg_warn("%s: conversion \"%s\" failed: " + "input value: \"%s\"", + tp->origin, key, asc_val); + return (asc_val); + } else { + return (utf8_val); + } + } + } + } +#endif + return (mail_conf_lookup_eval(key)); +} + +/* bounce_template_headers - send template headers */ + +void bounce_template_headers(BOUNCE_XP_PRN_FN out_fn, VSTREAM *fp, + BOUNCE_TEMPLATE *tp, + const char *rcpt, + int postmaster_copy) +{ + if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) + bounce_template_parse_buffer(tp); + + out_fn(fp, "From: %s", bounce_hfrom_format == HFROM_FORMAT_CODE_STD ? + tp->std_from : tp->obs_from); + out_fn(fp, "Subject: %s", tp->postmaster_subject && postmaster_copy ? + tp->postmaster_subject : tp->subject); + out_fn(fp, "To: %s", rcpt); +} + +/* bounce_template_expand - expand template to stream */ + +void bounce_template_expand(BOUNCE_XP_PUT_FN out_fn, VSTREAM *fp, + BOUNCE_TEMPLATE *tp) +{ + VSTRING *buf = vstring_alloc(100); + const char **cpp; + int stat; + + if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) + bounce_template_parse_buffer(tp); + + for (cpp = tp->message_text; *cpp; cpp++) { + stat = mac_expand(buf, *cpp, MAC_EXP_FLAG_PRINTABLE, (char *) 0, + bounce_template_lookup, (void *) tp); + if (stat & MAC_PARSE_ERROR) + msg_fatal("%s: bad $name syntax in %s template: %s", + tp->origin, tp->class, *cpp); + if (stat & MAC_PARSE_UNDEF) + msg_fatal("%s: undefined $name in %s template: %s", + tp->origin, tp->class, *cpp); + out_fn(fp, STR(buf)); + } + vstring_free(buf); +} + +/* bounce_template_dump - dump template to stream */ + +void bounce_template_dump(VSTREAM *fp, BOUNCE_TEMPLATE *tp) +{ + const char **cpp; + + if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) + bounce_template_parse_buffer(tp); + + vstream_fprintf(fp, "Charset: %s\n", tp->mime_charset); + vstream_fprintf(fp, "From: %s\n", bounce_hfrom_format == HFROM_FORMAT_CODE_STD ? + tp->std_from : tp->obs_from); + vstream_fprintf(fp, "Subject: %s\n", tp->subject); + if (tp->postmaster_subject) + vstream_fprintf(fp, "Postmaster-Subject: %s\n", + tp->postmaster_subject); + vstream_fprintf(fp, "\n"); + for (cpp = tp->message_text; *cpp; cpp++) + vstream_fprintf(fp, "%s\n", *cpp); +} |