#include <sys_defs.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
#include <msg.h>
#include <vstream.h>
#include <vstring.h>
#include <stringops.h>
#include <off_cvt.h>
#include <dsn_mask.h>
#include <rec_type.h>
#include <cleanup_user.h>
#include <record.h>
#include <rec_attr_map.h>
#include <mail_proto.h>
#include <mail_params.h>
#include <lex_822.h>
#include <is_header.h>
#include <quote_821_local.h>
#include <dsn_util.h>
#include <cleanup.h>
#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)
static void cleanup_milter_hbc_log(void *context, const char *action,
const char *where, const char *line,
const char *optional_text)
{
const CLEANUP_STATE *state = (CLEANUP_STATE *) context;
const char *attr;
vstring_sprintf(state->temp1, "%s: milter-%s-%s: %s %.60s from %s[%s];",
state->queue_id, where, action, where, line,
state->client_name, state->client_addr);
if (state->sender)
vstring_sprintf_append(state->temp1, " from=<%s>", state->sender);
if (state->recip)
vstring_sprintf_append(state->temp1, " to=<%s>", 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 (optional_text)
vstring_sprintf_append(state->temp1, ": %s", optional_text);
msg_info("%s", vstring_str(state->temp1));
}
static void cleanup_milter_header_prepend(void *context, int rec_type,
const char *buf, ssize_t len, off_t offset)
{
msg_warn("the milter_header/body_checks prepend action is not implemented");
}
static char *cleanup_milter_hbc_extend(void *context, const char *command,
int cmd_len, const char *optional_text,
const char *where, const char *buf,
ssize_t buf_len, off_t offset)
{
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
const char *map_class = VAR_MILT_HEAD_CHECKS;
#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
#define CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state) \
((state->flags & CLEANUP_FLAG_DISCARD) || (state->errs & CLEANUP_STAT_CONT))
if ((state->flags & CLEANUP_FLAG_FILTER_ALL) == 0)
return ((char *) buf);
if (STREQUAL(command, "REJECT", cmd_len)) {
const CLEANUP_STAT_DETAIL *detail;
if (state->reason)
myfree(state->reason);
detail = cleanup_stat_detail(CLEANUP_STAT_CONT);
if (*optional_text) {
state->reason = dsn_prepend(detail->dsn, 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 {
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_milter_hbc_log(context, "reject", where, buf, state->reason);
vstring_sprintf(state->milter_hbc_reply, "%d %s",
detail->smtp, state->reason);
STR(state->milter_hbc_reply)[0] = *state->reason;
return ((char *) buf);
}
if (STREQUAL(command, "FILTER", cmd_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_milter_hbc_log(context, "filter", where, buf,
optional_text);
}
return ((char *) buf);
}
if (STREQUAL(command, "DISCARD", cmd_len)) {
cleanup_milter_hbc_log(context, "discard", where, buf, optional_text);
vstring_strcpy(state->milter_hbc_reply, "D");
state->flags |= CLEANUP_FLAG_DISCARD;
state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
return ((char *) buf);
}
if (STREQUAL(command, "HOLD", cmd_len)) {
if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) {
cleanup_milter_hbc_log(context, "hold", where, buf, optional_text);
state->flags |= CLEANUP_FLAG_HOLD;
}
return ((char *) buf);
}
if (STREQUAL(command, "REDIRECT", cmd_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_milter_hbc_log(context, "redirect", where, buf,
optional_text);
state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
}
return ((char *) buf);
}
return ((char *) HBC_CHECKS_STAT_UNKNOWN);
}
static int cleanup_milter_header_checks(CLEANUP_STATE *state, VSTRING *buf)
{
char *ret;
ret = hbc_header_checks((void *) state, state->milter_hbc_checks,
MIME_HDR_PRIMARY, (HEADER_OPTS *) 0,
buf, (off_t) 0);
if (ret == 0) {
return (0);
} else {
if (ret != STR(buf)) {
vstring_strcpy(buf, ret);
myfree(ret);
}
return (1);
}
}
static void cleanup_milter_hbc_add_meta_records(CLEANUP_STATE *state)
{
const char *myname = "cleanup_milter_hbc_add_meta_records";
off_t reverse_ptr_offset;
off_t new_meta_offset;
if (state->append_meta_pt_offset < 0)
msg_panic("%s: no meta append pointer location", myname);
if (state->append_meta_pt_target < 0)
msg_panic("%s: no meta append pointer target", myname);
if ((new_meta_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
state->errs |= CLEANUP_STAT_WRITE;
return;
}
if (state->filter != 0)
cleanup_out_string(state, REC_TYPE_FILT, state->filter);
if (state->redirect != 0)
cleanup_out_string(state, REC_TYPE_RDR, state->redirect);
if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) {
msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
state->errs |= CLEANUP_STAT_WRITE;
return;
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long) state->append_meta_pt_target);
if (vstream_fseek(state->dst, state->append_meta_pt_offset, SEEK_SET) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
state->errs |= CLEANUP_STAT_WRITE;
return;
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long) new_meta_offset);
state->append_meta_pt_offset = reverse_ptr_offset;
}
static void cleanup_milter_header_checks_init(CLEANUP_STATE *state)
{
#define NO_NESTED_HDR_NAME ""
#define NO_NESTED_HDR_VALUE ""
#define NO_MIME_HDR_NAME ""
#define NO_MIME_HDR_VALUE ""
static HBC_CALL_BACKS call_backs = {
cleanup_milter_hbc_log,
cleanup_milter_header_prepend,
cleanup_milter_hbc_extend,
};
state->milter_hbc_checks =
hbc_header_checks_create(VAR_MILT_HEAD_CHECKS, var_milt_head_checks,
NO_MIME_HDR_NAME, NO_MIME_HDR_VALUE,
NO_NESTED_HDR_NAME, NO_NESTED_HDR_VALUE,
&call_backs);
state->milter_hbc_reply = vstring_alloc(100);
if (state->filter)
myfree(state->filter);
state->filter = 0;
if (state->redirect)
myfree(state->redirect);
state->redirect = 0;
}
static void cleanup_milter_hbc_finish(CLEANUP_STATE *state)
{
if (state->milter_hbc_checks)
hbc_header_checks_free(state->milter_hbc_checks);
state->milter_hbc_checks = 0;
if (state->milter_hbc_reply)
vstring_free(state->milter_hbc_reply);
state->milter_hbc_reply = 0;
if (CLEANUP_OUT_OK(state)
&& !CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state)
&& (state->filter || state->redirect))
cleanup_milter_hbc_add_meta_records(state);
}
#define CLEANUP_MILTER_SET_REASON(__state, __reason) do { \
if ((__state)->reason) \
myfree((__state)->reason); \
(__state)->reason = mystrdup(__reason); \
if ((__state)->smtp_reply) { \
myfree((__state)->smtp_reply); \
(__state)->smtp_reply = 0; \
} \
} while (0)
#define CLEANUP_MILTER_SET_SMTP_REPLY(__state, __smtp_reply) do { \
if ((__state)->reason) \
myfree((__state)->reason); \
(__state)->reason = mystrdup(__smtp_reply + 4); \
printable((__state)->reason, '_'); \
if ((__state)->smtp_reply) \
myfree((__state)->smtp_reply); \
(__state)->smtp_reply = mystrdup(__smtp_reply); \
} while (0)
static void cleanup_milter_set_error(CLEANUP_STATE *state, int err)
{
if (err == EFBIG) {
msg_warn("%s: queue file size limit exceeded", state->queue_id);
state->errs |= CLEANUP_STAT_SIZE;
} else {
msg_warn("%s: write queue file: %m", state->queue_id);
state->errs |= CLEANUP_STAT_WRITE;
}
}
static const char *cleanup_milter_error(CLEANUP_STATE *state, int err)
{
const char *myname = "cleanup_milter_error";
const CLEANUP_STAT_DETAIL *dp;
if (err)
cleanup_milter_set_error(state, err);
else if (CLEANUP_OUT_OK(state))
msg_panic("%s: missing errno to error flag mapping", myname);
if (state->milter_err_text == 0)
state->milter_err_text = vstring_alloc(50);
dp = cleanup_stat_detail(state->errs);
return (STR(vstring_sprintf(state->milter_err_text,
"%d %s %s", dp->smtp, dp->dsn, dp->text)));
}
static const char *cleanup_add_header(void *context, const char *name,
const char *space,
const char *value)
{
const char *myname = "cleanup_add_header";
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
VSTRING *buf;
off_t reverse_ptr_offset;
off_t new_hdr_offset;
if (state->append_hdr_pt_offset < 0)
msg_panic("%s: no header append pointer location", myname);
if (state->append_hdr_pt_target < 0)
msg_panic("%s: no header append pointer target", myname);
buf = vstring_alloc(100);
vstring_sprintf(buf, "%s:%s%s", name, space, value);
if (state->milter_hbc_checks
&& cleanup_milter_header_checks(state, buf) == 0) {
vstring_free(buf);
return (0);
}
if ((new_hdr_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
vstring_free(buf);
return (cleanup_milter_error(state, errno));
}
cleanup_out_header(state, buf);
vstring_free(buf);
if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) {
msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long) state->append_hdr_pt_target);
if (vstream_fseek(state->dst, state->append_hdr_pt_offset, SEEK_SET) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long) new_hdr_offset);
state->append_hdr_pt_offset = reverse_ptr_offset;
return (CLEANUP_OUT_OK(state) == 0 ? cleanup_milter_error(state, 0) :
state->milter_hbc_reply && LEN(state->milter_hbc_reply) ?
STR(state->milter_hbc_reply) : 0);
}
static off_t cleanup_find_header_start(CLEANUP_STATE *state, ssize_t index,
const char *header_label,
VSTRING *buf,
int *prec_type,
int allow_ptr_backup,
int skip_headers)
{
const char *myname = "cleanup_find_header_start";
off_t curr_offset;
off_t ptr_offset;
VSTRING *ptr_buf = 0;
int rec_type = REC_TYPE_ERROR;
int last_type;
ssize_t len;
int hdr_count = 0;
if (msg_verbose)
msg_info("%s: index %ld name \"%s\"",
myname, (long) index, header_label ? header_label : "(none)");
if (index < 1)
msg_panic("%s: bad header index %ld", myname, (long) index);
#define CLEANUP_FIND_HEADER_NOTFOUND (-1)
#define CLEANUP_FIND_HEADER_IOERROR (-2)
#define CLEANUP_FIND_HEADER_RETURN(offs) do { \
if (ptr_buf) \
vstring_free(ptr_buf); \
return (offs); \
} while (0)
#define GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, buf, curr_offset, quit) \
if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) < 0) { \
msg_warn("%s: read file %s: %m", myname, cleanup_path); \
cleanup_milter_set_error(state, errno); \
do { quit; } while (0); \
} \
if (msg_verbose > 1) \
msg_info("%s: read: %ld: %.*s", myname, (long) curr_offset, \
LEN(buf) > 30 ? 30 : (int) LEN(buf), STR(buf)); \
if (rec_type == REC_TYPE_DTXT) \
continue; \
if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT \
&& rec_type != REC_TYPE_PTR) \
break;
if (vstream_fseek(state->dst, state->data_offset, SEEK_SET) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
cleanup_milter_set_error(state, errno);
CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
}
for (ptr_offset = 0, last_type = 0; ; ) {
if ((curr_offset = vstream_ftell(state->dst)) < 0) {
msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
cleanup_milter_set_error(state, errno);
CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
}
if (curr_offset == state->append_hdr_pt_offset)
break;
GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, buf, curr_offset,
CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR));
if (rec_type == REC_TYPE_PTR) {
if (rec_goto(state->dst, STR(buf)) < 0) {
msg_warn("%s: read file %s: %m", myname, cleanup_path);
cleanup_milter_set_error(state, errno);
CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
}
if (allow_ptr_backup) {
ptr_offset = curr_offset;
if (ptr_buf == 0)
ptr_buf = vstring_alloc(100);
vstring_strcpy(ptr_buf, STR(buf));
}
continue;
}
else if (last_type == REC_TYPE_CONT || IS_SPACE_TAB(STR(buf)[0])) {
}
else if ((len = is_header(STR(buf))) == 0) {
break;
}
else if (hdr_count++ < skip_headers)
;
else if ((header_label == 0
|| (strncasecmp(header_label, STR(buf), len) == 0
&& (IS_SPACE_TAB(STR(buf)[len])
|| STR(buf)[len] == ':')))
&& --index == 0) {
break;
}
ptr_offset = 0;
last_type = rec_type;
}
if (index > 0) {
curr_offset = CLEANUP_FIND_HEADER_NOTFOUND;
} else {
if (LEN(buf) < REC_TYPE_PTR_PAYL_SIZE) {
VSTRING *rbuf = (ptr_offset ? buf :
(ptr_buf ? ptr_buf :
(ptr_buf = vstring_alloc(100))));
int rval;
if ((rval = rec_get_raw(state->dst, rbuf, 0, REC_FLAG_NONE)) < 0) {
cleanup_milter_set_error(state, errno);
CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
}
if (rval != REC_TYPE_DTXT)
msg_panic("%s: short header without padding", myname);
}
if (ptr_offset != 0) {
rec_type = REC_TYPE_PTR;
curr_offset = ptr_offset;
vstring_strcpy(buf, STR(ptr_buf));
}
*prec_type = rec_type;
}
if (msg_verbose)
msg_info("%s: index %ld name %s type %d offset %ld",
myname, (long) index, header_label ?
header_label : "(none)", rec_type, (long) curr_offset);
CLEANUP_FIND_HEADER_RETURN(curr_offset);
}
static off_t cleanup_find_header_end(CLEANUP_STATE *state,
VSTRING *rec_buf,
int last_type)
{
const char *myname = "cleanup_find_header_end";
off_t read_offset;
int rec_type;
for (;;) {
if ((read_offset = vstream_ftell(state->dst)) < 0) {
msg_warn("%s: read file %s: %m", myname, cleanup_path);
cleanup_milter_error(state, errno);
return (-1);
}
if (read_offset == state->append_hdr_pt_offset)
break;
GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, rec_buf, read_offset,
return (-1));
if (rec_type == REC_TYPE_PTR) {
if (rec_goto(state->dst, STR(rec_buf)) < 0) {
msg_warn("%s: read file %s: %m", myname, cleanup_path);
cleanup_milter_error(state, errno);
return (-1);
}
continue;
}
if (last_type != REC_TYPE_CONT && !IS_SPACE_TAB(STR(rec_buf)[0]))
break;
last_type = rec_type;
}
return (read_offset);
}
static const char *cleanup_patch_header(CLEANUP_STATE *state,
const char *new_hdr_name,
const char *hdr_space,
const char *new_hdr_value,
off_t old_rec_offset,
int old_rec_type,
VSTRING *old_rec_buf,
off_t next_offset)
{
const char *myname = "cleanup_patch_header";
VSTRING *buf = vstring_alloc(100);
off_t new_hdr_offset;
#define CLEANUP_PATCH_HEADER_RETURN(ret) do { \
vstring_free(buf); \
return (ret); \
} while (0)
if (msg_verbose)
msg_info("%s: \"%s\" \"%s\" at %ld",
myname, new_hdr_name, new_hdr_value, (long) old_rec_offset);
vstring_sprintf(buf, "%s:%s%s", new_hdr_name, hdr_space, new_hdr_value);
if (state->milter_hbc_checks
&& cleanup_milter_header_checks(state, buf) == 0)
CLEANUP_PATCH_HEADER_RETURN(0);
if ((new_hdr_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
CLEANUP_PATCH_HEADER_RETURN(cleanup_milter_error(state, errno));
}
cleanup_out_header(state, buf);
if (msg_verbose > 1)
msg_info("%s: %ld: write %.*s", myname, (long) new_hdr_offset,
LEN(buf) > 30 ? 30 : (int) LEN(buf), STR(buf));
if (old_rec_type > 0) {
CLEANUP_OUT_BUF(state, old_rec_type, old_rec_buf);
if (LEN(old_rec_buf) < REC_TYPE_PTR_PAYL_SIZE)
rec_pad(state->dst, REC_TYPE_DTXT,
REC_TYPE_PTR_PAYL_SIZE - LEN(old_rec_buf));
if (msg_verbose > 1)
msg_info("%s: write %.*s", myname, LEN(old_rec_buf) > 30 ?
30 : (int) LEN(old_rec_buf), STR(old_rec_buf));
}
if (old_rec_type != REC_TYPE_PTR) {
if (next_offset < 0)
msg_panic("%s: bad reverse pointer %ld",
myname, (long) next_offset);
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long) next_offset);
if (msg_verbose > 1)
msg_info("%s: write PTR %ld", myname, (long) next_offset);
}
if (vstream_fseek(state->dst, old_rec_offset, SEEK_SET) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
CLEANUP_PATCH_HEADER_RETURN(cleanup_milter_error(state, errno));
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long) new_hdr_offset);
if (msg_verbose > 1)
msg_info("%s: %ld: write PTR %ld", myname, (long) old_rec_offset,
(long) new_hdr_offset);
CLEANUP_PATCH_HEADER_RETURN(
CLEANUP_OUT_OK(state) == 0 ? cleanup_milter_error(state, 0) :
state->milter_hbc_reply && LEN(state->milter_hbc_reply) ?
STR(state->milter_hbc_reply) : 0);
}
static const char *cleanup_ins_header(void *context, ssize_t index,
const char *new_hdr_name,
const char *hdr_space,
const char *new_hdr_value)
{
const char *myname = "cleanup_ins_header";
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
VSTRING *old_rec_buf = vstring_alloc(100);
off_t old_rec_offset;
int old_rec_type;
off_t next_offset;
const char *ret;
#define CLEANUP_INS_HEADER_RETURN(ret) do { \
vstring_free(old_rec_buf); \
return (ret); \
} while (0)
if (msg_verbose)
msg_info("%s: %ld \"%s\" \"%s\"",
myname, (long) index, new_hdr_name, new_hdr_value);
#define NO_HEADER_NAME ((char *) 0)
#define ALLOW_PTR_BACKUP 1
#define SKIP_ONE_HEADER 1
#define DONT_SKIP_HEADERS 0
if (index < 1)
index = 1;
old_rec_offset = cleanup_find_header_start(state, index, NO_HEADER_NAME,
old_rec_buf, &old_rec_type,
ALLOW_PTR_BACKUP,
DONT_SKIP_HEADERS);
if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR)
CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state, 0));
if (old_rec_offset < 0)
CLEANUP_INS_HEADER_RETURN(cleanup_add_header(context, new_hdr_name,
hdr_space, new_hdr_value));
if (old_rec_type == REC_TYPE_PTR) {
next_offset = -1;
} else {
if ((next_offset = vstream_ftell(state->dst)) < 0) {
msg_warn("%s: read file %s: %m", myname, cleanup_path);
CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state, errno));
}
}
ret = cleanup_patch_header(state, new_hdr_name, hdr_space, new_hdr_value,
old_rec_offset, old_rec_type,
old_rec_buf, next_offset);
CLEANUP_INS_HEADER_RETURN(ret);
}
static const char *cleanup_upd_header(void *context, ssize_t index,
const char *new_hdr_name,
const char *hdr_space,
const char *new_hdr_value)
{
const char *myname = "cleanup_upd_header";
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
VSTRING *rec_buf;
off_t old_rec_offset;
off_t next_offset;
int last_type;
const char *ret;
if (msg_verbose)
msg_info("%s: %ld \"%s\" \"%s\"",
myname, (long) index, new_hdr_name, new_hdr_value);
if (*new_hdr_name == 0)
msg_panic("%s: null header name", myname);
#define DONT_SAVE_RECORD 0
#define NO_PTR_BACKUP 0
#define CLEANUP_UPD_HEADER_RETURN(ret) do { \
vstring_free(rec_buf); \
return (ret); \
} while (0)
rec_buf = vstring_alloc(100);
old_rec_offset = cleanup_find_header_start(state, index, new_hdr_name,
rec_buf, &last_type,
NO_PTR_BACKUP,
SKIP_ONE_HEADER);
if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR)
CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state, 0));
if (old_rec_offset < 0)
CLEANUP_UPD_HEADER_RETURN(cleanup_add_header(context, new_hdr_name,
hdr_space, new_hdr_value));
if ((next_offset = cleanup_find_header_end(state, rec_buf, last_type)) < 0)
CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state, 0));
ret = cleanup_patch_header(state, new_hdr_name, hdr_space, new_hdr_value,
old_rec_offset, DONT_SAVE_RECORD,
(VSTRING *) 0, next_offset);
CLEANUP_UPD_HEADER_RETURN(ret);
}
static const char *cleanup_del_header(void *context, ssize_t index,
const char *hdr_name)
{
const char *myname = "cleanup_del_header";
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
VSTRING *rec_buf;
off_t header_offset;
off_t next_offset;
int last_type;
if (msg_verbose)
msg_info("%s: %ld \"%s\"", myname, (long) index, hdr_name);
if (*hdr_name == 0)
msg_panic("%s: null header name", myname);
#define CLEANUP_DEL_HEADER_RETURN(ret) do { \
vstring_free(rec_buf); \
return (ret); \
} while (0)
rec_buf = vstring_alloc(100);
header_offset = cleanup_find_header_start(state, index, hdr_name, rec_buf,
&last_type, NO_PTR_BACKUP,
SKIP_ONE_HEADER);
if (header_offset == CLEANUP_FIND_HEADER_IOERROR)
CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, 0));
if (header_offset > 0) {
if ((next_offset = cleanup_find_header_end(state, rec_buf, last_type)) < 0)
CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, 0));
if (vstream_fseek(state->dst, header_offset, SEEK_SET) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, errno));
}
rec_fprintf(state->dst, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long) next_offset);
}
vstring_free(rec_buf);
return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0));
}
static const char *cleanup_chg_from(void *context, const char *ext_from,
const char *esmtp_args)
{
const char *myname = "cleanup_chg_from";
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
off_t new_sender_offset;
int addr_count;
TOK822 *tree;
TOK822 *tp;
VSTRING *int_sender_buf;
if (msg_verbose)
msg_info("%s: \"%s\" \"%s\"", myname, ext_from, esmtp_args);
if (esmtp_args[0])
msg_warn("%s: %s: ignoring ESMTP arguments \"%.100s\"",
state->queue_id, myname, esmtp_args);
if (state->sender_pt_offset < 0)
msg_panic("%s: no original sender record offset", myname);
if (state->sender_pt_target < 0)
msg_panic("%s: no post-sender record offset", myname);
if ((new_sender_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
int_sender_buf = vstring_alloc(strlen(ext_from) + 1);
tree = tok822_parse(ext_from);
for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) {
if (tp->type == TOK822_ADDR) {
if (addr_count == 0) {
tok822_internalize(int_sender_buf, tp->head, TOK822_STR_DEFL);
addr_count += 1;
} else {
msg_warn("%s: Milter request to add multi-sender: \"%s\"",
state->queue_id, ext_from);
break;
}
}
}
tok822_free_tree(tree);
cleanup_addr_sender(state, STR(int_sender_buf));
vstring_free(int_sender_buf);
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long) state->sender_pt_target);
if (vstream_fseek(state->dst, state->sender_pt_offset, SEEK_SET) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long) new_sender_offset);
return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0));
}
static const char *cleanup_add_rcpt(void *context, const char *ext_rcpt)
{
const char *myname = "cleanup_add_rcpt";
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
off_t new_rcpt_offset;
off_t reverse_ptr_offset;
int addr_count;
TOK822 *tree;
TOK822 *tp;
VSTRING *int_rcpt_buf;
if (msg_verbose)
msg_info("%s: \"%s\"", myname, ext_rcpt);
if (state->append_rcpt_pt_offset < 0)
msg_panic("%s: no recipient append pointer location", myname);
if (state->append_rcpt_pt_target < 0)
msg_panic("%s: no recipient append pointer target", myname);
#define NO_DSN_ORCPT ((char *) 0)
if ((new_rcpt_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
int_rcpt_buf = vstring_alloc(strlen(ext_rcpt) + 1);
tree = tok822_parse(ext_rcpt);
for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) {
if (tp->type == TOK822_ADDR) {
if (addr_count == 0) {
tok822_internalize(int_rcpt_buf, tp->head, TOK822_STR_DEFL);
addr_count += 1;
} else {
msg_warn("%s: Milter request to add multi-recipient: \"%s\"",
state->queue_id, ext_rcpt);
break;
}
}
}
tok822_free_tree(tree);
cleanup_addr_bcc_dsn(state, STR(int_rcpt_buf), NO_DSN_ORCPT, DEF_DSN_NOTIFY);
vstring_free(int_rcpt_buf);
if (addr_count == 0) {
msg_warn("%s: ignoring attempt from Milter to add null recipient",
state->queue_id);
return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0));
}
if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) {
msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long) state->append_rcpt_pt_target);
if (vstream_fseek(state->dst, state->append_rcpt_pt_offset, SEEK_SET) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long) new_rcpt_offset);
state->append_rcpt_pt_offset = reverse_ptr_offset;
return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0));
}
static const char *cleanup_add_rcpt_par(void *context, const char *ext_rcpt,
const char *esmtp_args)
{
const char *myname = "cleanup_add_rcpt";
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
if (esmtp_args[0])
msg_warn("%s: %s: ignoring ESMTP arguments \"%.100s\"",
state->queue_id, myname, esmtp_args);
return (cleanup_add_rcpt(context, ext_rcpt));
}
static const char *cleanup_del_rcpt(void *context, const char *ext_rcpt)
{
const char *myname = "cleanup_del_rcpt";
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
off_t curr_offset;
VSTRING *buf;
char *attr_name;
char *attr_value;
char *dsn_orcpt = 0;
int dsn_notify = 0;
char *orig_rcpt = 0;
char *start;
int rec_type;
int junk;
int count = 0;
TOK822 *tree;
TOK822 *tp;
VSTRING *int_rcpt_buf;
int addr_count;
if (msg_verbose)
msg_info("%s: \"%s\"", myname, ext_rcpt);
if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
#define CLEANUP_DEL_RCPT_RETURN(ret) do { \
if (orig_rcpt != 0) \
myfree(orig_rcpt); \
if (dsn_orcpt != 0) \
myfree(dsn_orcpt); \
vstring_free(buf); \
vstring_free(int_rcpt_buf); \
return (ret); \
} while (0)
int_rcpt_buf = vstring_alloc(strlen(ext_rcpt) + 1);
tree = tok822_parse(ext_rcpt);
for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) {
if (tp->type == TOK822_ADDR) {
if (addr_count == 0) {
tok822_internalize(int_rcpt_buf, tp->head, TOK822_STR_DEFL);
addr_count += 1;
} else {
msg_warn("%s: Milter request to drop multi-recipient: \"%s\"",
state->queue_id, ext_rcpt);
break;
}
}
}
tok822_free_tree(tree);
buf = vstring_alloc(100);
for (;;) {
if (CLEANUP_OUT_OK(state) == 0)
CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, 0));
if ((curr_offset = vstream_ftell(state->dst)) < 0) {
msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
}
if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) <= 0) {
msg_warn("%s: read file %s: %m", myname, cleanup_path);
CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
}
if (rec_type == REC_TYPE_END)
break;
if (rec_type == REC_TYPE_MESG) {
if (vstream_fseek(state->dst, state->xtra_offset, SEEK_SET) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
}
continue;
}
start = STR(buf);
if (rec_type == REC_TYPE_PTR) {
if (rec_goto(state->dst, start) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
}
continue;
}
if (rec_type == REC_TYPE_ATTR) {
if (split_nameval(STR(buf), &attr_name, &attr_value) != 0
|| *attr_value == 0)
continue;
if ((junk = rec_attr_map(attr_name)) != 0) {
start = attr_value;
rec_type = junk;
}
}
switch (rec_type) {
case REC_TYPE_DSN_ORCPT:
if (dsn_orcpt != 0)
myfree(dsn_orcpt);
dsn_orcpt = mystrdup(start);
break;
case REC_TYPE_DSN_NOTIFY:
if (alldig(start) && (junk = atoi(start)) > 0
&& DSN_NOTIFY_OK(junk))
dsn_notify = junk;
else
dsn_notify = 0;
break;
case REC_TYPE_ORCP:
if (orig_rcpt != 0)
myfree(orig_rcpt);
orig_rcpt = mystrdup(start);
break;
case REC_TYPE_RCPT:
if (strcmp(orig_rcpt ? orig_rcpt : start, STR(int_rcpt_buf)) == 0) {
if (vstream_fseek(state->dst, curr_offset, SEEK_SET) < 0) {
msg_warn("%s: seek file %s: %m", myname, cleanup_path);
CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
}
if (REC_PUT_BUF(state->dst, REC_TYPE_DRCP, buf) < 0) {
msg_warn("%s: write queue file: %m", state->queue_id);
CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
}
count++;
}
case REC_TYPE_DRCP:
case REC_TYPE_DONE:
if (orig_rcpt != 0) {
myfree(orig_rcpt);
orig_rcpt = 0;
}
if (dsn_orcpt != 0) {
myfree(dsn_orcpt);
dsn_orcpt = 0;
}
dsn_notify = 0;
break;
}
}
if (msg_verbose)
msg_info("%s: deleted %d records for recipient \"%s\"",
myname, count, ext_rcpt);
CLEANUP_DEL_RCPT_RETURN(0);
}
static const char *cleanup_repl_body(void *context, int cmd, VSTRING *buf)
{
const char *myname = "cleanup_repl_body";
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
static VSTRING empty;
switch (cmd) {
case MILTER_BODY_LINE:
if (cleanup_body_edit_write(state, REC_TYPE_NORM, buf) < 0)
return (cleanup_milter_error(state, errno));
break;
case MILTER_BODY_START:
VSTRING_RESET(&empty);
if (cleanup_body_edit_start(state) < 0
|| cleanup_body_edit_write(state, REC_TYPE_NORM, &empty) < 0)
return (cleanup_milter_error(state, errno));
break;
case MILTER_BODY_END:
if (cleanup_body_edit_finish(state) < 0)
return (cleanup_milter_error(state, errno));
break;
default:
msg_panic("%s: bad command: %d", myname, cmd);
}
return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, errno));
}
static const char *cleanup_milter_eval(const char *name, void *ptr)
{
CLEANUP_STATE *state = (CLEANUP_STATE *) ptr;
if (*name != '{') {
vstring_sprintf(state->temp1, "{%s}", name);
name = STR(state->temp1);
}
if (strcmp(name, S8_MAC_DAEMON_NAME) == 0)
return (var_milt_daemon_name);
if (strcmp(name, S8_MAC_V) == 0)
return (var_milt_v);
#ifndef CLIENT_ATTR_UNKNOWN
#define CLIENT_ATTR_UNKNOWN "unknown"
#endif
if (strcmp(name, S8_MAC__) == 0) {
vstring_sprintf(state->temp1, "%s [%s]",
state->reverse_name, state->client_addr);
if (strcasecmp(state->client_name, state->reverse_name) != 0)
vstring_strcat(state->temp1, " (may be forged)");
return (STR(state->temp1));
}
if (strcmp(name, S8_MAC_J) == 0)
return (var_myhostname);
if (strcmp(name, S8_MAC_CLIENT_ADDR) == 0)
return (state->client_addr);
if (strcmp(name, S8_MAC_CLIENT_NAME) == 0)
return (state->client_name);
if (strcmp(name, S8_MAC_CLIENT_PORT) == 0)
return (state->client_port
&& strcmp(state->client_port, CLIENT_ATTR_UNKNOWN) ?
state->client_port : "0");
if (strcmp(name, S8_MAC_CLIENT_PTR) == 0)
return (state->reverse_name);
if (strcmp(name, S8_MAC_I) == 0)
return (state->queue_id);
#ifdef USE_SASL_AUTH
if (strcmp(name, S8_MAC_AUTH_TYPE) == 0)
return (nvtable_find(state->attr, MAIL_ATTR_SASL_METHOD));
if (strcmp(name, S8_MAC_AUTH_AUTHEN) == 0)
return (nvtable_find(state->attr, MAIL_ATTR_SASL_USERNAME));
if (strcmp(name, S8_MAC_AUTH_AUTHOR) == 0)
return (nvtable_find(state->attr, MAIL_ATTR_SASL_SENDER));
#endif
if (strcmp(name, S8_MAC_MAIL_ADDR) == 0)
return (state->milter_ext_from ? STR(state->milter_ext_from) : 0);
if (strcmp(name, S8_MAC_RCPT_ADDR) == 0)
return (state->milter_ext_rcpt ? STR(state->milter_ext_rcpt) : 0);
return (0);
}
void cleanup_milter_receive(CLEANUP_STATE *state, int count)
{
if (state->milters)
milter_free(state->milters);
state->milters = milter_receive(state->src, count);
if (state->milters == 0)
msg_fatal("cleanup_milter_receive: milter receive failed");
if (count <= 0)
return;
milter_macro_callback(state->milters, cleanup_milter_eval, (void *) state);
milter_edit_callback(state->milters,
cleanup_add_header, cleanup_upd_header,
cleanup_ins_header, cleanup_del_header,
cleanup_chg_from, cleanup_add_rcpt,
cleanup_add_rcpt_par, cleanup_del_rcpt,
cleanup_repl_body, (void *) state);
}
static const char *cleanup_milter_apply(CLEANUP_STATE *state, const char *event,
const char *resp)
{
const char *myname = "cleanup_milter_apply";
const char *action;
const char *text;
const char *attr;
const char *ret = 0;
if (msg_verbose)
msg_info("%s: %s", myname, resp);
if (state->milter_hbc_reply &&
strcmp(resp, STR(state->milter_hbc_reply)) == 0)
return (0);
if (CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state)) {
if (msg_verbose)
msg_info("%s: ignoring redundant or conflicting milter reply: %s",
state->queue_id, resp);
return (0);
}
if (state->client_name == 0)
msg_panic("%s: missing client info initialization", myname);
if (CLEANUP_OUT_OK(state) == 0)
return (0);
switch (resp[0]) {
case 'H':
if (state->flags & CLEANUP_FLAG_HOLD)
return (0);
state->flags |= CLEANUP_FLAG_HOLD;
action = "milter-hold";
text = "milter triggers HOLD action";
break;
case 'D':
state->flags |= CLEANUP_FLAG_DISCARD;
action = "milter-discard";
text = "milter triggers DISCARD action";
break;
case 'S':
state->flags |= CLEANUP_STAT_CONT;
action = "milter-reject";
text = cleanup_strerror(CLEANUP_STAT_CONT);
break;
case '4':
CLEANUP_MILTER_SET_SMTP_REPLY(state, resp);
ret = state->reason;
state->errs |= CLEANUP_STAT_DEFER;
action = "milter-reject";
text = resp + 4;
break;
case '5':
CLEANUP_MILTER_SET_SMTP_REPLY(state, resp);
ret = state->reason;
state->errs |= CLEANUP_STAT_CONT;
action = "milter-reject";
text = resp + 4;
break;
default:
msg_panic("%s: unexpected mail filter reply: %s", myname, resp);
}
vstring_sprintf(state->temp1, "%s: %s: %s from %s[%s]: %s;",
state->queue_id, action, event, state->client_name,
state->client_addr, text);
if (state->sender)
vstring_sprintf_append(state->temp1, " from=<%s>", state->sender);
if (state->recip)
vstring_sprintf_append(state->temp1, " to=<%s>", 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);
msg_info("%s", vstring_str(state->temp1));
return (ret);
}
static void cleanup_milter_client_init(CLEANUP_STATE *state)
{
const char *proto_attr;
#define NO_CLIENT_PORT "0"
state->client_name = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_NAME);
state->reverse_name =
nvtable_find(state->attr, MAIL_ATTR_ACT_REVERSE_CLIENT_NAME);
state->client_addr = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_ADDR);
state->client_port = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_PORT);
proto_attr = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_AF);
if (state->client_name == 0 || state->client_addr == 0 || proto_attr == 0
|| !alldig(proto_attr)) {
state->client_name = "localhost";
state->client_addr = "127.0.0.1";
state->client_af = AF_INET;
} else
state->client_af = atoi(proto_attr);
if (state->reverse_name == 0)
state->reverse_name = state->client_name;
if (state->client_port == 0)
state->client_port = NO_CLIENT_PORT;
}
void cleanup_milter_inspect(CLEANUP_STATE *state, MILTERS *milters)
{
const char *myname = "cleanup_milter";
const char *resp;
if (msg_verbose)
msg_info("enter %s", myname);
if (state->client_name == 0)
cleanup_milter_client_init(state);
if (*var_milt_head_checks)
cleanup_milter_header_checks_init(state);
if ((resp = milter_message(milters, state->handle->stream,
state->data_offset)) != 0)
cleanup_milter_apply(state, "END-OF-MESSAGE", resp);
if (*var_milt_head_checks)
cleanup_milter_hbc_finish(state);
if (msg_verbose)
msg_info("leave %s", myname);
}
void cleanup_milter_emul_mail(CLEANUP_STATE *state,
MILTERS *milters,
const char *addr)
{
const char *resp;
const char *helo;
const char *argv[2];
milter_macro_callback(milters, cleanup_milter_eval, (void *) state);
milter_edit_callback(milters,
cleanup_add_header, cleanup_upd_header,
cleanup_ins_header, cleanup_del_header,
cleanup_chg_from, cleanup_add_rcpt,
cleanup_add_rcpt_par, cleanup_del_rcpt,
cleanup_repl_body, (void *) state);
if (state->client_name == 0)
cleanup_milter_client_init(state);
if ((resp = milter_conn_event(milters, state->client_name, state->client_addr,
state->client_port, state->client_af)) != 0) {
cleanup_milter_apply(state, "CONNECT", resp);
return;
}
#define PRETEND_ESMTP 1
if (CLEANUP_MILTER_OK(state)) {
if ((helo = nvtable_find(state->attr, MAIL_ATTR_ACT_HELO_NAME)) == 0)
helo = state->client_name;
if ((resp = milter_helo_event(milters, helo, PRETEND_ESMTP)) != 0) {
cleanup_milter_apply(state, "EHLO", resp);
return;
}
}
if (CLEANUP_MILTER_OK(state)) {
if (state->milter_ext_from == 0)
state->milter_ext_from = vstring_alloc(100);
if (*addr)
quote_821_local(state->milter_ext_from, addr);
else
vstring_strcpy(state->milter_ext_from, addr);
argv[0] = STR(state->milter_ext_from);
argv[1] = 0;
if ((resp = milter_mail_event(milters, argv)) != 0) {
cleanup_milter_apply(state, "MAIL", resp);
return;
}
}
}
void cleanup_milter_emul_rcpt(CLEANUP_STATE *state,
MILTERS *milters,
const char *addr)
{
const char *myname = "cleanup_milter_emul_rcpt";
const char *resp;
const char *argv[2];
if (state->client_name == 0)
msg_panic("%s: missing client info initialization", myname);
if (state->milter_ext_rcpt == 0)
state->milter_ext_rcpt = vstring_alloc(100);
if (*addr)
quote_821_local(state->milter_ext_rcpt, addr);
else
vstring_strcpy(state->milter_ext_rcpt, addr);
argv[0] = STR(state->milter_ext_rcpt);
argv[1] = 0;
if ((resp = milter_rcpt_event(milters, MILTER_FLAG_NONE, argv)) != 0
&& cleanup_milter_apply(state, "RCPT", resp) != 0) {
msg_warn("%s: milter configuration error: can't reject recipient "
"in non-smtpd(8) submission", state->queue_id);
msg_warn("%s: deferring delivery of this message", state->queue_id);
CLEANUP_MILTER_SET_REASON(state, "4.3.5 Server configuration error");
state->errs |= CLEANUP_STAT_DEFER;
}
}
void cleanup_milter_emul_data(CLEANUP_STATE *state, MILTERS *milters)
{
const char *myname = "cleanup_milter_emul_data";
const char *resp;
if (state->client_name == 0)
msg_panic("%s: missing client info initialization", myname);
if ((resp = milter_data_event(milters)) != 0)
cleanup_milter_apply(state, "DATA", resp);
}
#ifdef TEST
#include <stdio.h>
#include <msg_vstream.h>
#include <vstring_vstream.h>
#include <mail_addr.h>
#include <mail_version.h>
#undef msg_verbose
char *cleanup_path;
VSTRING *cleanup_trace_path;
VSTRING *cleanup_strip_chars;
int cleanup_comm_canon_flags;
MAPS *cleanup_comm_canon_maps;
int cleanup_ext_prop_mask;
ARGV *cleanup_masq_domains;
int cleanup_masq_flags;
MAPS *cleanup_rcpt_bcc_maps;
int cleanup_rcpt_canon_flags;
MAPS *cleanup_rcpt_canon_maps;
MAPS *cleanup_send_bcc_maps;
int cleanup_send_canon_flags;
MAPS *cleanup_send_canon_maps;
int var_dup_filter_limit = DEF_DUP_FILTER_LIMIT;
char *var_empty_addr = DEF_EMPTY_ADDR;
int var_enable_orcpt = DEF_ENABLE_ORCPT;
MAPS *cleanup_virt_alias_maps;
char *var_milt_daemon_name = "host.example.com";
char *var_milt_v = DEF_MILT_V;
MILTERS *cleanup_milters = (MILTERS *) ((char *) sizeof(*cleanup_milters));
char *var_milt_head_checks = "";
int cleanup_masquerade_internal(VSTRING *addr, ARGV *masq_domains)
{
msg_panic("cleanup_masquerade_internal dummy");
}
int cleanup_rewrite_internal(const char *context, VSTRING *result,
const char *addr)
{
vstring_strcpy(result, addr);
return (0);
}
int cleanup_map11_internal(CLEANUP_STATE *state, VSTRING *addr,
MAPS *maps, int propagate)
{
msg_panic("cleanup_map11_internal dummy");
}
ARGV *cleanup_map1n_internal(CLEANUP_STATE *state, const char *addr,
MAPS *maps, int propagate)
{
msg_panic("cleanup_map1n_internal dummy");
}
void cleanup_envelope(CLEANUP_STATE *state, int type, const char *buf,
ssize_t len)
{
msg_panic("cleanup_envelope dummy");
}
static void usage(void)
{
msg_warn("usage:");
msg_warn(" verbose on|off");
msg_warn(" open pathname");
msg_warn(" close");
msg_warn(" add_header index name [value]");
msg_warn(" ins_header index name [value]");
msg_warn(" upd_header index name [value]");
msg_warn(" del_header index name");
msg_warn(" chg_from addr parameters");
msg_warn(" add_rcpt addr");
msg_warn(" add_rcpt_par addr parameters");
msg_warn(" del_rcpt addr");
msg_warn(" replbody pathname");
msg_warn(" header_checks type:name");
}
static void flatten_args(VSTRING *buf, char **argv)
{
char **cpp;
VSTRING_RESET(buf);
for (cpp = argv; *cpp; cpp++) {
vstring_strcat(buf, *cpp);
if (cpp[1])
VSTRING_ADDCH(buf, ' ');
}
VSTRING_TERMINATE(buf);
}
static void open_queue_file(CLEANUP_STATE *state, const char *path)
{
VSTRING *buf = vstring_alloc(100);
off_t curr_offset;
int rec_type;
long msg_seg_len;
long data_offset;
long rcpt_count;
long qmgr_opts;
if (state->dst != 0) {
msg_warn("closing %s", cleanup_path);
vstream_fclose(state->dst);
state->dst = 0;
myfree(cleanup_path);
cleanup_path = 0;
}
if ((state->dst = vstream_fopen(path, O_RDWR, 0)) == 0) {
msg_warn("open %s: %m", path);
} else {
cleanup_path = mystrdup(path);
for (;;) {
if ((curr_offset = vstream_ftell(state->dst)) < 0)
msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) < 0)
msg_fatal("file %s: missing SIZE or PTR record", cleanup_path);
if (rec_type == REC_TYPE_SIZE) {
if (sscanf(STR(buf), "%ld %ld %ld %ld",
&msg_seg_len, &data_offset,
&rcpt_count, &qmgr_opts) != 4)
msg_fatal("file %s: bad SIZE record: %s",
cleanup_path, STR(buf));
state->data_offset = data_offset;
state->xtra_offset = data_offset + msg_seg_len;
} else if (rec_type == REC_TYPE_FROM) {
state->sender_pt_offset = curr_offset;
if (LEN(buf) < REC_TYPE_PTR_PAYL_SIZE
&& rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE) != REC_TYPE_PTR)
msg_fatal("file %s: missing PTR record after short sender",
cleanup_path);
if ((state->sender_pt_target = vstream_ftell(state->dst)) < 0)
msg_fatal("file %s: missing END record", cleanup_path);
} else if (rec_type == REC_TYPE_PTR) {
if (state->data_offset < 0)
msg_fatal("file %s: missing SIZE record", cleanup_path);
if (curr_offset < state->data_offset
|| curr_offset > state->xtra_offset) {
if (state->append_rcpt_pt_offset < 0) {
state->append_rcpt_pt_offset = curr_offset;
if (atol(STR(buf)) != 0)
msg_fatal("file %s: bad dummy recipient PTR record: %s",
cleanup_path, STR(buf));
if ((state->append_rcpt_pt_target =
vstream_ftell(state->dst)) < 0)
msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
} else if (curr_offset > state->xtra_offset
&& state->append_meta_pt_offset < 0) {
state->append_meta_pt_offset = curr_offset;
if (atol(STR(buf)) != 0)
msg_fatal("file %s: bad dummy meta PTR record: %s",
cleanup_path, STR(buf));
if ((state->append_meta_pt_target =
vstream_ftell(state->dst)) < 0)
msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
}
} else {
if (state->append_hdr_pt_offset < 0) {
state->append_hdr_pt_offset = curr_offset;
if (atol(STR(buf)) != 0)
msg_fatal("file %s: bad dummy header PTR record: %s",
cleanup_path, STR(buf));
if ((state->append_hdr_pt_target =
vstream_ftell(state->dst)) < 0)
msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
}
}
}
if (state->append_rcpt_pt_offset > 0
&& state->append_hdr_pt_offset > 0
&& (rec_type == REC_TYPE_END
|| state->append_meta_pt_offset > 0))
break;
}
if (msg_verbose) {
msg_info("append_rcpt_pt_offset %ld append_rcpt_pt_target %ld",
(long) state->append_rcpt_pt_offset,
(long) state->append_rcpt_pt_target);
msg_info("append_hdr_pt_offset %ld append_hdr_pt_target %ld",
(long) state->append_hdr_pt_offset,
(long) state->append_hdr_pt_target);
}
}
vstring_free(buf);
}
static void close_queue_file(CLEANUP_STATE *state)
{
(void) vstream_fclose(state->dst);
state->dst = 0;
myfree(cleanup_path);
cleanup_path = 0;
}
int main(int unused_argc, char **argv)
{
VSTRING *inbuf = vstring_alloc(100);
VSTRING *arg_buf = vstring_alloc(100);
char *bufp;
int istty = isatty(vstream_fileno(VSTREAM_IN));
CLEANUP_STATE *state = cleanup_state_alloc((VSTREAM *) 0);
state->queue_id = mystrdup("NOQUEUE");
state->sender = mystrdup("sender");
state->recip = mystrdup("recipient");
state->client_name = "client_name";
state->client_addr = "client_addr";
state->flags |= CLEANUP_FLAG_FILTER_ALL;
msg_vstream_init(argv[0], VSTREAM_ERR);
var_line_limit = DEF_LINE_LIMIT;
var_header_limit = DEF_HEADER_LIMIT;
for (;;) {
ARGV *argv;
ssize_t index;
const char *resp = 0;
if (istty) {
vstream_printf("- ");
vstream_fflush(VSTREAM_OUT);
}
if (vstring_fgets_nonl(inbuf, VSTREAM_IN) == 0)
break;
bufp = vstring_str(inbuf);
if (!istty) {
vstream_printf("> %s\n", bufp);
vstream_fflush(VSTREAM_OUT);
}
if (*bufp == '#' || *bufp == 0 || allspace(bufp))
continue;
argv = argv_split(bufp, " ");
if (argv->argc == 0) {
msg_warn("missing command");
} else if (strcmp(argv->argv[0], "?") == 0) {
usage();
} else if (strcmp(argv->argv[0], "verbose") == 0) {
if (argv->argc != 2) {
msg_warn("bad verbose argument count: %d", argv->argc);
} else if (strcmp(argv->argv[1], "on") == 0) {
msg_verbose = 2;
} else if (strcmp(argv->argv[1], "off") == 0) {
msg_verbose = 0;
} else {
msg_warn("bad verbose argument");
}
} else if (strcmp(argv->argv[0], "open") == 0) {
if (state->dst != 0) {
msg_info("closing %s", VSTREAM_PATH(state->dst));
close_queue_file(state);
}
if (argv->argc != 2) {
msg_warn("bad open argument count: %d", argv->argc);
} else {
open_queue_file(state, argv->argv[1]);
}
} else if (state->dst == 0) {
msg_warn("no open queue file");
} else if (strcmp(argv->argv[0], "close") == 0) {
if (*var_milt_head_checks) {
cleanup_milter_hbc_finish(state);
var_milt_head_checks = "";
}
close_queue_file(state);
} else if (state->milter_hbc_reply && LEN(state->milter_hbc_reply)) {
msg_info("ignoring: %s %s %s", argv->argv[0],
argv->argc > 1 ? argv->argv[1] : "",
argv->argc > 2 ? argv->argv[2] : "");
continue;
} else if (strcmp(argv->argv[0], "add_header") == 0) {
if (argv->argc < 2) {
msg_warn("bad add_header argument count: %d", argv->argc);
} else {
flatten_args(arg_buf, argv->argv + 2);
resp = cleanup_add_header(state, argv->argv[1], " ", STR(arg_buf));
}
} else if (strcmp(argv->argv[0], "ins_header") == 0) {
if (argv->argc < 3) {
msg_warn("bad ins_header argument count: %d", argv->argc);
} else if ((index = atoi(argv->argv[1])) < 1) {
msg_warn("bad ins_header index value");
} else {
flatten_args(arg_buf, argv->argv + 3);
resp = cleanup_ins_header(state, index, argv->argv[2], " ", STR(arg_buf));
}
} else if (strcmp(argv->argv[0], "upd_header") == 0) {
if (argv->argc < 3) {
msg_warn("bad upd_header argument count: %d", argv->argc);
} else if ((index = atoi(argv->argv[1])) < 1) {
msg_warn("bad upd_header index value");
} else {
flatten_args(arg_buf, argv->argv + 3);
resp = cleanup_upd_header(state, index, argv->argv[2], " ", STR(arg_buf));
}
} else if (strcmp(argv->argv[0], "del_header") == 0) {
if (argv->argc != 3) {
msg_warn("bad del_header argument count: %d", argv->argc);
} else if ((index = atoi(argv->argv[1])) < 1) {
msg_warn("bad del_header index value");
} else {
cleanup_del_header(state, index, argv->argv[2]);
}
} else if (strcmp(argv->argv[0], "chg_from") == 0) {
if (argv->argc != 3) {
msg_warn("bad chg_from argument count: %d", argv->argc);
} else {
cleanup_chg_from(state, argv->argv[1], argv->argv[2]);
}
} else if (strcmp(argv->argv[0], "add_rcpt") == 0) {
if (argv->argc != 2) {
msg_warn("bad add_rcpt argument count: %d", argv->argc);
} else {
cleanup_add_rcpt(state, argv->argv[1]);
}
} else if (strcmp(argv->argv[0], "add_rcpt_par") == 0) {
if (argv->argc != 3) {
msg_warn("bad add_rcpt_par argument count: %d", argv->argc);
} else {
cleanup_add_rcpt_par(state, argv->argv[1], argv->argv[2]);
}
} else if (strcmp(argv->argv[0], "del_rcpt") == 0) {
if (argv->argc != 2) {
msg_warn("bad del_rcpt argument count: %d", argv->argc);
} else {
cleanup_del_rcpt(state, argv->argv[1]);
}
} else if (strcmp(argv->argv[0], "replbody") == 0) {
if (argv->argc != 2) {
msg_warn("bad replbody argument count: %d", argv->argc);
} else {
VSTREAM *fp;
VSTRING *buf;
if ((fp = vstream_fopen(argv->argv[1], O_RDONLY, 0)) == 0) {
msg_warn("open %s file: %m", argv->argv[1]);
} else {
buf = vstring_alloc(100);
cleanup_repl_body(state, MILTER_BODY_START, buf);
while (vstring_get_nonl(buf, fp) != VSTREAM_EOF)
cleanup_repl_body(state, MILTER_BODY_LINE, buf);
cleanup_repl_body(state, MILTER_BODY_END, buf);
vstring_free(buf);
vstream_fclose(fp);
}
}
} else if (strcmp(argv->argv[0], "header_checks") == 0) {
if (argv->argc != 2) {
msg_warn("bad header_checks argument count: %d", argv->argc);
} else if (*var_milt_head_checks) {
msg_warn("can't change header checks");
} else {
var_milt_head_checks = mystrdup(argv->argv[1]);
cleanup_milter_header_checks_init(state);
}
} else {
msg_warn("bad command: %s", argv->argv[0]);
}
argv_free(argv);
if (resp)
cleanup_milter_apply(state, "END-OF-MESSAGE", resp);
}
vstring_free(inbuf);
vstring_free(arg_buf);
if (state->append_meta_pt_offset >= 0) {
if (state->flags)
msg_info("flags = %s", cleanup_strflags(state->flags));
if (state->errs)
msg_info("errs = %s", cleanup_strerror(state->errs));
}
cleanup_state_free(state);
return (0);
}
#endif