summaryrefslogtreecommitdiffstats
path: root/src/cleanup/cleanup_envelope.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 12:06:34 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 12:06:34 +0000
commit5e61585d76ae77fd5e9e96ebabb57afa4d74880d (patch)
tree2b467823aaeebc7ef8bc9e3cabe8074eaef1666d /src/cleanup/cleanup_envelope.c
parentInitial commit. (diff)
downloadpostfix-upstream/3.5.24.tar.xz
postfix-upstream/3.5.24.zip
Adding upstream version 3.5.24.upstream/3.5.24upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/cleanup/cleanup_envelope.c502
1 files changed, 502 insertions, 0 deletions
diff --git a/src/cleanup/cleanup_envelope.c b/src/cleanup/cleanup_envelope.c
new file mode 100644
index 0000000..a4b991d
--- /dev/null
+++ b/src/cleanup/cleanup_envelope.c
@@ -0,0 +1,502 @@
+/*++
+/* NAME
+/* cleanup_envelope 3
+/* SUMMARY
+/* process envelope segment
+/* SYNOPSIS
+/* #include <cleanup.h>
+/*
+/* void cleanup_envelope(state, type, buf, len)
+/* CLEANUP_STATE *state;
+/* int type;
+/* const char *buf;
+/* ssize_t len;
+/* DESCRIPTION
+/* This module processes envelope records and writes the result
+/* to the queue file. It validates the message structure, rewrites
+/* sender/recipient addresses to canonical form, and expands recipients
+/* according to entries in the virtual table. This routine absorbs but
+/* does not emit the envelope to content 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 <string.h>
+#include <stdlib.h>
+#include <stdio.h> /* ssscanf() */
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <nvtable.h>
+
+/* Global library. */
+
+#include <record.h>
+#include <rec_type.h>
+#include <cleanup_user.h>
+#include <qmgr_user.h>
+#include <mail_params.h>
+#include <verp_sender.h>
+#include <mail_proto.h>
+#include <dsn_mask.h>
+#include <rec_attr_map.h>
+#include <smtputf8.h>
+#include <deliver_request.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+#define STR vstring_str
+#define STREQ(x,y) (strcmp((x), (y)) == 0)
+
+static void cleanup_envelope_process(CLEANUP_STATE *, int, const char *, ssize_t);
+
+/* cleanup_envelope - initialize message envelope */
+
+void cleanup_envelope(CLEANUP_STATE *state, int type,
+ const char *str, ssize_t len)
+{
+
+ /*
+ * The message size and count record goes first, so it can easily be
+ * updated in place. This information takes precedence over any size
+ * estimate provided by the client. It's all in one record, data size
+ * first, for backwards compatibility reasons.
+ */
+ cleanup_out_format(state, REC_TYPE_SIZE, REC_TYPE_SIZE_FORMAT,
+ (REC_TYPE_SIZE_CAST1) 0, /* extra offs - content offs */
+ (REC_TYPE_SIZE_CAST2) 0, /* content offset */
+ (REC_TYPE_SIZE_CAST3) 0, /* recipient count */
+ (REC_TYPE_SIZE_CAST4) 0, /* qmgr options */
+ (REC_TYPE_SIZE_CAST5) 0, /* content length */
+ (REC_TYPE_SIZE_CAST6) 0); /* smtputf8 */
+
+ /*
+ * Pass control to the actual envelope processing routine.
+ */
+ state->action = cleanup_envelope_process;
+ cleanup_envelope_process(state, type, str, len);
+}
+
+/* cleanup_envelope_process - process one envelope record */
+
+static void cleanup_envelope_process(CLEANUP_STATE *state, int type,
+ const char *buf, ssize_t len)
+{
+ const char *myname = "cleanup_envelope_process";
+ char *attr_name;
+ char *attr_value;
+ const char *error_text;
+ int extra_opts;
+ int junk;
+ int mapped_type = type;
+ const char *mapped_buf = buf;
+ int milter_count;
+
+#ifdef DELAY_ACTION
+ int defer_delay;
+
+#endif
+
+ if (msg_verbose)
+ msg_info("initial envelope %c %.*s", type, (int) len, buf);
+
+ if (type == REC_TYPE_FLGS) {
+ /* Not part of queue file format. */
+ extra_opts = atoi(buf);
+ if (extra_opts & ~CLEANUP_FLAG_MASK_EXTRA)
+ msg_warn("%s: ignoring bad extra flags: 0x%x",
+ state->queue_id, extra_opts);
+ else
+ state->flags |= extra_opts;
+ return;
+ }
+#ifdef DELAY_ACTION
+ if (type == REC_TYPE_DELAY) {
+ /* Not part of queue file format. */
+ defer_delay = atoi(buf);
+ if (defer_delay <= 0)
+ msg_warn("%s: ignoring bad delay time: %s", state->queue_id, buf);
+ else
+ state->defer_delay = defer_delay;
+ return;
+ }
+#endif
+
+ /*
+ * XXX We instantiate a MILTERS structure even when the filter count is
+ * zero (for example, all filters are in ACCEPT state, or the SMTP server
+ * sends a dummy MILTERS structure without any filters), otherwise the
+ * cleanup server would apply the non_smtpd_milters setting
+ * inappropriately.
+ */
+ if (type == REC_TYPE_MILT_COUNT) {
+ /* Not part of queue file format. */
+ if ((milter_count = atoi(buf)) >= 0)
+ cleanup_milter_receive(state, milter_count);
+ return;
+ }
+
+ /*
+ * Map DSN attribute name to pseudo record type so that we don't have to
+ * pollute the queue file with records that are incompatible with past
+ * Postfix versions. Preferably, people should be able to back out from
+ * an upgrade without losing mail.
+ */
+ if (type == REC_TYPE_ATTR) {
+ vstring_strcpy(state->attr_buf, buf);
+ error_text = split_nameval(STR(state->attr_buf), &attr_name, &attr_value);
+ if (error_text != 0) {
+ msg_warn("%s: message rejected: malformed attribute: %s: %.100s",
+ state->queue_id, error_text, buf);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ /* Zero-length values are place holders for unavailable values. */
+ if (*attr_value == 0) {
+ msg_warn("%s: spurious null attribute value for \"%s\" -- ignored",
+ state->queue_id, attr_name);
+ return;
+ }
+ if ((junk = rec_attr_map(attr_name)) != 0) {
+ mapped_buf = attr_value;
+ mapped_type = junk;
+ }
+ }
+
+ /*
+ * Sanity check.
+ */
+ if (strchr(REC_TYPE_ENVELOPE, type) == 0) {
+ msg_warn("%s: message rejected: unexpected record type %d in envelope",
+ state->queue_id, type);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+
+ /*
+ * Although recipient records appear at the end of the initial or
+ * extracted envelope, the code for processing recipient records is first
+ * because there can be lots of them.
+ *
+ * Recipient records may be mixed with other information (such as FILTER or
+ * REDIRECT actions from SMTPD). In that case the queue manager needs to
+ * examine all queue file records before it can start delivery. This is
+ * not a problem when SMTPD recipient lists are small.
+ *
+ * However, if recipient records are not mixed with other records
+ * (typically, mailing list mail) then we can make an optimization: the
+ * queue manager does not need to examine every envelope record before it
+ * can start deliveries. This can help with very large mailing lists.
+ */
+
+ /*
+ * On the transition from non-recipient records to recipient records,
+ * emit some records and do some sanity checks.
+ *
+ * XXX Moving the envelope sender (and the test for its presence) to the
+ * extracted segment can reduce qmqpd memory requirements because it no
+ * longer needs to read the entire message into main memory.
+ */
+ if ((state->flags & CLEANUP_FLAG_INRCPT) == 0
+ && strchr(REC_TYPE_ENV_RECIPIENT, type) != 0) {
+ if (state->sender == 0) {
+ msg_warn("%s: message rejected: missing sender envelope record",
+ state->queue_id);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ if (state->arrival_time.tv_sec == 0) {
+ msg_warn("%s: message rejected: missing time envelope record",
+ state->queue_id);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+
+ /*
+ * XXX This works by accident, because the sender is recorded at the
+ * beginning of the envelope segment.
+ */
+ if ((state->flags & CLEANUP_FLAG_WARN_SEEN) == 0
+ && state->sender && *state->sender
+ && var_delay_warn_time > 0) {
+ cleanup_out_format(state, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT,
+ REC_TYPE_WARN_ARG(state->arrival_time.tv_sec
+ + var_delay_warn_time));
+ }
+ state->flags |= CLEANUP_FLAG_INRCPT;
+ }
+
+ /*
+ * Initial envelope recipient record processing.
+ */
+ if (type == REC_TYPE_RCPT) {
+ if (state->sender == 0) { /* protect showq */
+ msg_warn("%s: message rejected: envelope recipient precedes sender",
+ state->queue_id);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ if (state->orig_rcpt == 0)
+ state->orig_rcpt = mystrdup(buf);
+ cleanup_addr_recipient(state, buf);
+ if (cleanup_milters != 0
+ && state->milters == 0
+ && CLEANUP_MILTER_OK(state))
+ cleanup_milter_emul_rcpt(state, cleanup_milters, state->recip);
+ myfree(state->orig_rcpt);
+ state->orig_rcpt = 0;
+ if (state->dsn_orcpt != 0) {
+ myfree(state->dsn_orcpt);
+ state->dsn_orcpt = 0;
+ }
+ state->dsn_notify = 0;
+ return;
+ }
+ if (type == REC_TYPE_DONE || type == REC_TYPE_DRCP) {
+ if (state->orig_rcpt != 0) {
+ myfree(state->orig_rcpt);
+ state->orig_rcpt = 0;
+ }
+ if (state->dsn_orcpt != 0) {
+ myfree(state->dsn_orcpt);
+ state->dsn_orcpt = 0;
+ }
+ state->dsn_notify = 0;
+ return;
+ }
+ if (mapped_type == REC_TYPE_DSN_ORCPT) {
+ if (state->dsn_orcpt) {
+ msg_warn("%s: ignoring out-of-order DSN original recipient record <%.200s>",
+ state->queue_id, state->dsn_orcpt);
+ myfree(state->dsn_orcpt);
+ }
+ state->dsn_orcpt = mystrdup(mapped_buf);
+ return;
+ }
+ if (mapped_type == REC_TYPE_DSN_NOTIFY) {
+ if (state->dsn_notify) {
+ msg_warn("%s: ignoring out-of-order DSN notify record <%d>",
+ state->queue_id, state->dsn_notify);
+ state->dsn_notify = 0;
+ }
+ if (!alldig(mapped_buf) || (junk = atoi(mapped_buf)) == 0
+ || DSN_NOTIFY_OK(junk) == 0)
+ msg_warn("%s: ignoring malformed DSN notify record <%.200s>",
+ state->queue_id, buf);
+ else
+ state->qmgr_opts |=
+ QMGR_READ_FLAG_FROM_DSN(state->dsn_notify = junk);
+ return;
+ }
+ if (type == REC_TYPE_ORCP) {
+ if (state->orig_rcpt != 0) {
+ msg_warn("%s: ignoring out-of-order original recipient record <%.200s>",
+ state->queue_id, state->orig_rcpt);
+ myfree(state->orig_rcpt);
+ }
+ state->orig_rcpt = mystrdup(buf);
+ return;
+ }
+ if (type == REC_TYPE_MESG) {
+ state->action = cleanup_message;
+ if (state->flags & CLEANUP_FLAG_INRCPT) {
+ if (state->milters || cleanup_milters) {
+ /* Make room to append recipient. */
+ if ((state->append_rcpt_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_rcpt_pt_target = vstream_ftell(state->dst)) < 0)
+ msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
+ }
+ state->flags &= ~CLEANUP_FLAG_INRCPT;
+ }
+ return;
+ }
+
+ /*
+ * Initial envelope non-recipient record processing.
+ *
+ * If the message was requeued with "postsuper -r" use their
+ * SMTPUTF8_REQUESTED flag.
+ */
+ if (state->flags & CLEANUP_FLAG_INRCPT)
+ /* Tell qmgr that recipient records are mixed with other information. */
+ state->qmgr_opts |= QMGR_READ_FLAG_MIXED_RCPT_OTHER;
+ if (type == REC_TYPE_SIZE) {
+ /* Use our own SIZE record, except for the SMTPUTF8_REQUESTED flag. */
+ (void) sscanf(buf, "%*s $*s %*s %*s %*s %d", &state->smtputf8);
+ state->smtputf8 &= SMTPUTF8_FLAG_REQUESTED;
+ return;
+ }
+ if (mapped_type == REC_TYPE_CTIME)
+ /* Use our own expiration time base record instead. */
+ return;
+ if (type == REC_TYPE_TIME) {
+ /* First instance wins. */
+ if (state->arrival_time.tv_sec == 0) {
+ REC_TYPE_TIME_SCAN(buf, state->arrival_time);
+ cleanup_out(state, type, buf, len);
+ }
+ /* Generate our own expiration time base record. */
+ cleanup_out_format(state, REC_TYPE_ATTR, "%s=%ld",
+ MAIL_ATTR_CREATE_TIME, (long) time((time_t *) 0));
+ return;
+ }
+ if (type == REC_TYPE_FULL) {
+ /* First instance wins. */
+ if (state->fullname == 0) {
+ state->fullname = mystrdup(buf);
+ cleanup_out(state, type, buf, len);
+ }
+ return;
+ }
+ if (type == REC_TYPE_FROM) {
+ off_t after_sender_offs;
+
+ /* Allow only one instance. */
+ if (state->sender != 0) {
+ msg_warn("%s: message rejected: multiple envelope sender records",
+ state->queue_id);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ if (state->milters || cleanup_milters) {
+ /* Remember the sender record offset. */
+ if ((state->sender_pt_offset = vstream_ftell(state->dst)) < 0)
+ msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
+ }
+ after_sender_offs = cleanup_addr_sender(state, buf);
+ if (state->milters || cleanup_milters) {
+ /* Remember the after-sender record offset. */
+ state->sender_pt_target = after_sender_offs;
+ }
+ if (cleanup_milters != 0
+ && state->milters == 0
+ && CLEANUP_MILTER_OK(state))
+ cleanup_milter_emul_mail(state, cleanup_milters, state->sender);
+ return;
+ }
+ if (mapped_type == REC_TYPE_DSN_ENVID) {
+ /* Don't break "postsuper -r" after Milter overrides ENVID. */
+ if (!allprint(mapped_buf)) {
+ msg_warn("%s: message rejected: bad DSN envelope ID record",
+ state->queue_id);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ if (state->dsn_envid != 0)
+ myfree(state->dsn_envid);
+ state->dsn_envid = mystrdup(mapped_buf);
+ cleanup_out(state, type, buf, len);
+ return;
+ }
+ if (mapped_type == REC_TYPE_DSN_RET) {
+ /* Don't break "postsuper -r" after Milter overrides RET. */
+ if (!alldig(mapped_buf) || (junk = atoi(mapped_buf)) == 0
+ || DSN_RET_OK(junk) == 0) {
+ msg_warn("%s: message rejected: bad DSN RET record <%.200s>",
+ state->queue_id, buf);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ state->dsn_ret = junk;
+ cleanup_out(state, type, buf, len);
+ return;
+ }
+ if (type == REC_TYPE_WARN) {
+ /* First instance wins. */
+ if ((state->flags & CLEANUP_FLAG_WARN_SEEN) == 0) {
+ state->flags |= CLEANUP_FLAG_WARN_SEEN;
+ cleanup_out(state, type, buf, len);
+ }
+ return;
+ }
+ /* XXX Needed for cleanup_bounce(); sanity check usage. */
+ if (type == REC_TYPE_VERP) {
+ if (state->verp_delims == 0) {
+ if (state->sender == 0 || state->sender[0] == 0) {
+ msg_warn("%s: ignoring VERP request for null sender",
+ state->queue_id);
+ } else if (verp_delims_verify(buf) != 0) {
+ msg_warn("%s: ignoring bad VERP request: \"%.100s\"",
+ state->queue_id, buf);
+ } else {
+ state->verp_delims = mystrdup(buf);
+ cleanup_out(state, type, buf, len);
+ }
+ }
+ return;
+ }
+ if (type == REC_TYPE_ATTR) {
+ if (state->attr->used >= var_qattr_count_limit) {
+ msg_warn("%s: message rejected: attribute count exceeds limit %d",
+ state->queue_id, var_qattr_count_limit);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ if (strcmp(attr_name, MAIL_ATTR_RWR_CONTEXT) == 0) {
+ /* Choose header rewriting context. See also cleanup_addr.c. */
+ if (STREQ(attr_value, MAIL_ATTR_RWR_LOCAL)) {
+ state->hdr_rewrite_context = MAIL_ATTR_RWR_LOCAL;
+ } else if (STREQ(attr_value, MAIL_ATTR_RWR_REMOTE)) {
+ state->hdr_rewrite_context =
+ (*var_remote_rwr_domain ? MAIL_ATTR_RWR_REMOTE : 0);
+ } else {
+ msg_warn("%s: message rejected: bad rewriting context: %.100s",
+ state->queue_id, attr_value);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ }
+ if (strcmp(attr_name, MAIL_ATTR_TRACE_FLAGS) == 0) {
+ if (!alldig(attr_value)) {
+ msg_warn("%s: message rejected: bad TFLAG record <%.200s>",
+ state->queue_id, buf);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ if (state->tflags == 0)
+ state->tflags = DEL_REQ_TRACE_FLAGS(atoi(attr_value));
+ }
+ nvtable_update(state->attr, attr_name, attr_value);
+ cleanup_out(state, type, buf, len);
+ return;
+ } else {
+ cleanup_out(state, type, buf, len);
+ return;
+ }
+}