summaryrefslogtreecommitdiffstats
path: root/src/global/deliver_request.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/global/deliver_request.c')
-rw-r--r--src/global/deliver_request.c484
1 files changed, 484 insertions, 0 deletions
diff --git a/src/global/deliver_request.c b/src/global/deliver_request.c
new file mode 100644
index 0000000..7bc5553
--- /dev/null
+++ b/src/global/deliver_request.c
@@ -0,0 +1,484 @@
+/*++
+/* NAME
+/* deliver_request 3
+/* SUMMARY
+/* mail delivery request protocol, server side
+/* SYNOPSIS
+/* #include <deliver_request.h>
+/*
+/* typedef struct DELIVER_REQUEST {
+/* .in +5
+/* VSTREAM *fp;
+/* int flags;
+/* char *queue_name;
+/* char *queue_id;
+/* long data_offset;
+/* long data_size;
+/* char *nexthop;
+/* char *encoding;
+/* char *sender;
+/* MSG_STATS msg_stats;
+/* RECIPIENT_LIST rcpt_list;
+/* DSN *hop_status;
+/* char *client_name;
+/* char *client_addr;
+/* char *client_port;
+/* char *client_proto;
+/* char *client_helo;
+/* char *sasl_method;
+/* char *sasl_username;
+/* char *sasl_sender;
+/* char *log_ident;
+/* char *rewrite_context;
+/* char *dsn_envid;
+/* int dsn_ret;
+/* .in -5
+/* } DELIVER_REQUEST;
+/*
+/* DELIVER_REQUEST *deliver_request_read(stream)
+/* VSTREAM *stream;
+/*
+/* void deliver_request_done(stream, request, status)
+/* VSTREAM *stream;
+/* DELIVER_REQUEST *request;
+/* int status;
+/* DESCRIPTION
+/* This module implements the delivery agent side of the `queue manager
+/* to delivery agent' protocol. In this game, the queue manager is
+/* the client, while the delivery agent is the server.
+/*
+/* deliver_request_read() reads a client message delivery request,
+/* opens the queue file, and acquires a shared lock.
+/* A null result means that the client sent bad information or that
+/* it went away unexpectedly.
+/*
+/* The \fBflags\fR structure member is the bit-wise OR of zero or more
+/* of the following:
+/* .IP \fBDEL_REQ_FLAG_SUCCESS\fR
+/* Delete successful recipients from the queue file.
+/*
+/* Note: currently, this also controls whether bounced recipients
+/* are deleted.
+/*
+/* Note: the deliver_completed() function ignores this request
+/* when the recipient queue file offset is -1.
+/* .IP \fBDEL_REQ_FLAG_BOUNCE\fR
+/* Delete bounced recipients from the queue file. Currently,
+/* this flag is non-functional.
+/* .PP
+/* The \fBDEL_REQ_FLAG_DEFLT\fR constant provides a convenient shorthand
+/* for the most common case: delete successful and bounced recipients.
+/*
+/* The \fIhop_status\fR member must be updated by the caller
+/* when all delivery to the destination in \fInexthop\fR should
+/* be deferred. This member is passed to dsn_free().
+/*
+/* deliver_request_done() reports the delivery status back to the
+/* client, including the optional \fIhop_status\fR etc. information,
+/* closes the queue file,
+/* and destroys the DELIVER_REQUEST structure. The result is
+/* non-zero when the status could not be reported to the client.
+/* DIAGNOSTICS
+/* Warnings: bad data sent by the client. Fatal errors: out of
+/* memory, queue file open errors.
+/* SEE ALSO
+/* attr_scan(3) low-level intra-mail input routines
+/* 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/stat.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <myflock.h>
+
+/* Global library. */
+
+#include "mail_queue.h"
+#include "mail_proto.h"
+#include "mail_open_ok.h"
+#include "recipient_list.h"
+#include "dsn.h"
+#include "dsn_print.h"
+#include "deliver_request.h"
+#include "rcpt_buf.h"
+
+/* deliver_request_initial - send initial status code */
+
+static int deliver_request_initial(VSTREAM *stream)
+{
+ int err;
+
+ /*
+ * The master processes runs a finite number of delivery agent processes
+ * to handle service requests. Thus, a delivery agent process must send
+ * something to inform the queue manager that it is ready to receive a
+ * delivery request; otherwise the queue manager could block in write().
+ */
+ if (msg_verbose)
+ msg_info("deliver_request_initial: send initial response");
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_DELIVER),
+ ATTR_TYPE_END);
+ if ((err = vstream_fflush(stream)) != 0)
+ if (msg_verbose)
+ msg_warn("send initial response: %m");
+ return (err);
+}
+
+/* deliver_request_final - send final delivery request status */
+
+static int deliver_request_final(VSTREAM *stream, DELIVER_REQUEST *request,
+ int status)
+{
+ DSN *hop_status;
+ int err;
+
+ /* XXX This DSN structure initialization bypasses integrity checks. */
+ static DSN dummy_dsn = {"", "", "", "", "", "", ""};
+
+ /*
+ * Send the status and the optional reason.
+ */
+ if ((hop_status = request->hop_status) == 0)
+ hop_status = &dummy_dsn;
+ if (msg_verbose)
+ msg_info("deliver_request_final: send: \"%s\" %d",
+ hop_status->reason, status);
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(dsn_print, (const void *) hop_status),
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ ATTR_TYPE_END);
+ if ((err = vstream_fflush(stream)) != 0)
+ if (msg_verbose)
+ msg_warn("send final status: %m");
+
+ /*
+ * With some UNIX systems, stream sockets lose data when you close them
+ * immediately after writing to them. That is not how sockets are
+ * supposed to behave! The workaround is to wait until the receiver
+ * closes the connection. Calling VSTREAM_GETC() has the benefit of using
+ * whatever timeout is specified in the ipc_timeout parameter.
+ */
+ (void) VSTREAM_GETC(stream);
+ return (err);
+}
+
+/* deliver_request_get - receive message delivery request */
+
+static int deliver_request_get(VSTREAM *stream, DELIVER_REQUEST *request)
+{
+ const char *myname = "deliver_request_get";
+ const char *path;
+ struct stat st;
+ static VSTRING *queue_name;
+ static VSTRING *queue_id;
+ static VSTRING *nexthop;
+ static VSTRING *encoding;
+ static VSTRING *address;
+ static VSTRING *client_name;
+ static VSTRING *client_addr;
+ static VSTRING *client_port;
+ static VSTRING *client_proto;
+ static VSTRING *client_helo;
+ static VSTRING *sasl_method;
+ static VSTRING *sasl_username;
+ static VSTRING *sasl_sender;
+ static VSTRING *log_ident;
+ static VSTRING *rewrite_context;
+ static VSTRING *dsn_envid;
+ static RCPT_BUF *rcpt_buf;
+ int rcpt_count;
+ int smtputf8;
+ int dsn_ret;
+
+ /*
+ * Initialize. For some reason I wanted to allow for multiple instances
+ * of a deliver_request structure, thus the hoopla with string
+ * initialization and copying.
+ */
+ if (queue_name == 0) {
+ queue_name = vstring_alloc(10);
+ queue_id = vstring_alloc(10);
+ nexthop = vstring_alloc(10);
+ encoding = vstring_alloc(10);
+ address = vstring_alloc(10);
+ client_name = vstring_alloc(10);
+ client_addr = vstring_alloc(10);
+ client_port = vstring_alloc(10);
+ client_proto = vstring_alloc(10);
+ client_helo = vstring_alloc(10);
+ sasl_method = vstring_alloc(10);
+ sasl_username = vstring_alloc(10);
+ sasl_sender = vstring_alloc(10);
+ log_ident = vstring_alloc(10);
+ rewrite_context = vstring_alloc(10);
+ dsn_envid = vstring_alloc(10);
+ rcpt_buf = rcpb_create();
+ }
+
+ /*
+ * Extract the queue file name, data offset, and sender address. Abort
+ * the conversation when they send bad information.
+ */
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request->flags),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ RECV_ATTR_LONG(MAIL_ATTR_OFFSET, &request->data_offset),
+ RECV_ATTR_LONG(MAIL_ATTR_SIZE, &request->data_size),
+ RECV_ATTR_STR(MAIL_ATTR_NEXTHOP, nexthop),
+ RECV_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
+ RECV_ATTR_INT(MAIL_ATTR_SMTPUTF8, &smtputf8),
+ RECV_ATTR_STR(MAIL_ATTR_SENDER, address),
+ RECV_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
+ RECV_ATTR_INT(MAIL_ATTR_DSN_RET, &dsn_ret),
+ RECV_ATTR_FUNC(msg_stats_scan, (void *) &request->msg_stats),
+ /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */
+ RECV_ATTR_STR(MAIL_ATTR_LOG_CLIENT_NAME, client_name),
+ RECV_ATTR_STR(MAIL_ATTR_LOG_CLIENT_ADDR, client_addr),
+ RECV_ATTR_STR(MAIL_ATTR_LOG_CLIENT_PORT, client_port),
+ RECV_ATTR_STR(MAIL_ATTR_LOG_PROTO_NAME, client_proto),
+ RECV_ATTR_STR(MAIL_ATTR_LOG_HELO_NAME, client_helo),
+ /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */
+ RECV_ATTR_STR(MAIL_ATTR_SASL_METHOD, sasl_method),
+ RECV_ATTR_STR(MAIL_ATTR_SASL_USERNAME, sasl_username),
+ RECV_ATTR_STR(MAIL_ATTR_SASL_SENDER, sasl_sender),
+ /* XXX Ditto if we want to pass TLS certificate info. */
+ RECV_ATTR_STR(MAIL_ATTR_LOG_IDENT, log_ident),
+ RECV_ATTR_STR(MAIL_ATTR_RWR_CONTEXT, rewrite_context),
+ RECV_ATTR_INT(MAIL_ATTR_RCPT_COUNT, &rcpt_count),
+ ATTR_TYPE_END) != 23) {
+ msg_warn("%s: error receiving common attributes", myname);
+ return (-1);
+ }
+ if (mail_open_ok(vstring_str(queue_name),
+ vstring_str(queue_id), &st, &path) == 0)
+ return (-1);
+
+ /* Don't override hand-off time after deliver_pass() delegation. */
+ if (request->msg_stats.agent_handoff.tv_sec == 0)
+ GETTIMEOFDAY(&request->msg_stats.agent_handoff);
+
+ request->queue_name = mystrdup(vstring_str(queue_name));
+ request->queue_id = mystrdup(vstring_str(queue_id));
+ request->nexthop = mystrdup(vstring_str(nexthop));
+ request->encoding = mystrdup(vstring_str(encoding));
+ /* Fix 20140708: dedicated smtputf8 attribute with its own flags. */
+ request->smtputf8 = smtputf8;
+ request->sender = mystrdup(vstring_str(address));
+ request->client_name = mystrdup(vstring_str(client_name));
+ request->client_addr = mystrdup(vstring_str(client_addr));
+ request->client_port = mystrdup(vstring_str(client_port));
+ request->client_proto = mystrdup(vstring_str(client_proto));
+ request->client_helo = mystrdup(vstring_str(client_helo));
+ request->sasl_method = mystrdup(vstring_str(sasl_method));
+ request->sasl_username = mystrdup(vstring_str(sasl_username));
+ request->sasl_sender = mystrdup(vstring_str(sasl_sender));
+ request->log_ident = mystrdup(vstring_str(log_ident));
+ request->rewrite_context = mystrdup(vstring_str(rewrite_context));
+ request->dsn_envid = mystrdup(vstring_str(dsn_envid));
+ request->dsn_ret = dsn_ret;
+
+ /*
+ * Extract the recipient offset and address list. Skip over any
+ * attributes from the sender that we do not understand.
+ */
+ while (rcpt_count-- > 0) {
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_FUNC(rcpb_scan, (void *) rcpt_buf),
+ ATTR_TYPE_END) != 1) {
+ msg_warn("%s: error receiving recipient attributes", myname);
+ return (-1);
+ }
+ recipient_list_add(&request->rcpt_list, rcpt_buf->offset,
+ vstring_str(rcpt_buf->dsn_orcpt),
+ rcpt_buf->dsn_notify,
+ vstring_str(rcpt_buf->orig_addr),
+ vstring_str(rcpt_buf->address));
+ }
+ if (request->rcpt_list.len <= 0) {
+ msg_warn("%s: no recipients in delivery request for destination %s",
+ request->queue_id, request->nexthop);
+ return (-1);
+ }
+
+ /*
+ * Open the queue file and set a shared lock, in order to prevent
+ * duplicate deliveries when the queue is flushed immediately after queue
+ * manager restart.
+ *
+ * The queue manager locks the file exclusively when it enters the active
+ * queue, and releases the lock before starting deliveries from that
+ * file. The queue manager does not lock the file again when reading more
+ * recipients into memory. When the queue manager is restarted, the new
+ * process moves files from the active queue to the incoming queue to
+ * cool off for a while. Delivery agents should therefore never try to
+ * open a file that is locked by a queue manager process.
+ *
+ * Opening the queue file can fail for a variety of reasons, such as the
+ * system running out of resources. Instead of throwing away mail, we're
+ * raising a fatal error which forces the mail system to back off, and
+ * retry later.
+ */
+#define DELIVER_LOCK_MODE (MYFLOCK_OP_SHARED | MYFLOCK_OP_NOWAIT)
+
+ request->fp =
+ mail_queue_open(request->queue_name, request->queue_id, O_RDWR, 0);
+ if (request->fp == 0) {
+ if (errno != ENOENT)
+ msg_fatal("open %s %s: %m", request->queue_name, request->queue_id);
+ msg_warn("open %s %s: %m", request->queue_name, request->queue_id);
+ return (-1);
+ }
+ if (msg_verbose)
+ msg_info("%s: file %s", myname, VSTREAM_PATH(request->fp));
+ if (myflock(vstream_fileno(request->fp), INTERNAL_LOCK, DELIVER_LOCK_MODE) < 0)
+ msg_fatal("shared lock %s: %m", VSTREAM_PATH(request->fp));
+ close_on_exec(vstream_fileno(request->fp), CLOSE_ON_EXEC);
+
+ return (0);
+}
+
+/* deliver_request_alloc - allocate delivery request structure */
+
+static DELIVER_REQUEST *deliver_request_alloc(void)
+{
+ DELIVER_REQUEST *request;
+
+ request = (DELIVER_REQUEST *) mymalloc(sizeof(*request));
+ request->fp = 0;
+ request->queue_name = 0;
+ request->queue_id = 0;
+ request->nexthop = 0;
+ request->encoding = 0;
+ request->sender = 0;
+ request->data_offset = 0;
+ request->data_size = 0;
+ recipient_list_init(&request->rcpt_list, RCPT_LIST_INIT_STATUS);
+ request->hop_status = 0;
+ request->client_name = 0;
+ request->client_addr = 0;
+ request->client_port = 0;
+ request->client_proto = 0;
+ request->client_helo = 0;
+ request->sasl_method = 0;
+ request->sasl_username = 0;
+ request->sasl_sender = 0;
+ request->log_ident = 0;
+ request->rewrite_context = 0;
+ request->dsn_envid = 0;
+ return (request);
+}
+
+/* deliver_request_free - clean up delivery request structure */
+
+static void deliver_request_free(DELIVER_REQUEST *request)
+{
+ if (request->fp)
+ vstream_fclose(request->fp);
+ if (request->queue_name)
+ myfree(request->queue_name);
+ if (request->queue_id)
+ myfree(request->queue_id);
+ if (request->nexthop)
+ myfree(request->nexthop);
+ if (request->encoding)
+ myfree(request->encoding);
+ if (request->sender)
+ myfree(request->sender);
+ recipient_list_free(&request->rcpt_list);
+ if (request->hop_status)
+ dsn_free(request->hop_status);
+ if (request->client_name)
+ myfree(request->client_name);
+ if (request->client_addr)
+ myfree(request->client_addr);
+ if (request->client_port)
+ myfree(request->client_port);
+ if (request->client_proto)
+ myfree(request->client_proto);
+ if (request->client_helo)
+ myfree(request->client_helo);
+ if (request->sasl_method)
+ myfree(request->sasl_method);
+ if (request->sasl_username)
+ myfree(request->sasl_username);
+ if (request->sasl_sender)
+ myfree(request->sasl_sender);
+ if (request->log_ident)
+ myfree(request->log_ident);
+ if (request->rewrite_context)
+ myfree(request->rewrite_context);
+ if (request->dsn_envid)
+ myfree(request->dsn_envid);
+ myfree((void *) request);
+}
+
+/* deliver_request_read - create and read delivery request */
+
+DELIVER_REQUEST *deliver_request_read(VSTREAM *stream)
+{
+ DELIVER_REQUEST *request;
+
+ /*
+ * Tell the queue manager that we are ready for this request.
+ */
+ if (deliver_request_initial(stream) != 0)
+ return (0);
+
+ /*
+ * Be prepared for the queue manager to change its mind after contacting
+ * us. This can happen when a transport or host goes bad.
+ */
+ (void) read_wait(vstream_fileno(stream), -1);
+ if (peekfd(vstream_fileno(stream)) <= 0)
+ return (0);
+
+ /*
+ * Allocate and read the queue manager's delivery request.
+ */
+#define XXX_DEFER_STATUS -1
+
+ request = deliver_request_alloc();
+ if (deliver_request_get(stream, request) < 0) {
+ deliver_request_done(stream, request, XXX_DEFER_STATUS);
+ request = 0;
+ }
+ return (request);
+}
+
+/* deliver_request_done - finish delivery request */
+
+int deliver_request_done(VSTREAM *stream, DELIVER_REQUEST *request, int status)
+{
+ int err;
+
+ err = deliver_request_final(stream, request, status);
+ deliver_request_free(request);
+ return (err);
+}