summaryrefslogtreecommitdiffstats
path: root/src/global/header_body_checks.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/global/header_body_checks.c')
-rw-r--r--src/global/header_body_checks.c655
1 files changed, 655 insertions, 0 deletions
diff --git a/src/global/header_body_checks.c b/src/global/header_body_checks.c
new file mode 100644
index 0000000..0252dd1
--- /dev/null
+++ b/src/global/header_body_checks.c
@@ -0,0 +1,655 @@
+/*++
+/* NAME
+/* header_body_checks 3
+/* SUMMARY
+/* header/body checks
+/* SYNOPSIS
+/* #include <header_body_checks.h>
+/*
+/* typedef struct {
+/* void (*logger) (void *context, const char *action,
+/* const char *where, const char *line,
+/* const char *optional_text);
+/* void (*prepend) (void *context, int rec_type,
+/* const char *buf, ssize_t len, off_t offset);
+/* char *(*extend) (void *context, const char *command,
+/* ssize_t cmd_len, const char *cmd_args,
+/* const char *where, const char *line,
+/* ssize_t line_len, off_t offset);
+/* } HBC_CALL_BACKS;
+/*
+/* HBC_CHECKS *hbc_header_checks_create(
+/* header_checks_name, header_checks_value
+/* mime_header_checks_name, mime_header_checks_value,
+/* nested_header_checks_name, nested_header_checks_value,
+/* call_backs)
+/* const char *header_checks_name;
+/* const char *header_checks_value;
+/* const char *mime_header_checks_name;
+/* const char *mime_header_checks_value;
+/* const char *nested_header_checks_name;
+/* const char *nested_header_checks_value;
+/* HBC_CALL_BACKS *call_backs;
+/*
+/* HBC_CHECKS *hbc_body_checks_create(
+/* body_checks_name, body_checks_value,
+/* call_backs)
+/* const char *body_checks_name;
+/* const char *body_checks_value;
+/* HBC_CALL_BACKS *call_backs;
+/*
+/* char *hbc_header_checks(context, hbc, header_class, hdr_opts, header)
+/* void *context;
+/* HBC_CHECKS *hbc;
+/* int header_class;
+/* const HEADER_OPTS *hdr_opts;
+/* VSTRING *header;
+/*
+/* char *hbc_body_checks(context, hbc, body_line, body_line_len)
+/* void *context;
+/* HBC_CHECKS *hbc;
+/* const char *body_line;
+/* ssize_t body_line_len;
+/*
+/* void hbc_header_checks_free(hbc)
+/* HBC_CHECKS *hbc;
+/*
+/* void hbc_body_checks_free(hbc)
+/* HBC_CHECKS *hbc;
+/* DESCRIPTION
+/* This module implements header_checks and body_checks.
+/* Actions are executed while mail is being delivered. The
+/* following actions are recognized: INFO, WARN, REPLACE,
+/* PREPEND, IGNORE, DUNNO, and OK. These actions are safe for
+/* use in delivery agents.
+/*
+/* Other actions may be supplied via the extension mechanism
+/* described below. For example, actions that change the
+/* message delivery time or destination. Such actions do not
+/* make sense in delivery agents, but they can be appropriate
+/* in, for example, before-queue filters.
+/*
+/* hbc_header_checks_create() creates a context for header
+/* inspection. This function is typically called once during
+/* program initialization. The result is a null pointer when
+/* all _value arguments specify zero-length strings; in this
+/* case, hbc_header_checks() and hbc_header_checks_free() must
+/* not be called.
+/*
+/* hbc_header_checks() inspects the specified logical header.
+/* The result is either the original header, HBC_CHECKS_STAT_IGNORE
+/* (meaning: discard the header), HBC_CHECKS_STAT_ERROR, or a
+/* new header (meaning: replace the header and destroy the new
+/* header with myfree()).
+/*
+/* hbc_header_checks_free() returns memory to the pool.
+/*
+/* hbc_body_checks_create(), hbc_body_checks(), hbc_body_free()
+/* perform similar functions for body lines.
+/*
+/* Arguments:
+/* .IP body_line
+/* One line of body text.
+/* .IP body_line_len
+/* Body line length.
+/* .IP call_backs
+/* Table with call-back function pointers. This argument is
+/* not copied. Note: the description below is not necessarily
+/* in data structure order.
+/* .RS
+/* .IP logger
+/* Call-back function for logging an action with the action's
+/* name in lower case, a location within a message ("header"
+/* or "body"), the content of the header or body line that
+/* triggered the action, and optional text or a zero-length
+/* string. This call-back feature must be specified.
+/* .IP prepend
+/* Call-back function for the PREPEND action. The arguments
+/* are the same as those of mime_state(3) body output call-back
+/* functions. Specify a null pointer to disable this action.
+/* .IP extend
+/* Call-back function that logs and executes other actions.
+/* This function receives as arguments the command name and
+/* name length, the command arguments if any, the location
+/* within the message ("header" or "body"), the content and
+/* length of the header or body line that triggered the action,
+/* and the input byte offset within the current header or body
+/* segment. The result value is either the original line
+/* argument, HBC_CHECKS_STAT_IGNORE (delete the line from the
+/* input stream) or HBC_CHECKS_STAT_UNKNOWN (the command was
+/* not recognized). Specify a null pointer to disable this
+/* feature.
+/* .RE
+/* .IP context
+/* Application context for call-back functions specified with the
+/* call_backs argument.
+/* .IP header
+/* A logical message header. Lines within a multi-line header
+/* are separated by a newline character.
+/* .IP "header_checks_name, mime_header_checks_name"
+/* .IP "nested_header_checks_name, body_checks_name"
+/* The main.cf configuration parameter names for header and body
+/* map lists.
+/* .IP "header_checks_value, mime_header_checks_value"
+/* .IP "nested_header_checks_value, body_checks_value"
+/* The values of main.cf configuration parameters for header and body
+/* map lists. Specify a zero-length string to disable a specific list.
+/* .IP header_class
+/* A number in the range MIME_HDR_FIRST..MIME_HDR_LAST.
+/* .IP hbc
+/* A handle created with hbc_header_checks_create() or
+/* hbc_body_checks_create().
+/* .IP hdr_opts
+/* Message header properties.
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* 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 <ctype.h>
+#include <string.h>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mime_state.h>
+#include <rec_type.h>
+#include <is_header.h>
+#include <cleanup_user.h>
+#include <dsn_util.h>
+#include <header_body_checks.h>
+
+/* Application-specific. */
+
+ /*
+ * Something that is guaranteed to be different from a real string result
+ * from header/body_checks.
+ */
+char hbc_checks_error;
+const char hbc_checks_unknown;
+
+ /*
+ * Header checks are stored as an array of HBC_MAP_INFO structures, one
+ * structure for each header class (MIME_HDR_PRIMARY, MIME_HDR_MULTIPART, or
+ * MIME_HDR_NESTED).
+ *
+ * Body checks are stored as one single HBC_MAP_INFO structure, because we make
+ * no distinction between body segments.
+ */
+#define HBC_HEADER_INDEX(class) ((class) - MIME_HDR_FIRST)
+#define HBC_BODY_INDEX (0)
+
+#define HBC_INIT(hbc, index, name, value) do { \
+ HBC_MAP_INFO *_mp = (hbc)->map_info + (index); \
+ if (*(value) != 0) { \
+ _mp->map_class = (name); \
+ _mp->maps = maps_create((name), (value), DICT_FLAG_LOCK); \
+ } else { \
+ _mp->map_class = 0; \
+ _mp->maps = 0; \
+ } \
+ } while (0)
+
+/* How does the action routine know where we are? */
+
+#define HBC_CTXT_HEADER "header"
+#define HBC_CTXT_BODY "body"
+
+/* Silly little macros. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* hbc_action - act upon a header/body match */
+
+static char *hbc_action(void *context, HBC_CALL_BACKS *cb,
+ const char *map_class, const char *where,
+ const char *cmd, const char *line,
+ ssize_t line_len, off_t offset)
+{
+ const char *cmd_args = cmd + strcspn(cmd, " \t");
+ ssize_t cmd_len = cmd_args - cmd;
+ char *ret;
+
+ /*
+ * XXX We don't use a hash table for action lookup. Mail rarely triggers
+ * an action, and mail that triggers multiple actions is even rarer.
+ * Setting up the hash table costs more than we would gain from using it.
+ */
+ while (*cmd_args && ISSPACE(*cmd_args))
+ cmd_args++;
+
+#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
+
+ if (cb->extend
+ && (ret = cb->extend(context, cmd, cmd_len, cmd_args, where, line,
+ line_len, offset)) != HBC_CHECKS_STAT_UNKNOWN)
+ return (ret);
+
+ if (STREQUAL(cmd, "WARN", cmd_len)) {
+ cb->logger(context, "warning", where, line, cmd_args);
+ return ((char *) line);
+ }
+ if (STREQUAL(cmd, "INFO", cmd_len)) {
+ cb->logger(context, "info", where, line, cmd_args);
+ return ((char *) line);
+ }
+ if (STREQUAL(cmd, "REPLACE", cmd_len)) {
+ if (*cmd_args == 0) {
+ msg_warn("REPLACE action without text in %s map", map_class);
+ return ((char *) line);
+ } else if (strcmp(where, HBC_CTXT_HEADER) == 0
+ && !is_header(cmd_args)) {
+ msg_warn("bad REPLACE header text \"%s\" in %s map -- "
+ "need \"headername: headervalue\"", cmd_args, map_class);
+ return ((char *) line);
+ } else {
+ cb->logger(context, "replace", where, line, cmd_args);
+ return (mystrdup(cmd_args));
+ }
+ }
+ if (cb->prepend && STREQUAL(cmd, "PREPEND", cmd_len)) {
+ if (*cmd_args == 0) {
+ msg_warn("PREPEND action without text in %s map", map_class);
+ } else if (strcmp(where, HBC_CTXT_HEADER) == 0
+ && !is_header(cmd_args)) {
+ msg_warn("bad PREPEND header text \"%s\" in %s map -- "
+ "need \"headername: headervalue\"", cmd_args, map_class);
+ } else {
+ cb->logger(context, "prepend", where, line, cmd_args);
+ cb->prepend(context, REC_TYPE_NORM, cmd_args, strlen(cmd_args), offset);
+ }
+ return ((char *) line);
+ }
+ if (STREQUAL(cmd, "STRIP", cmd_len)) {
+ cb->logger(context, "strip", where, line, cmd_args);
+ return (HBC_CHECKS_STAT_IGNORE);
+ }
+ /* Allow and ignore optional text after the action. */
+
+ if (STREQUAL(cmd, "IGNORE", cmd_len))
+ /* XXX Not logged for compatibility with cleanup(8). */
+ return (HBC_CHECKS_STAT_IGNORE);
+
+ if (STREQUAL(cmd, "DUNNO", cmd_len) /* preferred */
+ ||STREQUAL(cmd, "OK", cmd_len)) /* compatibility */
+ return ((char *) line);
+
+ msg_warn("unsupported command in %s map: %s", map_class, cmd);
+ return ((char *) line);
+}
+
+/* hbc_header_checks - process one complete header line */
+
+char *hbc_header_checks(void *context, HBC_CHECKS *hbc, int header_class,
+ const HEADER_OPTS *hdr_opts,
+ VSTRING *header, off_t offset)
+{
+ const char *myname = "hbc_header_checks";
+ const char *action;
+ HBC_MAP_INFO *mp;
+
+ if (msg_verbose)
+ msg_info("%s: '%.30s'", myname, STR(header));
+
+ /*
+ * XXX This is for compatibility with the cleanup(8) server.
+ */
+ if (hdr_opts && (hdr_opts->flags & HDR_OPT_MIME))
+ header_class = MIME_HDR_MULTIPART;
+
+ mp = hbc->map_info + HBC_HEADER_INDEX(header_class);
+
+ if (mp->maps != 0 && (action = maps_find(mp->maps, STR(header), 0)) != 0) {
+ return (hbc_action(context, hbc->call_backs,
+ mp->map_class, HBC_CTXT_HEADER, action,
+ STR(header), LEN(header), offset));
+ } else if (mp->maps && mp->maps->error) {
+ return (HBC_CHECKS_STAT_ERROR);
+ } else {
+ return (STR(header));
+ }
+}
+
+/* hbc_body_checks - inspect one body record */
+
+char *hbc_body_checks(void *context, HBC_CHECKS *hbc, const char *line,
+ ssize_t len, off_t offset)
+{
+ const char *myname = "hbc_body_checks";
+ const char *action;
+ HBC_MAP_INFO *mp;
+
+ if (msg_verbose)
+ msg_info("%s: '%.30s'", myname, line);
+
+ mp = hbc->map_info;
+
+ if ((action = maps_find(mp->maps, line, 0)) != 0) {
+ return (hbc_action(context, hbc->call_backs,
+ mp->map_class, HBC_CTXT_BODY, action,
+ line, len, offset));
+ } else if (mp->maps->error) {
+ return (HBC_CHECKS_STAT_ERROR);
+ } else {
+ return ((char *) line);
+ }
+}
+
+/* hbc_header_checks_create - create header checking context */
+
+HBC_CHECKS *hbc_header_checks_create(const char *header_checks_name,
+ const char *header_checks_value,
+ const char *mime_header_checks_name,
+ const char *mime_header_checks_value,
+ const char *nested_header_checks_name,
+ const char *nested_header_checks_value,
+ HBC_CALL_BACKS *call_backs)
+{
+ HBC_CHECKS *hbc;
+
+ /*
+ * Optimize for the common case.
+ */
+ if (*header_checks_value == 0 && *mime_header_checks_value == 0
+ && *nested_header_checks_value == 0) {
+ return (0);
+ } else {
+ hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc)
+ + (MIME_HDR_LAST - MIME_HDR_FIRST) * sizeof(HBC_MAP_INFO));
+ hbc->call_backs = call_backs;
+ HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_PRIMARY),
+ header_checks_name, header_checks_value);
+ HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_MULTIPART),
+ mime_header_checks_name, mime_header_checks_value);
+ HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_NESTED),
+ nested_header_checks_name, nested_header_checks_value);
+ return (hbc);
+ }
+}
+
+/* hbc_body_checks_create - create body checking context */
+
+HBC_CHECKS *hbc_body_checks_create(const char *body_checks_name,
+ const char *body_checks_value,
+ HBC_CALL_BACKS *call_backs)
+{
+ HBC_CHECKS *hbc;
+
+ /*
+ * Optimize for the common case.
+ */
+ if (*body_checks_value == 0) {
+ return (0);
+ } else {
+ hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc));
+ hbc->call_backs = call_backs;
+ HBC_INIT(hbc, HBC_BODY_INDEX, body_checks_name, body_checks_value);
+ return (hbc);
+ }
+}
+
+/* _hbc_checks_free - destroy header/body checking context */
+
+void _hbc_checks_free(HBC_CHECKS *hbc, ssize_t len)
+{
+ HBC_MAP_INFO *mp;
+
+ for (mp = hbc->map_info; mp < hbc->map_info + len; mp++)
+ if (mp->maps)
+ maps_free(mp->maps);
+ myfree((void *) hbc);
+}
+
+ /*
+ * Test program. Specify the four maps on the command line, and feed a
+ * MIME-formatted message on stdin.
+ */
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <stringops.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <rec_streamlf.h>
+#include <mail_params.h>
+
+typedef struct {
+ HBC_CHECKS *header_checks;
+ HBC_CHECKS *body_checks;
+ HBC_CALL_BACKS *call_backs;
+ VSTREAM *fp;
+ VSTRING *buf;
+ const char *queueid;
+ int recno;
+} HBC_TEST_CONTEXT;
+
+/*#define REC_LEN 40*/
+#define REC_LEN 1024
+
+/* log_cb - log action with context */
+
+static void log_cb(void *context, const char *action, const char *where,
+ const char *content, const char *text)
+{
+ const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
+
+ if (*text) {
+ msg_info("%s: %s: %s %.200s: %s",
+ dp->queueid, action, where, content, text);
+ } else {
+ msg_info("%s: %s: %s %.200s",
+ dp->queueid, action, where, content);
+ }
+}
+
+/* out_cb - output call-back */
+
+static void out_cb(void *context, int rec_type, const char *buf,
+ ssize_t len, off_t offset)
+{
+ const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
+
+ vstream_fwrite(dp->fp, buf, len);
+ VSTREAM_PUTC('\n', dp->fp);
+ vstream_fflush(dp->fp);
+}
+
+/* head_out - MIME_STATE header call-back */
+
+static void head_out(void *context, int header_class,
+ const HEADER_OPTS *header_info,
+ VSTRING *buf, off_t offset)
+{
+ HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
+ char *out;
+
+ if (dp->header_checks == 0
+ || (out = hbc_header_checks(context, dp->header_checks, header_class,
+ header_info, buf, offset)) == STR(buf)) {
+ vstring_sprintf(dp->buf, "%d %s %ld\t|%s",
+ dp->recno,
+ header_class == MIME_HDR_PRIMARY ? "MAIN" :
+ header_class == MIME_HDR_MULTIPART ? "MULT" :
+ header_class == MIME_HDR_NESTED ? "NEST" :
+ "ERROR", (long) offset, STR(buf));
+ out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset);
+ } else if (out != 0) {
+ vstring_sprintf(dp->buf, "%d %s %ld\t|%s",
+ dp->recno,
+ header_class == MIME_HDR_PRIMARY ? "MAIN" :
+ header_class == MIME_HDR_MULTIPART ? "MULT" :
+ header_class == MIME_HDR_NESTED ? "NEST" :
+ "ERROR", (long) offset, out);
+ out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset);
+ myfree(out);
+ }
+ dp->recno += 1;
+}
+
+/* header_end - MIME_STATE end-of-header call-back */
+
+static void head_end(void *context)
+{
+ HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
+
+ out_cb(dp, 0, "HEADER END", sizeof("HEADER END") - 1, 0);
+}
+
+/* body_out - MIME_STATE body line call-back */
+
+static void body_out(void *context, int rec_type, const char *buf,
+ ssize_t len, off_t offset)
+{
+ HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
+ char *out;
+
+ if (dp->body_checks == 0
+ || (out = hbc_body_checks(context, dp->body_checks,
+ buf, len, offset)) == buf) {
+ vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s",
+ dp->recno, rec_type, (long) offset, buf);
+ out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset);
+ } else if (out != 0) {
+ vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s",
+ dp->recno, rec_type, (long) offset, out);
+ out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset);
+ myfree(out);
+ }
+ dp->recno += 1;
+}
+
+/* body_end - MIME_STATE end-of-message call-back */
+
+static void body_end(void *context)
+{
+ HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
+
+ out_cb(dp, 0, "BODY END", sizeof("BODY END") - 1, 0);
+}
+
+/* err_print - print MIME_STATE errors */
+
+static void err_print(void *unused_context, int err_flag,
+ const char *text, ssize_t len)
+{
+ msg_warn("%s: %.*s", mime_state_error(err_flag),
+ len < 100 ? (int) len : 100, text);
+}
+
+int var_header_limit = 2000;
+int var_mime_maxdepth = 20;
+int var_mime_bound_len = 2000;
+char *var_drop_hdrs = DEF_DROP_HDRS;
+
+int main(int argc, char **argv)
+{
+ int rec_type;
+ VSTRING *buf;
+ int err;
+ MIME_STATE *mime_state;
+ HBC_TEST_CONTEXT context;
+ static HBC_CALL_BACKS call_backs[1] = {
+ log_cb, /* logger */
+ out_cb, /* prepend */
+ };
+
+ /*
+ * Sanity check.
+ */
+ if (argc != 5)
+ msg_fatal("usage: %s header_checks mime_header_checks nested_header_checks body_checks", argv[0]);
+
+ /*
+ * Initialize.
+ */
+#define MIME_OPTIONS \
+ (MIME_OPT_REPORT_8BIT_IN_7BIT_BODY \
+ | MIME_OPT_REPORT_8BIT_IN_HEADER \
+ | MIME_OPT_REPORT_ENCODING_DOMAIN \
+ | MIME_OPT_REPORT_TRUNC_HEADER \
+ | MIME_OPT_REPORT_NESTING \
+ | MIME_OPT_DOWNGRADE)
+ msg_vstream_init(basename(argv[0]), VSTREAM_OUT);
+ buf = vstring_alloc(10);
+ mime_state = mime_state_alloc(MIME_OPTIONS,
+ head_out, head_end,
+ body_out, body_end,
+ err_print,
+ (void *) &context);
+ context.header_checks =
+ hbc_header_checks_create("header_checks", argv[1],
+ "mime_header_checks", argv[2],
+ "nested_header_checks", argv[3],
+ call_backs);
+ context.body_checks =
+ hbc_body_checks_create("body_checks", argv[4], call_backs);
+ context.buf = vstring_alloc(100);
+ context.fp = VSTREAM_OUT;
+ context.queueid = "test-queueID";
+ context.recno = 0;
+
+ /*
+ * Main loop.
+ */
+ do {
+ rec_type = rec_streamlf_get(VSTREAM_IN, buf, REC_LEN);
+ VSTRING_TERMINATE(buf);
+ err = mime_state_update(mime_state, rec_type, STR(buf), LEN(buf));
+ vstream_fflush(VSTREAM_OUT);
+ } while (rec_type > 0);
+
+ /*
+ * Error reporting.
+ */
+ if (err & MIME_ERR_TRUNC_HEADER)
+ msg_warn("message header length exceeds safety limit");
+ if (err & MIME_ERR_NESTING)
+ msg_warn("MIME nesting exceeds safety limit");
+ if (err & MIME_ERR_8BIT_IN_HEADER)
+ msg_warn("improper use of 8-bit data in message header");
+ if (err & MIME_ERR_8BIT_IN_7BIT_BODY)
+ msg_warn("improper use of 8-bit data in message body");
+ if (err & MIME_ERR_ENCODING_DOMAIN)
+ msg_warn("improper message/* or multipart/* encoding domain");
+
+ /*
+ * Cleanup.
+ */
+ if (context.header_checks)
+ hbc_header_checks_free(context.header_checks);
+ if (context.body_checks)
+ hbc_body_checks_free(context.body_checks);
+ vstring_free(context.buf);
+ mime_state_free(mime_state);
+ vstring_free(buf);
+ exit(0);
+}
+
+#endif