summaryrefslogtreecommitdiffstats
path: root/src/global/mail_copy.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/global/mail_copy.c')
-rw-r--r--src/global/mail_copy.c315
1 files changed, 315 insertions, 0 deletions
diff --git a/src/global/mail_copy.c b/src/global/mail_copy.c
new file mode 100644
index 0000000..7c60370
--- /dev/null
+++ b/src/global/mail_copy.c
@@ -0,0 +1,315 @@
+/*++
+/* NAME
+/* mail_copy 3
+/* SUMMARY
+/* copy message with extreme prejudice
+/* SYNOPSIS
+/* #include <mail_copy.h>
+/*
+/* int mail_copy(sender, orig_to, delivered, src, dst, flags, eol, why)
+/* const char *sender;
+/* const char *orig_to;
+/* const char *delivered;
+/* VSTREAM *src;
+/* VSTREAM *dst;
+/* int flags;
+/* const char *eol;
+/* DSN_BUF *why;
+/* DESCRIPTION
+/* mail_copy() copies a mail message from record stream to stream-lf
+/* stream, and attempts to detect all possible I/O errors.
+/*
+/* Arguments:
+/* .IP sender
+/* The sender envelope address.
+/* .IP delivered
+/* Null pointer or delivered-to: header address.
+/* .IP src
+/* The source record stream, positioned at the beginning of the
+/* message contents.
+/* .IP dst
+/* The destination byte stream (in stream-lf format). If the message
+/* ends in an incomplete line, a newline character is appended to
+/* the output.
+/* .IP flags
+/* The binary OR of zero or more of the following:
+/* .RS
+/* .IP MAIL_COPY_QUOTE
+/* Prepend a `>' character to lines beginning with `From '.
+/* .IP MAIL_COPY_DOT
+/* Prepend a `.' character to lines beginning with `.'.
+/* .IP MAIL_COPY_TOFILE
+/* On systems that support this, use fsync() to flush the
+/* data to stable storage, and truncate the destination
+/* file to its original length in case of problems.
+/* .IP MAIL_COPY_FROM
+/* Prepend a UNIX-style From_ line to the message.
+/* .IP MAIL_COPY_BLANK
+/* Append an empty line to the end of the message.
+/* .IP MAIL_COPY_DELIVERED
+/* Prepend a Delivered-To: header with the name of the
+/* \fIdelivered\fR attribute.
+/* The address is quoted according to RFC822 rules.
+/* .IP MAIL_COPY_ORIG_RCPT
+/* Prepend an X-Original-To: header with the original
+/* envelope recipient address. This is a NOOP with
+/* var_enable_orcpt === 0.
+/* .IP MAIL_COPY_RETURN_PATH
+/* Prepend a Return-Path: header with the value of the
+/* \fIsender\fR attribute.
+/* .RE
+/* The manifest constant MAIL_COPY_MBOX is a convenient shorthand for
+/* all MAIL_COPY_XXX options that are appropriate for mailbox delivery.
+/* Use MAIL_COPY_NONE to copy a message without any options enabled.
+/* .IP eol
+/* Record delimiter, for example, LF or CF LF.
+/* .IP why
+/* A null pointer, or storage for the reason of failure in
+/* the form of a DSN detail code plus free text.
+/* DIAGNOSTICS
+/* A non-zero result means the operation failed. Warnings: corrupt
+/* message file. A corrupt message is marked as corrupt.
+/*
+/* The result is the bit-wise OR of zero or more of the following:
+/* .IP MAIL_COPY_STAT_CORRUPT
+/* The queue file is marked as corrupt.
+/* .IP MAIL_COPY_STAT_READ
+/* A read error was detected; errno specifies the nature of the problem.
+/* .IP MAIL_COPY_STAT_WRITE
+/* A write error was detected; errno specifies the nature of the problem.
+/* SEE ALSO
+/* mark_corrupt(3), mark queue file as corrupted.
+/* 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 <time.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+#include <iostuff.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include "quote_822_local.h"
+#include "record.h"
+#include "rec_type.h"
+#include "mail_queue.h"
+#include "mail_addr.h"
+#include "mark_corrupt.h"
+#include "mail_params.h"
+#include "mail_copy.h"
+#include "mbox_open.h"
+#include "dsn_buf.h"
+#include "sys_exits.h"
+
+/* mail_copy - copy message with extreme prejudice */
+
+int mail_copy(const char *sender,
+ const char *orig_rcpt,
+ const char *delivered,
+ VSTREAM *src, VSTREAM *dst,
+ int flags, const char *eol, DSN_BUF *why)
+{
+ const char *myname = "mail_copy";
+ VSTRING *buf;
+ char *bp;
+ off_t orig_length;
+ int read_error;
+ int write_error;
+ int corrupt_error = 0;
+ time_t now;
+ int type;
+ int prev_type;
+ struct stat st;
+ off_t size_limit;
+
+ /*
+ * Workaround 20090114. This will hopefully get someone's attention. The
+ * problem with file_size_limit < message_size_limit is that mail will be
+ * delivered again and again until someone removes it from the queue by
+ * hand, because Postfix cannot mark a recipient record as "completed".
+ */
+ if (fstat(vstream_fileno(src), &st) < 0)
+ msg_fatal("fstat: %m");
+ if ((size_limit = get_file_limit()) < st.st_size)
+ msg_panic("file size limit %lu < message size %lu. This "
+ "causes large messages to be delivered repeatedly "
+ "after they were submitted with \"sendmail -t\" "
+ "or after recipients were added with the Milter "
+ "SMFIR_ADDRCPT request",
+ (unsigned long) size_limit,
+ (unsigned long) st.st_size);
+
+ /*
+ * Initialize.
+ */
+#ifndef NO_TRUNCATE
+ if ((flags & MAIL_COPY_TOFILE) != 0)
+ if ((orig_length = vstream_fseek(dst, (off_t) 0, SEEK_END)) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(dst));
+#endif
+ buf = vstring_alloc(100);
+
+ /*
+ * Prepend a bunch of headers to the message.
+ */
+ if (flags & (MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH)) {
+ if (sender == 0)
+ msg_panic("%s: null sender", myname);
+ quote_822_local(buf, sender);
+ if (flags & MAIL_COPY_FROM) {
+ time(&now);
+ vstream_fprintf(dst, "From %s %.24s%s", *sender == 0 ?
+ MAIL_ADDR_MAIL_DAEMON : vstring_str(buf),
+ asctime(localtime(&now)), eol);
+ }
+ if (flags & MAIL_COPY_RETURN_PATH) {
+ vstream_fprintf(dst, "Return-Path: <%s>%s",
+ *sender ? vstring_str(buf) : "", eol);
+ }
+ }
+ if (flags & MAIL_COPY_ORIG_RCPT) {
+ if (orig_rcpt == 0)
+ msg_panic("%s: null orig_rcpt", myname);
+
+ /*
+ * An empty original recipient record almost certainly means that
+ * original recipient processing was disabled.
+ */
+ if (var_enable_orcpt && *orig_rcpt) {
+ quote_822_local(buf, orig_rcpt);
+ vstream_fprintf(dst, "X-Original-To: %s%s", vstring_str(buf), eol);
+ }
+ }
+ if (flags & MAIL_COPY_DELIVERED) {
+ if (delivered == 0)
+ msg_panic("%s: null delivered", myname);
+ quote_822_local(buf, delivered);
+ vstream_fprintf(dst, "Delivered-To: %s%s", vstring_str(buf), eol);
+ }
+
+ /*
+ * Copy the message. Escape lines that could be confused with the ugly
+ * From_ line. Make sure that there is a blank line at the end of the
+ * message so that the next ugly From_ can be found by mail reading
+ * software.
+ *
+ * XXX Rely on the front-end services to enforce record size limits.
+ */
+#define VSTREAM_FWRITE_BUF(s,b) \
+ vstream_fwrite((s),vstring_str(b),VSTRING_LEN(b))
+
+ prev_type = REC_TYPE_NORM;
+ while ((type = rec_get(src, buf, 0)) > 0) {
+ if (type != REC_TYPE_NORM && type != REC_TYPE_CONT)
+ break;
+ bp = vstring_str(buf);
+ if (prev_type == REC_TYPE_NORM) {
+ if ((flags & MAIL_COPY_QUOTE) && *bp == 'F' && !strncmp(bp, "From ", 5))
+ VSTREAM_PUTC('>', dst);
+ if ((flags & MAIL_COPY_DOT) && *bp == '.')
+ VSTREAM_PUTC('.', dst);
+ }
+ if (VSTRING_LEN(buf) && VSTREAM_FWRITE_BUF(dst, buf) != VSTRING_LEN(buf))
+ break;
+ if (type == REC_TYPE_NORM && vstream_fputs(eol, dst) == VSTREAM_EOF)
+ break;
+ prev_type = type;
+ }
+ if (vstream_ferror(dst) == 0) {
+ if (var_fault_inj_code == 1)
+ type = 0;
+ if (type != REC_TYPE_XTRA) {
+ /* XXX Where is the queue ID? */
+ msg_warn("bad record type: %d in message content", type);
+ corrupt_error = mark_corrupt(src);
+ }
+ if (prev_type != REC_TYPE_NORM)
+ vstream_fputs(eol, dst);
+ if (flags & MAIL_COPY_BLANK)
+ vstream_fputs(eol, dst);
+ }
+ vstring_free(buf);
+
+ /*
+ * Make sure we read and wrote all. Truncate the file to its original
+ * length when the delivery failed. POSIX does not require ftruncate(),
+ * so we may have a portability problem. Note that fclose() may fail even
+ * while fflush and fsync() succeed. Think of remote file systems such as
+ * AFS that copy the file back to the server upon close. Oh well, no
+ * point optimizing the error case. XXX On systems that use flock()
+ * locking, we must truncate the file before closing it (and losing
+ * the exclusive lock).
+ */
+ read_error = vstream_ferror(src);
+ write_error = vstream_fflush(dst);
+#ifdef HAS_FSYNC
+ if ((flags & MAIL_COPY_TOFILE) != 0)
+ write_error |= fsync(vstream_fileno(dst));
+#endif
+ if (var_fault_inj_code == 2) {
+ read_error = 1;
+ errno = ENOENT;
+ }
+ if (var_fault_inj_code == 3) {
+ write_error = 1;
+ errno = ENOENT;
+ }
+#ifndef NO_TRUNCATE
+ if ((flags & MAIL_COPY_TOFILE) != 0)
+ if (corrupt_error || read_error || write_error)
+ /* Complain about ignored "undo" errors? So sue me. */
+ (void) ftruncate(vstream_fileno(dst), orig_length);
+#endif
+ write_error |= vstream_fclose(dst);
+
+ /*
+ * Return the optional verbose error description.
+ */
+#define TRY_AGAIN_ERROR(errno) \
+ (errno == EAGAIN || errno == ESTALE)
+
+ if (why && read_error)
+ dsb_unix(why, TRY_AGAIN_ERROR(errno) ? "4.3.0" : "5.3.0",
+ sys_exits_detail(EX_IOERR)->text,
+ "error reading message: %m");
+ if (why && write_error)
+ dsb_unix(why, mbox_dsn(errno, "5.3.0"),
+ sys_exits_detail(EX_IOERR)->text,
+ "error writing message: %m");
+
+ /*
+ * Use flag+errno description when the optional verbose description is
+ * not desired.
+ */
+ return ((corrupt_error ? MAIL_COPY_STAT_CORRUPT : 0)
+ | (read_error ? MAIL_COPY_STAT_READ : 0)
+ | (write_error ? MAIL_COPY_STAT_WRITE : 0));
+}