summaryrefslogtreecommitdiffstats
path: root/src/bounce/bounce_template.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bounce/bounce_template.c')
-rw-r--r--src/bounce/bounce_template.c548
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);
+}