diff options
Diffstat (limited to 'src/global/header_body_checks.c')
-rw-r--r-- | src/global/header_body_checks.c | 655 |
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 |