diff options
Diffstat (limited to 'src/local/forward.c')
-rw-r--r-- | src/local/forward.c | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/src/local/forward.c b/src/local/forward.c new file mode 100644 index 0000000..14367af --- /dev/null +++ b/src/local/forward.c @@ -0,0 +1,392 @@ +/*++ +/* 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_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); +} |