/*++ /* NAME /* header_body_checks 3 /* SUMMARY /* header/body checks /* SYNOPSIS /* #include /* /* 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 #include #include #ifdef STRCASECMP_IN_STRINGS_H #include #endif /* Utility library. */ #include #include /* Global library. */ #include #include #include #include #include #include /* 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 #include #include #include #include #include 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