mirror of
https://github.com/vdukhovni/postfix
synced 2025-08-22 01:49:47 +00:00
1127 lines
38 KiB
C
1127 lines
38 KiB
C
/*++
|
|
/* NAME
|
|
/* cleanup_message 3
|
|
/* SUMMARY
|
|
/* process message segment
|
|
/* SYNOPSIS
|
|
/* #include "cleanup.h"
|
|
/*
|
|
/* void cleanup_message(state, type, buf, len)
|
|
/* CLEANUP_STATE *state;
|
|
/* int type;
|
|
/* const char *buf;
|
|
/* ssize_t len;
|
|
/* DESCRIPTION
|
|
/* This module processes message content records and copies the
|
|
/* result to the queue file. It validates the input, rewrites
|
|
/* sender/recipient addresses to canonical form, inserts missing
|
|
/* message headers, and extracts information from message headers
|
|
/* to be used later when generating the extracted output segment.
|
|
/* This routine absorbs but does not emit the content to extracted
|
|
/* boundary record.
|
|
/*
|
|
/* Arguments:
|
|
/* .IP state
|
|
/* Queue file and message processing state. This state is updated
|
|
/* as records are processed and as errors happen.
|
|
/* .IP type
|
|
/* Record type.
|
|
/* .IP buf
|
|
/* Record content.
|
|
/* .IP len
|
|
/* Record content length.
|
|
/* 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>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef STRCASECMP_IN_STRINGS_H
|
|
#include <strings.h>
|
|
#endif
|
|
|
|
/* Utility library. */
|
|
|
|
#include <msg.h>
|
|
#include <vstring.h>
|
|
#include <vstream.h>
|
|
#include <argv.h>
|
|
#include <split_at.h>
|
|
#include <mymalloc.h>
|
|
#include <stringops.h>
|
|
#include <nvtable.h>
|
|
|
|
/* Global library. */
|
|
|
|
#include <record.h>
|
|
#include <rec_type.h>
|
|
#include <cleanup_user.h>
|
|
#include <tok822.h>
|
|
#include <lex_822.h>
|
|
#include <header_opts.h>
|
|
#include <quote_822_local.h>
|
|
#include <mail_params.h>
|
|
#include <mail_date.h>
|
|
#include <mail_addr.h>
|
|
#include <is_header.h>
|
|
#include <ext_prop.h>
|
|
#include <mail_proto.h>
|
|
#include <mime_state.h>
|
|
#include <lex_822.h>
|
|
#include <dsn_util.h>
|
|
#include <conv_time.h>
|
|
#include <info_log_addr_form.h>
|
|
#include <hfrom_format.h>
|
|
|
|
/* Application-specific. */
|
|
|
|
#include "cleanup.h"
|
|
|
|
/* cleanup_fold_header - wrap address list header */
|
|
|
|
static void cleanup_fold_header(CLEANUP_STATE *state, VSTRING *header_buf)
|
|
{
|
|
char *start_line = vstring_str(header_buf);
|
|
char *end_line;
|
|
char *next_line;
|
|
char *line;
|
|
|
|
/*
|
|
* A rewritten address list contains one address per line. The code below
|
|
* replaces newlines by spaces, to fit as many addresses on a line as
|
|
* possible (without rearranging the order of addresses). Prepending
|
|
* white space to the beginning of lines is delegated to the output
|
|
* routine.
|
|
*/
|
|
for (line = start_line; line != 0; line = next_line) {
|
|
end_line = line + strcspn(line, "\n");
|
|
if (line > start_line) {
|
|
if (end_line - start_line < 70) { /* TAB counts as one */
|
|
line[-1] = ' ';
|
|
} else {
|
|
start_line = line;
|
|
}
|
|
}
|
|
next_line = *end_line ? end_line + 1 : 0;
|
|
}
|
|
cleanup_out_header(state, header_buf);
|
|
}
|
|
|
|
/* cleanup_extract_internal - save unquoted copy of extracted address */
|
|
|
|
static char *cleanup_extract_internal(VSTRING *buffer, TOK822 *addr)
|
|
{
|
|
|
|
/*
|
|
* A little routine to stash away a copy of an address that we extracted
|
|
* from a message header line.
|
|
*/
|
|
tok822_internalize(buffer, addr->head, TOK822_STR_DEFL);
|
|
return (mystrdup(vstring_str(buffer)));
|
|
}
|
|
|
|
/* cleanup_rewrite_sender - sender address rewriting */
|
|
|
|
static void cleanup_rewrite_sender(CLEANUP_STATE *state,
|
|
const HEADER_OPTS *hdr_opts,
|
|
VSTRING *header_buf)
|
|
{
|
|
TOK822 *tree;
|
|
TOK822 **addr_list;
|
|
TOK822 **tpp;
|
|
int did_rewrite = 0;
|
|
|
|
if (msg_verbose)
|
|
msg_info("rewrite_sender: %s", hdr_opts->name);
|
|
|
|
/*
|
|
* Parse the header line, rewrite each address found, and regenerate the
|
|
* header line. Finally, pipe the result through the header line folding
|
|
* routine.
|
|
*/
|
|
tree = tok822_parse_limit(vstring_str(header_buf)
|
|
+ strlen(hdr_opts->name) + 1,
|
|
var_token_limit);
|
|
addr_list = tok822_grep(tree, TOK822_ADDR);
|
|
for (tpp = addr_list; *tpp; tpp++) {
|
|
did_rewrite |= cleanup_rewrite_tree(state->hdr_rewrite_context, *tpp);
|
|
if (state->flags & CLEANUP_FLAG_MAP_OK) {
|
|
if (cleanup_send_canon_maps
|
|
&& (cleanup_send_canon_flags & CLEANUP_CANON_FLAG_HDR_FROM))
|
|
did_rewrite |=
|
|
cleanup_map11_tree(state, *tpp, cleanup_send_canon_maps,
|
|
cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
|
|
if (cleanup_comm_canon_maps
|
|
&& (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_HDR_FROM))
|
|
did_rewrite |=
|
|
cleanup_map11_tree(state, *tpp, cleanup_comm_canon_maps,
|
|
cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
|
|
if (cleanup_masq_domains
|
|
&& (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_FROM))
|
|
did_rewrite |=
|
|
cleanup_masquerade_tree(state, *tpp, cleanup_masq_domains);
|
|
}
|
|
}
|
|
if (did_rewrite) {
|
|
vstring_truncate(header_buf, strlen(hdr_opts->name));
|
|
vstring_strcat(header_buf, ": ");
|
|
tok822_externalize(header_buf, tree, TOK822_STR_HEAD);
|
|
}
|
|
myfree((void *) addr_list);
|
|
tok822_free_tree(tree);
|
|
if ((hdr_opts->flags & HDR_OPT_DROP) == 0) {
|
|
if (did_rewrite)
|
|
cleanup_fold_header(state, header_buf);
|
|
else
|
|
cleanup_out_header(state, header_buf);
|
|
}
|
|
}
|
|
|
|
/* cleanup_rewrite_recip - recipient address rewriting */
|
|
|
|
static void cleanup_rewrite_recip(CLEANUP_STATE *state,
|
|
const HEADER_OPTS *hdr_opts,
|
|
VSTRING *header_buf)
|
|
{
|
|
TOK822 *tree;
|
|
TOK822 **addr_list;
|
|
TOK822 **tpp;
|
|
int did_rewrite = 0;
|
|
|
|
if (msg_verbose)
|
|
msg_info("rewrite_recip: %s", hdr_opts->name);
|
|
|
|
/*
|
|
* Parse the header line, rewrite each address found, and regenerate the
|
|
* header line. Finally, pipe the result through the header line folding
|
|
* routine.
|
|
*/
|
|
tree = tok822_parse_limit(vstring_str(header_buf)
|
|
+ strlen(hdr_opts->name) + 1,
|
|
var_token_limit);
|
|
addr_list = tok822_grep(tree, TOK822_ADDR);
|
|
for (tpp = addr_list; *tpp; tpp++) {
|
|
did_rewrite |= cleanup_rewrite_tree(state->hdr_rewrite_context, *tpp);
|
|
if (state->flags & CLEANUP_FLAG_MAP_OK) {
|
|
if (cleanup_rcpt_canon_maps
|
|
&& (cleanup_rcpt_canon_flags & CLEANUP_CANON_FLAG_HDR_RCPT))
|
|
did_rewrite |=
|
|
cleanup_map11_tree(state, *tpp, cleanup_rcpt_canon_maps,
|
|
cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
|
|
if (cleanup_comm_canon_maps
|
|
&& (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_HDR_RCPT))
|
|
did_rewrite |=
|
|
cleanup_map11_tree(state, *tpp, cleanup_comm_canon_maps,
|
|
cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
|
|
if (cleanup_masq_domains
|
|
&& (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_RCPT))
|
|
did_rewrite |=
|
|
cleanup_masquerade_tree(state, *tpp, cleanup_masq_domains);
|
|
}
|
|
}
|
|
if (did_rewrite) {
|
|
vstring_truncate(header_buf, strlen(hdr_opts->name));
|
|
vstring_strcat(header_buf, ": ");
|
|
tok822_externalize(header_buf, tree, TOK822_STR_HEAD);
|
|
}
|
|
myfree((void *) addr_list);
|
|
tok822_free_tree(tree);
|
|
if ((hdr_opts->flags & HDR_OPT_DROP) == 0) {
|
|
if (did_rewrite)
|
|
cleanup_fold_header(state, header_buf);
|
|
else
|
|
cleanup_out_header(state, header_buf);
|
|
}
|
|
}
|
|
|
|
/* cleanup_act_log - log action with context */
|
|
|
|
static void cleanup_act_log(CLEANUP_STATE *state,
|
|
const char *action, const char *class,
|
|
const char *content, const char *text)
|
|
{
|
|
const char *attr;
|
|
|
|
if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_ORIGIN)) == 0)
|
|
attr = "unknown";
|
|
vstring_sprintf(state->temp1, "%s: %s: %s %.200s from %s;",
|
|
state->queue_id, action, class, content, attr);
|
|
if (state->sender)
|
|
vstring_sprintf_append(state->temp1, " from=<%s>",
|
|
info_log_addr_form_sender(state->sender));
|
|
if (state->recip)
|
|
vstring_sprintf_append(state->temp1, " to=<%s>",
|
|
info_log_addr_form_recipient(state->recip));
|
|
if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0)
|
|
vstring_sprintf_append(state->temp1, " proto=%s", attr);
|
|
if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0)
|
|
vstring_sprintf_append(state->temp1, " helo=<%s>", attr);
|
|
if (text && *text)
|
|
vstring_sprintf_append(state->temp1, ": %s", text);
|
|
msg_info("%s", vstring_str(state->temp1));
|
|
}
|
|
|
|
#define CLEANUP_ACT_CTXT_HEADER "header"
|
|
#define CLEANUP_ACT_CTXT_BODY "body"
|
|
#define CLEANUP_ACT_CTXT_ANY "content"
|
|
|
|
/* cleanup_act - act upon a header/body match */
|
|
|
|
static const char *cleanup_act(CLEANUP_STATE *state, char *context,
|
|
const char *buf, const char *value,
|
|
const char *map_class)
|
|
{
|
|
const char *optional_text = value + strcspn(value, " \t");
|
|
int command_len = optional_text - value;
|
|
|
|
#ifdef DELAY_ACTION
|
|
int defer_delay;
|
|
|
|
#endif
|
|
|
|
while (*optional_text && ISSPACE(*optional_text))
|
|
optional_text++;
|
|
|
|
#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
|
|
#define CLEANUP_ACT_DROP 0
|
|
|
|
/*
|
|
* CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason
|
|
* attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates
|
|
* queue record processing, and prevents bounces from being sent.
|
|
*/
|
|
if (STREQUAL(value, "REJECT", command_len)) {
|
|
const CLEANUP_STAT_DETAIL *detail;
|
|
|
|
if (state->reason)
|
|
myfree(state->reason);
|
|
if (*optional_text) {
|
|
state->reason = dsn_prepend("5.7.1", optional_text);
|
|
if (*state->reason != '4' && *state->reason != '5') {
|
|
msg_warn("bad DSN action in %s -- need 4.x.x or 5.x.x",
|
|
optional_text);
|
|
*state->reason = '4';
|
|
}
|
|
} else {
|
|
detail = cleanup_stat_detail(CLEANUP_STAT_CONT);
|
|
state->reason = dsn_prepend(detail->dsn, detail->text);
|
|
}
|
|
if (*state->reason == '4')
|
|
state->errs |= CLEANUP_STAT_DEFER;
|
|
else
|
|
state->errs |= CLEANUP_STAT_CONT;
|
|
state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
|
|
cleanup_act_log(state, "reject", context, buf, state->reason);
|
|
return (buf);
|
|
}
|
|
if (STREQUAL(value, "WARN", command_len)) {
|
|
cleanup_act_log(state, "warning", context, buf, optional_text);
|
|
return (buf);
|
|
}
|
|
if (STREQUAL(value, "INFO", command_len)) {
|
|
cleanup_act_log(state, "info", context, buf, optional_text);
|
|
return (buf);
|
|
}
|
|
if (STREQUAL(value, "FILTER", command_len)) {
|
|
if (*optional_text == 0) {
|
|
msg_warn("missing FILTER command argument in %s map", map_class);
|
|
} else if (strchr(optional_text, ':') == 0) {
|
|
msg_warn("bad FILTER command %s in %s -- "
|
|
"need transport:destination",
|
|
optional_text, map_class);
|
|
} else {
|
|
if (state->filter)
|
|
myfree(state->filter);
|
|
state->filter = mystrdup(optional_text);
|
|
cleanup_act_log(state, "filter", context, buf, optional_text);
|
|
}
|
|
return (buf);
|
|
}
|
|
if (STREQUAL(value, "PASS", command_len)) {
|
|
cleanup_act_log(state, "pass", context, buf, optional_text);
|
|
state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
|
|
return (buf);
|
|
}
|
|
if (STREQUAL(value, "DISCARD", command_len)) {
|
|
cleanup_act_log(state, "discard", context, buf, optional_text);
|
|
state->flags |= CLEANUP_FLAG_DISCARD;
|
|
state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
|
|
return (buf);
|
|
}
|
|
if (STREQUAL(value, "HOLD", command_len)) {
|
|
if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) {
|
|
cleanup_act_log(state, "hold", context, buf, optional_text);
|
|
state->flags |= CLEANUP_FLAG_HOLD;
|
|
}
|
|
return (buf);
|
|
}
|
|
|
|
/*
|
|
* The DELAY feature is disabled because it has too many problems. 1) It
|
|
* does not work on some remote file systems; 2) mail will be delivered
|
|
* anyway with "sendmail -q" etc.; 3) while the mail is queued it bogs
|
|
* down the deferred queue scan with huge amounts of useless disk I/O
|
|
* operations.
|
|
*/
|
|
#ifdef DELAY_ACTION
|
|
if (STREQUAL(value, "DELAY", command_len)) {
|
|
if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) {
|
|
if (*optional_text == 0) {
|
|
msg_warn("missing DELAY argument in %s map", map_class);
|
|
} else if (conv_time(optional_text, &defer_delay, 's') == 0) {
|
|
msg_warn("ignoring bad DELAY argument %s in %s map",
|
|
optional_text, map_class);
|
|
} else {
|
|
cleanup_act_log(state, "delay", context, buf, optional_text);
|
|
state->defer_delay = defer_delay;
|
|
}
|
|
}
|
|
return (buf);
|
|
}
|
|
#endif
|
|
if (STREQUAL(value, "PREPEND", command_len)) {
|
|
if (*optional_text == 0) {
|
|
msg_warn("PREPEND action without text in %s map", map_class);
|
|
} else if (strcmp(context, CLEANUP_ACT_CTXT_HEADER) == 0) {
|
|
if (!is_header(optional_text)) {
|
|
msg_warn("bad PREPEND header text \"%s\" in %s map -- "
|
|
"need \"headername: headervalue\"",
|
|
optional_text, map_class);
|
|
}
|
|
|
|
/*
|
|
* By design, cleanup_out_header() may modify content. Play safe
|
|
* and prepare for future developments.
|
|
*/
|
|
else {
|
|
VSTRING *temp;
|
|
|
|
cleanup_act_log(state, "prepend", context, buf, optional_text);
|
|
temp = vstring_strcpy(vstring_alloc(strlen(optional_text)),
|
|
optional_text);
|
|
cleanup_out_header(state, temp);
|
|
vstring_free(temp);
|
|
}
|
|
} else {
|
|
cleanup_act_log(state, "prepend", context, buf, optional_text);
|
|
cleanup_out_string(state, REC_TYPE_NORM, optional_text);
|
|
}
|
|
return (buf);
|
|
}
|
|
if (STREQUAL(value, "REPLACE", command_len)) {
|
|
if (*optional_text == 0) {
|
|
msg_warn("REPLACE action without text in %s map", map_class);
|
|
return (buf);
|
|
} else if (strcmp(context, CLEANUP_ACT_CTXT_HEADER) == 0
|
|
&& !is_header(optional_text)) {
|
|
msg_warn("bad REPLACE header text \"%s\" in %s map -- "
|
|
"need \"headername: headervalue\"",
|
|
optional_text, map_class);
|
|
return (buf);
|
|
} else {
|
|
cleanup_act_log(state, "replace", context, buf, optional_text);
|
|
return (mystrdup(optional_text));
|
|
}
|
|
}
|
|
if (STREQUAL(value, "REDIRECT", command_len)) {
|
|
if (strchr(optional_text, '@') == 0) {
|
|
msg_warn("bad REDIRECT target \"%s\" in %s map -- "
|
|
"need user@domain",
|
|
optional_text, map_class);
|
|
} else {
|
|
if (state->redirect)
|
|
myfree(state->redirect);
|
|
state->redirect = mystrdup(optional_text);
|
|
cleanup_act_log(state, "redirect", context, buf, optional_text);
|
|
state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
|
|
}
|
|
return (buf);
|
|
}
|
|
if (STREQUAL(value, "BCC", command_len)) {
|
|
if (strchr(optional_text, '@') == 0) {
|
|
msg_warn("bad BCC address \"%s\" in %s map -- "
|
|
"need user@domain",
|
|
optional_text, map_class);
|
|
} else {
|
|
if (state->hbc_rcpt == 0)
|
|
state->hbc_rcpt = argv_alloc(1);
|
|
argv_add(state->hbc_rcpt, optional_text, (char *) 0);
|
|
cleanup_act_log(state, "bcc", context, buf, optional_text);
|
|
}
|
|
return (buf);
|
|
}
|
|
if (STREQUAL(value, "STRIP", command_len)) {
|
|
cleanup_act_log(state, "strip", context, buf, optional_text);
|
|
return (CLEANUP_ACT_DROP);
|
|
}
|
|
/* Allow and ignore optional text after the action. */
|
|
|
|
if (STREQUAL(value, "IGNORE", command_len))
|
|
return (CLEANUP_ACT_DROP);
|
|
|
|
if (STREQUAL(value, "DUNNO", command_len)) /* preferred */
|
|
return (buf);
|
|
|
|
if (STREQUAL(value, "OK", command_len)) /* compat */
|
|
return (buf);
|
|
|
|
msg_warn("unknown command in %s map: %s", map_class, value);
|
|
return (buf);
|
|
}
|
|
|
|
/* cleanup_header_callback - process one complete header line */
|
|
|
|
static void cleanup_header_callback(void *context, int header_class,
|
|
const HEADER_OPTS *hdr_opts,
|
|
VSTRING *header_buf,
|
|
off_t unused_offset)
|
|
{
|
|
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
|
|
const char *myname = "cleanup_header_callback";
|
|
char *hdrval;
|
|
struct code_map {
|
|
const char *name;
|
|
const char *encoding;
|
|
};
|
|
static struct code_map code_map[] = { /* RFC 2045 */
|
|
"7bit", MAIL_ATTR_ENC_7BIT,
|
|
"8bit", MAIL_ATTR_ENC_8BIT,
|
|
"binary", MAIL_ATTR_ENC_8BIT, /* XXX Violation */
|
|
"quoted-printable", MAIL_ATTR_ENC_7BIT,
|
|
"base64", MAIL_ATTR_ENC_7BIT,
|
|
0,
|
|
};
|
|
struct code_map *cmp;
|
|
MAPS *checks;
|
|
const char *map_class;
|
|
|
|
if (msg_verbose)
|
|
msg_info("%s: '%.200s'", myname, vstring_str(header_buf));
|
|
|
|
/*
|
|
* Crude header filtering. This stops malware that isn't sophisticated
|
|
* enough to use fancy header encodings.
|
|
*/
|
|
#define CHECK(class, maps, var_name) \
|
|
(header_class == class && (map_class = var_name, checks = maps) != 0)
|
|
|
|
if (hdr_opts && (hdr_opts->flags & HDR_OPT_MIME))
|
|
header_class = MIME_HDR_MULTIPART;
|
|
|
|
/* Update the Received: header count before maybe dropping headers below. */
|
|
if (hdr_opts && hdr_opts->type == HDR_RECEIVED)
|
|
state->hop_count += 1;
|
|
|
|
if ((state->flags & CLEANUP_FLAG_FILTER)
|
|
&& (CHECK(MIME_HDR_PRIMARY, cleanup_header_checks, VAR_HEADER_CHECKS)
|
|
|| CHECK(MIME_HDR_MULTIPART, cleanup_mimehdr_checks, VAR_MIMEHDR_CHECKS)
|
|
|| CHECK(MIME_HDR_NESTED, cleanup_nesthdr_checks, VAR_NESTHDR_CHECKS))) {
|
|
char *header = vstring_str(header_buf);
|
|
const char *value;
|
|
|
|
if ((value = maps_find(checks, header, 0)) != 0) {
|
|
const char *result;
|
|
|
|
if ((result = cleanup_act(state, CLEANUP_ACT_CTXT_HEADER,
|
|
header, value, map_class))
|
|
== CLEANUP_ACT_DROP) {
|
|
return;
|
|
} else if (result != header) {
|
|
vstring_strcpy(header_buf, result);
|
|
hdr_opts = header_opts_find(result);
|
|
myfree((void *) result);
|
|
}
|
|
} else if (checks->error) {
|
|
msg_warn("%s: %s map lookup problem -- "
|
|
"message not accepted, try again later",
|
|
state->queue_id, checks->title);
|
|
state->errs |= CLEANUP_STAT_WRITE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If this is an "unknown" header, just copy it to the output without
|
|
* even bothering to fold long lines. cleanup_out() will split long
|
|
* headers that do not fit a REC_TYPE_NORM record.
|
|
*/
|
|
if (hdr_opts == 0) {
|
|
cleanup_out_header(state, header_buf);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Allow 8-bit type info to override 7-bit type info. XXX Should reuse
|
|
* the effort that went into MIME header parsing.
|
|
*/
|
|
hdrval = vstring_str(header_buf) + strlen(hdr_opts->name) + 1;
|
|
while (ISSPACE(*hdrval))
|
|
hdrval++;
|
|
/* trimblanks(hdrval, 0)[0] = 0; */
|
|
if (var_auto_8bit_enc_hdr
|
|
&& hdr_opts->type == HDR_CONTENT_TRANSFER_ENCODING) {
|
|
for (cmp = code_map; cmp->name != 0; cmp++) {
|
|
if (strcasecmp(hdrval, cmp->name) == 0) {
|
|
if (strcasecmp(cmp->encoding, MAIL_ATTR_ENC_8BIT) == 0)
|
|
nvtable_update(state->attr, MAIL_ATTR_ENCODING,
|
|
cmp->encoding);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copy attachment etc. header blocks without further inspection.
|
|
*/
|
|
if (header_class != MIME_HDR_PRIMARY) {
|
|
cleanup_out_header(state, header_buf);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Known header. Remember that we have seen at least one. Find out what
|
|
* we should do with this header: delete, count, rewrite. Note that we
|
|
* should examine headers even when they will be deleted from the output,
|
|
* because the addresses in those headers might be needed elsewhere.
|
|
*
|
|
* XXX 2821: Return-path breakage.
|
|
*
|
|
* RFC 821 specifies: When the receiver-SMTP makes the "final delivery" of a
|
|
* message it inserts at the beginning of the mail data a return path
|
|
* line. The return path line preserves the information in the
|
|
* <reverse-path> from the MAIL command. Here, final delivery means the
|
|
* message leaves the SMTP world. Normally, this would mean it has been
|
|
* delivered to the destination user, but in some cases it may be further
|
|
* processed and transmitted by another mail system.
|
|
*
|
|
* And that is what Postfix implements. Delivery agents prepend
|
|
* Return-Path:. In order to avoid cluttering up the message with
|
|
* possibly inconsistent Return-Path: information (the sender can change
|
|
* as the result of mail forwarding or mailing list delivery), Postfix
|
|
* removes any existing Return-Path: headers.
|
|
*
|
|
* RFC 2821 Section 4.4 specifies: A message-originating SMTP system
|
|
* SHOULD NOT send a message that already contains a Return-path header.
|
|
* SMTP servers performing a relay function MUST NOT inspect the message
|
|
* data, and especially not to the extent needed to determine if
|
|
* Return-path headers are present. SMTP servers making final delivery
|
|
* MAY remove Return-path headers before adding their own.
|
|
*/
|
|
else {
|
|
state->headers_seen |= (1 << hdr_opts->type);
|
|
if (hdr_opts->type == HDR_MESSAGE_ID) {
|
|
ssize_t len;
|
|
|
|
msg_info("%s: message-id=%s", state->queue_id, hdrval);
|
|
if (state->message_id == 0 && (len = balpar(hdrval, "<>")) > 0)
|
|
/* This Message ID may end up in threaded bounces. */
|
|
state->message_id = printable(mystrndup(hdrval, len), ' ');
|
|
}
|
|
if (hdr_opts->type == HDR_RESENT_MESSAGE_ID)
|
|
msg_info("%s: resent-message-id=%s", state->queue_id, hdrval);
|
|
if (hdr_opts->type == HDR_RECEIVED) {
|
|
if (state->hop_count >= var_hopcount_limit) {
|
|
msg_warn("%s: message rejected: hopcount exceeded",
|
|
state->queue_id);
|
|
state->errs |= CLEANUP_STAT_HOPS;
|
|
}
|
|
/* Save our Received: header after maybe updating headers above. */
|
|
if (state->hop_count == 1)
|
|
argv_add(state->auto_hdrs, vstring_str(header_buf), ARGV_END);
|
|
}
|
|
if (CLEANUP_OUT_OK(state)) {
|
|
if (hdr_opts->flags & HDR_OPT_RR)
|
|
state->resent = "Resent-";
|
|
if ((hdr_opts->flags & HDR_OPT_SENDER)
|
|
&& state->hdr_rewrite_context) {
|
|
cleanup_rewrite_sender(state, hdr_opts, header_buf);
|
|
} else if ((hdr_opts->flags & HDR_OPT_RECIP)
|
|
&& state->hdr_rewrite_context) {
|
|
cleanup_rewrite_recip(state, hdr_opts, header_buf);
|
|
} else if ((hdr_opts->flags & HDR_OPT_DROP) == 0) {
|
|
cleanup_out_header(state, header_buf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* cleanup_header_done_callback - insert missing message headers */
|
|
|
|
static void cleanup_header_done_callback(void *context)
|
|
{
|
|
const char *myname = "cleanup_header_done_callback";
|
|
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
|
|
char time_stamp[1024]; /* XXX locale dependent? */
|
|
struct tm *tp;
|
|
TOK822 *token;
|
|
TOK822 *dummy_token;
|
|
time_t tv;
|
|
|
|
/*
|
|
* XXX Workaround: when we reach the end of headers, mime_state_update()
|
|
* may execute up to three call-backs before returning to the caller:
|
|
* head_out(), head_end(), and body_out() or body_end(). As long as
|
|
* call-backs don't return a result, each call-back has to check for
|
|
* itself if the previous call-back experienced a problem.
|
|
*/
|
|
if (CLEANUP_OUT_OK(state) == 0)
|
|
return;
|
|
|
|
/*
|
|
* Future proofing: the Milter client's header suppression algorithm
|
|
* assumes that the MTA prepends its own Received: header. This
|
|
* assumption may be violated after some source-code update. The
|
|
* following check ensures consistency, at least for local submission.
|
|
*/
|
|
if (state->hop_count < 1) {
|
|
msg_warn("%s: message rejected: no Received: header",
|
|
state->queue_id);
|
|
state->errs |= CLEANUP_STAT_BAD;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Add a missing (Resent-)Message-Id: header. The message ID gives the
|
|
* time in GMT units, plus the local queue ID.
|
|
*
|
|
* XXX Message-Id is not a required message header (RFC 822 and RFC 2822).
|
|
*
|
|
* XXX It is the queue ID non-inode bits that prevent messages from getting
|
|
* the same Message-Id within the same second.
|
|
*
|
|
* XXX An arbitrary amount of time may pass between the start of the mail
|
|
* transaction and the creation of a queue file. Since we guarantee queue
|
|
* ID uniqueness only within a second, we must ensure that the time in
|
|
* the message ID matches the queue ID creation time, as long as we use
|
|
* the queue ID in the message ID.
|
|
*
|
|
* XXX We log a dummy name=value record so that we (hopefully) don't break
|
|
* compatibility with existing logfile analyzers, and so that we don't
|
|
* complicate future code that wants to log more name=value attributes.
|
|
*/
|
|
if ((state->hdr_rewrite_context || var_always_add_hdrs)
|
|
&& (state->headers_seen & (1 << (state->resent[0] ?
|
|
HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID))) == 0) {
|
|
if (var_long_queue_ids) {
|
|
vstring_sprintf(state->temp1, "%s@%s",
|
|
state->queue_id, var_myhostname);
|
|
} else {
|
|
tv = state->handle->ctime.tv_sec;
|
|
tp = gmtime(&tv);
|
|
strftime(time_stamp, sizeof(time_stamp), "%Y%m%d%H%M%S", tp);
|
|
vstring_sprintf(state->temp1, "%s.%s@%s",
|
|
time_stamp, state->queue_id, var_myhostname);
|
|
}
|
|
cleanup_out_format(state, REC_TYPE_NORM, "%sMessage-Id: <%s>",
|
|
state->resent, vstring_str(state->temp1));
|
|
msg_info("%s: %smessage-id=<%s>",
|
|
state->queue_id, *state->resent ? "resent-" : "",
|
|
vstring_str(state->temp1));
|
|
state->headers_seen |= (1 << (state->resent[0] ?
|
|
HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID));
|
|
if (state->resent[0] == 0 && state->message_id == 0)
|
|
state->message_id = concatenate("<", vstring_str(state->temp1),
|
|
">", (char *) 0);
|
|
|
|
}
|
|
if ((state->headers_seen & (1 << HDR_MESSAGE_ID)) == 0)
|
|
msg_info("%s: message-id=<>", state->queue_id);
|
|
|
|
/*
|
|
* Add a missing (Resent-)Date: header. The date is in local time units,
|
|
* with the GMT offset at the end.
|
|
*/
|
|
if ((state->hdr_rewrite_context || var_always_add_hdrs)
|
|
&& (state->headers_seen & (1 << (state->resent[0] ?
|
|
HDR_RESENT_DATE : HDR_DATE))) == 0) {
|
|
cleanup_out_format(state, REC_TYPE_NORM, "%sDate: %s",
|
|
state->resent, mail_date(state->arrival_time.tv_sec));
|
|
}
|
|
|
|
/*
|
|
* Add a missing (Resent-)From: header.
|
|
*/
|
|
if ((state->hdr_rewrite_context || var_always_add_hdrs)
|
|
&& (state->headers_seen & (1 << (state->resent[0] ?
|
|
HDR_RESENT_FROM : HDR_FROM))) == 0) {
|
|
quote_822_local(state->temp1, *state->sender ?
|
|
state->sender : MAIL_ADDR_MAIL_DAEMON);
|
|
if (*state->sender && state->fullname && *state->fullname) {
|
|
char *cp;
|
|
|
|
/* Enforce some sanity on full name content. */
|
|
while ((cp = strchr(state->fullname, '\r')) != 0
|
|
|| (cp = strchr(state->fullname, '\n')) != 0)
|
|
*cp = ' ';
|
|
|
|
/*
|
|
* "From: phrase <route-addr>". Quote the phrase if it contains
|
|
* specials or the "%!" legacy address operators.
|
|
*/
|
|
if (cleanup_hfrom_format == HFROM_FORMAT_CODE_STD) {
|
|
vstring_sprintf(state->temp2, "%sFrom: ", state->resent);
|
|
if (state->fullname[strcspn(state->fullname,
|
|
"%!" LEX_822_SPECIALS)] == 0) {
|
|
/* Normalize whitespace. */
|
|
token = tok822_scan_limit(state->fullname, &dummy_token,
|
|
var_token_limit);
|
|
} else {
|
|
token = tok822_alloc(TOK822_QSTRING, state->fullname);
|
|
}
|
|
if (token) {
|
|
tok822_externalize(state->temp2, token, TOK822_STR_NONE);
|
|
tok822_free(token);
|
|
vstring_strcat(state->temp2, " ");
|
|
}
|
|
vstring_sprintf_append(state->temp2, "<%s>",
|
|
vstring_str(state->temp1));
|
|
}
|
|
|
|
/*
|
|
* "From: addr-spec (ctext)". This is the obsolete form.
|
|
*/
|
|
else {
|
|
vstring_sprintf(state->temp2, "%sFrom: %s ",
|
|
state->resent, vstring_str(state->temp1));
|
|
vstring_sprintf(state->temp1, "(%s)", state->fullname);
|
|
token = tok822_parse(vstring_str(state->temp1));
|
|
tok822_externalize(state->temp2, token, TOK822_STR_NONE);
|
|
tok822_free_tree(token);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* "From: addr-spec". This is the form in the absence of full name
|
|
* information, also used for mail from mailer-daemon.
|
|
*/
|
|
else {
|
|
vstring_sprintf(state->temp2, "%sFrom: %s",
|
|
state->resent, vstring_str(state->temp1));
|
|
}
|
|
CLEANUP_OUT_BUF(state, REC_TYPE_NORM, state->temp2);
|
|
}
|
|
|
|
/*
|
|
* XXX 2821: Appendix B: The return address in the MAIL command SHOULD,
|
|
* if possible, be derived from the system's identity for the submitting
|
|
* (local) user, and the "From:" header field otherwise. If there is a
|
|
* system identity available, it SHOULD also be copied to the Sender
|
|
* header field if it is different from the address in the From header
|
|
* field. (Any Sender field that was already there SHOULD be removed.)
|
|
* Similar wording appears in RFC 2822 section 3.6.2.
|
|
*
|
|
* Postfix presently does not insert a Sender: header if envelope and From:
|
|
* address differ. Older Postfix versions assumed that the envelope
|
|
* sender address specifies the system identity and inserted Sender:
|
|
* whenever envelope and From: differed. This was wrong with relayed
|
|
* mail, and was often not even desirable with original submissions.
|
|
*
|
|
* XXX 2822 Section 3.6.2, as well as RFC 822 Section 4.1: FROM headers can
|
|
* contain multiple addresses. If this is the case, then a Sender: header
|
|
* must be provided with a single address.
|
|
*
|
|
* Postfix does not count the number of addresses in a From: header
|
|
* (although doing so is trivial, once the address is parsed).
|
|
*/
|
|
|
|
/*
|
|
* Add a missing destination header.
|
|
*/
|
|
#define VISIBLE_RCPT ((1 << HDR_TO) | (1 << HDR_RESENT_TO) \
|
|
| (1 << HDR_CC) | (1 << HDR_RESENT_CC))
|
|
|
|
if ((state->hdr_rewrite_context || var_always_add_hdrs)
|
|
&& (state->headers_seen & VISIBLE_RCPT) == 0 && *var_rcpt_witheld) {
|
|
if (!is_header(var_rcpt_witheld)) {
|
|
msg_warn("bad %s header text \"%s\" -- "
|
|
"need \"headername: headervalue\"",
|
|
VAR_RCPT_WITHELD, var_rcpt_witheld);
|
|
} else {
|
|
cleanup_out_format(state, REC_TYPE_NORM, "%s", var_rcpt_witheld);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Place a dummy PTR record right after the last header so that we can
|
|
* append headers without having to worry about clobbering the
|
|
* end-of-content marker.
|
|
*/
|
|
if (state->milters || cleanup_milters) {
|
|
if ((state->append_hdr_pt_offset = vstream_ftell(state->dst)) < 0)
|
|
msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
|
|
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L);
|
|
if ((state->append_hdr_pt_target = vstream_ftell(state->dst)) < 0)
|
|
msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
|
|
state->body_offset = state->append_hdr_pt_target;
|
|
}
|
|
}
|
|
|
|
/* cleanup_body_callback - output one body record */
|
|
|
|
static void cleanup_body_callback(void *context, int type,
|
|
const char *buf, ssize_t len,
|
|
off_t offset)
|
|
{
|
|
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
|
|
|
|
/*
|
|
* XXX Workaround: when we reach the end of headers, mime_state_update()
|
|
* may execute up to three call-backs before returning to the caller:
|
|
* head_out(), head_end(), and body_out() or body_end(). As long as
|
|
* call-backs don't return a result, each call-back has to check for
|
|
* itself if the previous call-back experienced a problem.
|
|
*/
|
|
if (CLEANUP_OUT_OK(state) == 0)
|
|
return;
|
|
|
|
/*
|
|
* Crude message body content filter for emergencies. This code has
|
|
* several problems: it sees one line at a time; it looks at long lines
|
|
* only in chunks of line_length_limit (2048) characters; it is easily
|
|
* bypassed with encodings and other tricks.
|
|
*/
|
|
if ((state->flags & CLEANUP_FLAG_FILTER)
|
|
&& cleanup_body_checks
|
|
&& (var_body_check_len == 0 || offset < var_body_check_len)) {
|
|
const char *value;
|
|
|
|
if ((value = maps_find(cleanup_body_checks, buf, 0)) != 0) {
|
|
const char *result;
|
|
|
|
if ((result = cleanup_act(state, CLEANUP_ACT_CTXT_BODY,
|
|
buf, value, VAR_BODY_CHECKS))
|
|
== CLEANUP_ACT_DROP) {
|
|
return;
|
|
} else if (result != buf) {
|
|
cleanup_out(state, type, result, strlen(result));
|
|
myfree((void *) result);
|
|
return;
|
|
}
|
|
} else if (cleanup_body_checks->error) {
|
|
msg_warn("%s: %s map lookup problem -- "
|
|
"message not accepted, try again later",
|
|
state->queue_id, cleanup_body_checks->title);
|
|
state->errs |= CLEANUP_STAT_WRITE;
|
|
}
|
|
}
|
|
cleanup_out(state, type, buf, len);
|
|
}
|
|
|
|
/* cleanup_message_headerbody - process message content, header and body */
|
|
|
|
static void cleanup_message_headerbody(CLEANUP_STATE *state, int type,
|
|
const char *buf, ssize_t len)
|
|
{
|
|
const char *myname = "cleanup_message_headerbody";
|
|
const MIME_STATE_DETAIL *detail;
|
|
const char *cp;
|
|
char *dst;
|
|
|
|
/*
|
|
* Replace each stray CR or LF with one space. These are not allowed in
|
|
* SMTP, and can be used to enable outbound (remote) SMTP smuggling.
|
|
* Replacing these early ensures that our later DKIM etc. signature will
|
|
* not be invalidated. Besides preventing SMTP smuggling, replacing stray
|
|
* <CR> or <LF> ensures that the result of signature validation by a
|
|
* later mail system will not depend on how that mail system handles
|
|
* those stray characters in an implementation-dependent manner.
|
|
*
|
|
* The input length is not changed, therefore it is safe to overwrite the
|
|
* input.
|
|
*/
|
|
if (var_cleanup_mask_stray_cr_lf)
|
|
for (dst = (char *) buf; dst < buf + len; dst++)
|
|
if (*dst == '\r' || *dst == '\n')
|
|
*dst = ' ';
|
|
|
|
/*
|
|
* Reject unwanted characters.
|
|
*
|
|
* XXX Possible optimization: simplify the loop when the "reject" set
|
|
* contains only one character.
|
|
*/
|
|
if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_reject_chars) {
|
|
for (cp = buf; cp < buf + len; cp++) {
|
|
if (memchr(vstring_str(cleanup_reject_chars),
|
|
*(const unsigned char *) cp,
|
|
VSTRING_LEN(cleanup_reject_chars))) {
|
|
cleanup_act(state, CLEANUP_ACT_CTXT_ANY,
|
|
buf, "REJECT disallowed character",
|
|
"character reject");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Strip unwanted characters. Don't overwrite the input.
|
|
*
|
|
* XXX Possible space+time optimization: use a bitset.
|
|
*
|
|
* XXX Possible optimization: simplify the loop when the "strip" set
|
|
* contains only one character.
|
|
*
|
|
* XXX Possible optimization: copy the input only if we really have to.
|
|
*/
|
|
if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_strip_chars) {
|
|
VSTRING_RESET(state->stripped_buf);
|
|
VSTRING_SPACE(state->stripped_buf, len + 1);
|
|
dst = vstring_str(state->stripped_buf);
|
|
for (cp = buf; cp < buf + len; cp++)
|
|
if (!memchr(vstring_str(cleanup_strip_chars),
|
|
*(const unsigned char *) cp,
|
|
VSTRING_LEN(cleanup_strip_chars)))
|
|
*dst++ = *cp;
|
|
*dst = 0;
|
|
buf = vstring_str(state->stripped_buf);
|
|
len = dst - buf;
|
|
}
|
|
|
|
/*
|
|
* Copy text record to the output.
|
|
*/
|
|
if (type == REC_TYPE_NORM || type == REC_TYPE_CONT) {
|
|
state->mime_errs = mime_state_update(state->mime_state, type, buf, len);
|
|
}
|
|
|
|
/*
|
|
* If we have reached the end of the message content segment, record the
|
|
* current file position so we can compute the message size lateron.
|
|
*/
|
|
else if (type == REC_TYPE_XTRA) {
|
|
state->mime_errs = mime_state_update(state->mime_state, type, buf, len);
|
|
if (state->milters || cleanup_milters)
|
|
/* Make room for body modification. */
|
|
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L);
|
|
/* Ignore header truncation after primary message headers. */
|
|
state->mime_errs &= ~MIME_ERR_TRUNC_HEADER;
|
|
if (state->mime_errs && state->reason == 0) {
|
|
state->errs |= CLEANUP_STAT_CONT;
|
|
detail = mime_state_detail(state->mime_errs);
|
|
state->reason = dsn_prepend(detail->dsn, detail->text);
|
|
}
|
|
state->mime_state = mime_state_free(state->mime_state);
|
|
if ((state->xtra_offset = vstream_ftell(state->dst)) < 0)
|
|
msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
|
|
state->cont_length = state->xtra_offset - state->data_offset;
|
|
state->action = cleanup_extracted;
|
|
}
|
|
|
|
/*
|
|
* This should never happen.
|
|
*/
|
|
else {
|
|
msg_warn("%s: message rejected: "
|
|
"unexpected record type %d in message content", myname, type);
|
|
state->errs |= CLEANUP_STAT_BAD;
|
|
}
|
|
}
|
|
|
|
/* cleanup_mime_error_callback - error report call-back routine */
|
|
|
|
static void cleanup_mime_error_callback(void *context, int err_code,
|
|
const char *text, ssize_t len)
|
|
{
|
|
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
|
|
const char *origin;
|
|
|
|
/*
|
|
* Message header too large errors are handled after the end of the
|
|
* primary message headers.
|
|
*/
|
|
if ((err_code & ~MIME_ERR_TRUNC_HEADER) != 0) {
|
|
if ((origin = nvtable_find(state->attr, MAIL_ATTR_LOG_ORIGIN)) == 0)
|
|
origin = MAIL_ATTR_ORG_NONE;
|
|
#define TEXT_LEN (len < 100 ? (int) len : 100)
|
|
msg_info("%s: reject: mime-error %s: %.*s from %s; from=<%s> to=<%s>",
|
|
state->queue_id, mime_state_error(err_code), TEXT_LEN, text,
|
|
origin, info_log_addr_form_sender(state->sender),
|
|
info_log_addr_form_recipient(state->recip ?
|
|
state->recip : "unknown"));
|
|
}
|
|
}
|
|
|
|
/* cleanup_message - initialize message content segment */
|
|
|
|
void cleanup_message(CLEANUP_STATE *state, int type, const char *buf, ssize_t len)
|
|
{
|
|
const char *myname = "cleanup_message";
|
|
int mime_options;
|
|
|
|
/*
|
|
* Write the start-of-content segment marker.
|
|
*/
|
|
cleanup_out_string(state, REC_TYPE_MESG, "");
|
|
if ((state->data_offset = vstream_ftell(state->dst)) < 0)
|
|
msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
|
|
|
|
/*
|
|
* Set up MIME processing options, if any. MIME_OPT_DISABLE_MIME disables
|
|
* special processing of Content-Type: headers, and thus, causes all text
|
|
* after the primary headers to be treated as the message body.
|
|
*/
|
|
mime_options = 0;
|
|
if (var_disable_mime_input) {
|
|
if (var_force_mime_iconv)
|
|
msg_fatal("do not specify both %s=yes and %s=yes",
|
|
VAR_DISABLE_MIME_INPUT, VAR_FORCE_MIME_ICONV);
|
|
mime_options |= MIME_OPT_DISABLE_MIME;
|
|
} else {
|
|
/* Turn off content checks if bouncing or forwarding mail. */
|
|
if (state->flags & CLEANUP_FLAG_FILTER) {
|
|
if (var_strict_8bitmime || var_strict_7bit_hdrs)
|
|
mime_options |= MIME_OPT_REPORT_8BIT_IN_HEADER;
|
|
if (var_strict_8bitmime || var_strict_8bit_body)
|
|
mime_options |= MIME_OPT_REPORT_8BIT_IN_7BIT_BODY;
|
|
if (var_strict_encoding)
|
|
mime_options |= MIME_OPT_REPORT_ENCODING_DOMAIN;
|
|
if (var_strict_8bitmime || var_strict_7bit_hdrs
|
|
|| var_strict_8bit_body || var_strict_encoding
|
|
|| *var_header_checks || *var_mimehdr_checks
|
|
|| *var_nesthdr_checks)
|
|
mime_options |= MIME_OPT_REPORT_NESTING;
|
|
}
|
|
if (var_force_mime_iconv)
|
|
mime_options |= MIME_OPT_DOWNGRADE;
|
|
}
|
|
state->mime_state = mime_state_alloc(mime_options,
|
|
cleanup_header_callback,
|
|
cleanup_header_done_callback,
|
|
cleanup_body_callback,
|
|
(MIME_STATE_ANY_END) 0,
|
|
cleanup_mime_error_callback,
|
|
(void *) state);
|
|
|
|
/*
|
|
* XXX Workaround: truncate a long message header so that we don't exceed
|
|
* the default Sendmail libmilter request size limit of 65535.
|
|
*/
|
|
#define KLUDGE_HEADER_LIMIT 60000
|
|
if ((cleanup_milters || state->milters)
|
|
&& var_header_limit > KLUDGE_HEADER_LIMIT)
|
|
var_header_limit = KLUDGE_HEADER_LIMIT;
|
|
|
|
/*
|
|
* Pass control to the header processing routine.
|
|
*/
|
|
state->action = cleanup_message_headerbody;
|
|
cleanup_message_headerbody(state, type, buf, len);
|
|
}
|