summaryrefslogtreecommitdiffstats
path: root/src/local/forward.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/local/forward.c')
-rw-r--r--src/local/forward.c393
1 files changed, 393 insertions, 0 deletions
diff --git a/src/local/forward.c b/src/local/forward.c
new file mode 100644
index 0000000..722dcf7
--- /dev/null
+++ b/src/local/forward.c
@@ -0,0 +1,393 @@
+/*++
+/* NAME
+/* forward 3
+/* SUMMARY
+/* message forwarding
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int forward_init()
+/*
+/* int forward_append(attr)
+/* DELIVER_ATTR attr;
+/*
+/* int forward_finish(request, attr, cancel)
+/* DELIVER_REQUEST *request;
+/* DELIVER_ATTR attr;
+/* int cancel;
+/* DESCRIPTION
+/* This module implements the client interface for message
+/* forwarding.
+/*
+/* forward_init() initializes internal data structures.
+/*
+/* forward_append() appends a recipient to the list of recipients
+/* that will receive a message with the specified message sender
+/* and delivered-to addresses.
+/*
+/* forward_finish() forwards the actual message contents and
+/* releases the memory allocated by forward_init() and by
+/* forward_append(). When the \fIcancel\fR argument is true, no
+/* messages will be forwarded. The \fIattr\fR argument specifies
+/* the original message delivery attributes as they were before
+/* alias or forward expansions.
+/* DIAGNOSTICS
+/* A non-zero result means that the requested operation should
+/* be tried again.
+/* Warnings: problems connecting to the forwarding service,
+/* corrupt message file. A corrupt message is saved to the
+/* "corrupt" queue for further inspection.
+/* Fatal: out of memory.
+/* Panic: missing forward_init() or forward_finish() call.
+/* 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 <sys/time.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <argv.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <iostuff.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <cleanup_user.h>
+#include <sent.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mark_corrupt.h>
+#include <mail_date.h>
+#include <mail_params.h>
+#include <dsn_mask.h>
+#include <smtputf8.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+ /*
+ * Use one cleanup service connection for each (delivered to, sender) pair.
+ */
+static HTABLE *forward_dt;
+
+typedef struct FORWARD_INFO {
+ VSTREAM *cleanup; /* clean up service handle */
+ char *queue_id; /* forwarded message queue id */
+ struct timeval posting_time; /* posting time */
+} FORWARD_INFO;
+
+/* forward_init - prepare for forwarding */
+
+int forward_init(void)
+{
+
+ /*
+ * Sanity checks.
+ */
+ if (forward_dt != 0)
+ msg_panic("forward_init: missing forward_finish call");
+
+ forward_dt = htable_create(0);
+ return (0);
+}
+
+/* forward_open - open connection to cleanup service */
+
+static FORWARD_INFO *forward_open(DELIVER_REQUEST *request, const char *sender)
+{
+ VSTRING *buffer = vstring_alloc(100);
+ FORWARD_INFO *info;
+ VSTREAM *cleanup;
+
+#define FORWARD_OPEN_RETURN(res) do { \
+ vstring_free(buffer); \
+ return (res); \
+ } while (0)
+
+ /*
+ * Contact the cleanup service and save the new mail queue id. Request
+ * that the cleanup service bounces bad messages to the sender so that we
+ * can avoid the trouble of bounce management.
+ *
+ * In case you wonder what kind of bounces, examples are "too many hops",
+ * "message too large", perhaps some others. The reason not to bounce
+ * ourselves is that we don't really know who the recipients are.
+ */
+ cleanup = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service, BLOCKING);
+ if (cleanup == 0) {
+ msg_warn("connect to %s/%s: %m",
+ MAIL_CLASS_PUBLIC, var_cleanup_service);
+ FORWARD_OPEN_RETURN(0);
+ }
+ close_on_exec(vstream_fileno(cleanup), CLOSE_ON_EXEC);
+ if (attr_scan(cleanup, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_CLEANUP),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, buffer),
+ ATTR_TYPE_END) != 1) {
+ vstream_fclose(cleanup);
+ FORWARD_OPEN_RETURN(0);
+ }
+ info = (FORWARD_INFO *) mymalloc(sizeof(FORWARD_INFO));
+ info->cleanup = cleanup;
+ info->queue_id = mystrdup(STR(buffer));
+ GETTIMEOFDAY(&info->posting_time);
+
+#define FORWARD_CLEANUP_FLAGS \
+ (CLEANUP_FLAG_BOUNCE | CLEANUP_FLAG_MASK_INTERNAL \
+ | smtputf8_autodetect(MAIL_SRC_MASK_FORWARD) \
+ | ((request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) ? \
+ CLEANUP_FLAG_SMTPUTF8 : 0))
+
+ attr_print(cleanup, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, FORWARD_CLEANUP_FLAGS),
+ ATTR_TYPE_END);
+
+ /*
+ * Send initial message envelope information. For bounces, set the
+ * designated sender: mailing list owner, posting user, whatever.
+ */
+ rec_fprintf(cleanup, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT,
+ REC_TYPE_TIME_ARG(info->posting_time));
+ rec_fputs(cleanup, REC_TYPE_FROM, sender);
+
+ /*
+ * Don't send the original envelope ID or full/headers return mask if it
+ * was reset due to mailing list expansion.
+ */
+ if (request->dsn_ret)
+ rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_RET, request->dsn_ret);
+ if (request->dsn_envid && *(request->dsn_envid))
+ rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_DSN_ENVID, request->dsn_envid);
+
+ /*
+ * Zero-length attribute values are place holders for unavailable
+ * attribute values. See qmgr_message.c. They are not meant to be
+ * propagated to queue files.
+ */
+#define PASS_ATTR(fp, name, value) do { \
+ if ((value) && *(value)) \
+ rec_fprintf((fp), REC_TYPE_ATTR, "%s=%s", (name), (value)); \
+ } while (0)
+
+ /*
+ * XXX encapsulate these as one object.
+ */
+ PASS_ATTR(cleanup, MAIL_ATTR_LOG_CLIENT_NAME, request->client_name);
+ PASS_ATTR(cleanup, MAIL_ATTR_LOG_CLIENT_ADDR, request->client_addr);
+ PASS_ATTR(cleanup, MAIL_ATTR_LOG_PROTO_NAME, request->client_proto);
+ PASS_ATTR(cleanup, MAIL_ATTR_LOG_HELO_NAME, request->client_helo);
+ PASS_ATTR(cleanup, MAIL_ATTR_SASL_METHOD, request->sasl_method);
+ PASS_ATTR(cleanup, MAIL_ATTR_SASL_USERNAME, request->sasl_username);
+ PASS_ATTR(cleanup, MAIL_ATTR_SASL_SENDER, request->sasl_sender);
+ PASS_ATTR(cleanup, MAIL_ATTR_LOG_IDENT, request->log_ident);
+ PASS_ATTR(cleanup, MAIL_ATTR_RWR_CONTEXT, request->rewrite_context);
+
+ FORWARD_OPEN_RETURN(info);
+}
+
+/* forward_append - append recipient to message envelope */
+
+int forward_append(DELIVER_ATTR attr)
+{
+ FORWARD_INFO *info;
+ HTABLE *table_snd;
+
+ /*
+ * Sanity checks.
+ */
+ if (msg_verbose)
+ msg_info("forward delivered=%s sender=%s recip=%s",
+ attr.delivered, attr.sender, attr.rcpt.address);
+ if (forward_dt == 0)
+ msg_panic("forward_append: missing forward_init call");
+
+ /*
+ * In order to find the recipient list, first index a table by
+ * delivered-to header address, then by envelope sender address.
+ */
+ if ((table_snd = (HTABLE *) htable_find(forward_dt, attr.delivered)) == 0) {
+ table_snd = htable_create(0);
+ htable_enter(forward_dt, attr.delivered, (void *) table_snd);
+ }
+ if ((info = (FORWARD_INFO *) htable_find(table_snd, attr.sender)) == 0) {
+ if ((info = forward_open(attr.request, attr.sender)) == 0)
+ return (-1);
+ htable_enter(table_snd, attr.sender, (void *) info);
+ }
+
+ /*
+ * Append the recipient to the message envelope. Don't send the original
+ * recipient or notification mask if it was reset due to mailing list
+ * expansion.
+ */
+ if (*attr.rcpt.dsn_orcpt)
+ rec_fprintf(info->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_DSN_ORCPT, attr.rcpt.dsn_orcpt);
+ if (attr.rcpt.dsn_notify)
+ rec_fprintf(info->cleanup, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_NOTIFY, attr.rcpt.dsn_notify);
+ if (*attr.rcpt.orig_addr)
+ rec_fputs(info->cleanup, REC_TYPE_ORCP, attr.rcpt.orig_addr);
+ rec_fputs(info->cleanup, REC_TYPE_RCPT, attr.rcpt.address);
+
+ return (vstream_ferror(info->cleanup));
+}
+
+/* forward_send - send forwarded message */
+
+static int forward_send(FORWARD_INFO *info, DELIVER_REQUEST *request,
+ DELIVER_ATTR attr, char *delivered)
+{
+ const char *myname = "forward_send";
+ VSTRING *buffer = vstring_alloc(100);
+ VSTRING *folded;
+ int status;
+ int rec_type = 0;
+
+ /*
+ * Start the message content segment. Prepend our Delivered-To: header to
+ * the message data. Stop at the first error. XXX Rely on the front-end
+ * services to enforce record size limits.
+ */
+ rec_fputs(info->cleanup, REC_TYPE_MESG, "");
+ vstring_strcpy(buffer, delivered);
+ rec_fprintf(info->cleanup, REC_TYPE_NORM, "Received: by %s (%s)",
+ var_myhostname, var_mail_name);
+ rec_fprintf(info->cleanup, REC_TYPE_NORM, "\tid %s; %s",
+ info->queue_id, mail_date(info->posting_time.tv_sec));
+ if (local_deliver_hdr_mask & DELIVER_HDR_FWD) {
+ folded = vstring_alloc(100);
+ rec_fprintf(info->cleanup, REC_TYPE_NORM, "Delivered-To: %s",
+ casefold(folded, (STR(buffer))));
+ vstring_free(folded);
+ }
+ if ((status = vstream_ferror(info->cleanup)) == 0)
+ if (vstream_fseek(attr.fp, attr.offset, SEEK_SET) < 0)
+ msg_fatal("%s: seek queue file %s: %m:",
+ myname, VSTREAM_PATH(attr.fp));
+ while (status == 0 && (rec_type = rec_get(attr.fp, buffer, 0)) > 0) {
+ if (rec_type != REC_TYPE_CONT && rec_type != REC_TYPE_NORM)
+ break;
+ status = (REC_PUT_BUF(info->cleanup, rec_type, buffer) != rec_type);
+ }
+ if (status == 0 && rec_type != REC_TYPE_XTRA) {
+ msg_warn("%s: bad record type: %d in message content",
+ info->queue_id, rec_type);
+ status |= mark_corrupt(attr.fp);
+ }
+
+ /*
+ * Send the end-of-data marker only when there were no errors.
+ */
+ if (status == 0) {
+ rec_fputs(info->cleanup, REC_TYPE_XTRA, "");
+ rec_fputs(info->cleanup, REC_TYPE_END, "");
+ }
+
+ /*
+ * Retrieve the cleanup service completion status only if there are no
+ * problems.
+ */
+ if (status == 0)
+ if (vstream_fflush(info->cleanup)
+ || attr_scan(info->cleanup, ATTR_FLAG_MISSING,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1)
+ status = 1;
+
+ /*
+ * Log successful forwarding.
+ *
+ * XXX DSN alias and .forward expansion already report SUCCESS, so don't do
+ * it again here.
+ */
+ if (status == 0) {
+ attr.rcpt.dsn_notify =
+ (attr.rcpt.dsn_notify == DSN_NOTIFY_SUCCESS ?
+ DSN_NOTIFY_NEVER : attr.rcpt.dsn_notify & ~DSN_NOTIFY_SUCCESS);
+ dsb_update(attr.why, "2.0.0", "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY,
+ "forwarded as %s", info->queue_id);
+ status = sent(BOUNCE_FLAGS(request), SENT_ATTR(attr));
+ }
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(buffer);
+ return (status);
+}
+
+/* forward_finish - complete message forwarding requests and clean up */
+
+int forward_finish(DELIVER_REQUEST *request, DELIVER_ATTR attr, int cancel)
+{
+ HTABLE_INFO **dt_list;
+ HTABLE_INFO **dt;
+ HTABLE_INFO **sn_list;
+ HTABLE_INFO **sn;
+ HTABLE *table_snd;
+ char *delivered;
+ char *sender;
+ FORWARD_INFO *info;
+ int status = cancel;
+
+ /*
+ * Sanity checks.
+ */
+ if (forward_dt == 0)
+ msg_panic("forward_finish: missing forward_init call");
+
+ /*
+ * Walk over all delivered-to header addresses and over each envelope
+ * sender address.
+ */
+ for (dt = dt_list = htable_list(forward_dt); *dt; dt++) {
+ delivered = dt[0]->key;
+ table_snd = (HTABLE *) dt[0]->value;
+ for (sn = sn_list = htable_list(table_snd); *sn; sn++) {
+ sender = sn[0]->key;
+ info = (FORWARD_INFO *) sn[0]->value;
+ if (status == 0)
+ status |= forward_send(info, request, attr, delivered);
+ if (msg_verbose)
+ msg_info("forward_finish: delivered %s sender %s status %d",
+ delivered, sender, status);
+ (void) vstream_fclose(info->cleanup);
+ myfree(info->queue_id);
+ myfree((void *) info);
+ }
+ myfree((void *) sn_list);
+ htable_free(table_snd, (void (*) (void *)) 0);
+ }
+ myfree((void *) dt_list);
+ htable_free(forward_dt, (void (*) (void *)) 0);
+ forward_dt = 0;
+ return (status);
+}