1/*++
2/* NAME
3/*	mail_copy 3
4/* SUMMARY
5/*	copy message with extreme prejudice
6/* SYNOPSIS
7/*	#include <mail_copy.h>
8/*
9/*	int	mail_copy(sender, orig_to, delivered, src, dst, flags, eol, why)
10/*	const char *sender;
11/*	const char *orig_to;
12/*	const char *delivered;
13/*	VSTREAM	*src;
14/*	VSTREAM	*dst;
15/*	int	flags;
16/*	const char *eol;
17/*	DSN_BUF	*why;
18/* DESCRIPTION
19/*	mail_copy() copies a mail message from record stream to stream-lf
20/*	stream, and attempts to detect all possible I/O errors.
21/*
22/*	Arguments:
23/* .IP sender
24/*	The sender envelope address.
25/* .IP delivered
26/*	Null pointer or delivered-to: header address.
27/* .IP src
28/*	The source record stream, positioned at the beginning of the
29/*	message contents.
30/* .IP dst
31/*	The destination byte stream (in stream-lf format). If the message
32/*	ends in an incomplete line, a newline character is appended to
33/*	the output.
34/* .IP flags
35/*	The binary OR of zero or more of the following:
36/* .RS
37/* .IP MAIL_COPY_QUOTE
38/*	Prepend a `>' character to lines beginning with `From '.
39/* .IP MAIL_COPY_DOT
40/*	Prepend a `.' character to lines beginning with `.'.
41/* .IP MAIL_COPY_TOFILE
42/*	On systems that support this, use fsync() to flush the
43/*	data to stable storage, and truncate the destination
44/*	file to its original length in case of problems.
45/* .IP MAIL_COPY_FROM
46/*	Prepend a UNIX-style From_ line to the message.
47/* .IP MAIL_COPY_BLANK
48/*	Append an empty line to the end of the message.
49/* .IP MAIL_COPY_DELIVERED
50/*	Prepend a Delivered-To: header with the name of the
51/*	\fIdelivered\fR attribute.
52/*	The address is quoted according to RFC822 rules.
53/* .IP MAIL_COPY_ORIG_RCPT
54/*	Prepend an X-Original-To: header with the original
55/*	envelope recipient address.
56/* .IP MAIL_COPY_RETURN_PATH
57/*	Prepend a Return-Path: header with the value of the
58/*	\fIsender\fR attribute.
59/* .RE
60/*	The manifest constant MAIL_COPY_MBOX is a convenient shorthand for
61/*	all MAIL_COPY_XXX options that are appropriate for mailbox delivery.
62/*	Use MAIL_COPY_NONE to copy a message without any options enabled.
63/* .IP eol
64/*	Record delimiter, for example, LF or CF LF.
65/* .IP why
66/*	A null pointer, or storage for the reason of failure in
67/*	the form of a DSN detail code plus free text.
68/* DIAGNOSTICS
69/*	A non-zero result means the operation failed. Warnings: corrupt
70/*	message file. A corrupt message is marked as corrupt.
71/*
72/*	The result is the bit-wise OR of zero or more of the following:
73/* .IP MAIL_COPY_STAT_CORRUPT
74/*	The queue file is marked as corrupt.
75/* .IP MAIL_COPY_STAT_READ
76/*	A read error was detected; errno specifies the nature of the problem.
77/* .IP MAIL_COPY_STAT_WRITE
78/*	A write error was detected; errno specifies the nature of the problem.
79/* SEE ALSO
80/*	mark_corrupt(3), mark queue file as corrupted.
81/* LICENSE
82/* .ad
83/* .fi
84/*	The Secure Mailer license must be distributed with this software.
85/* AUTHOR(S)
86/*	Wietse Venema
87/*	IBM T.J. Watson Research
88/*	P.O. Box 704
89/*	Yorktown Heights, NY 10598, USA
90/*--*/
91
92/* System library. */
93
94#include <sys_defs.h>
95#include <sys/stat.h>
96#include <string.h>
97#include <unistd.h>
98#include <time.h>
99#include <errno.h>
100
101/* Utility library. */
102
103#include <msg.h>
104#include <htable.h>
105#include <vstream.h>
106#include <vstring.h>
107#include <vstring_vstream.h>
108#include <stringops.h>
109#include <iostuff.h>
110#include <warn_stat.h>
111
112/* Global library. */
113
114#include "quote_822_local.h"
115#include "record.h"
116#include "rec_type.h"
117#include "mail_queue.h"
118#include "mail_addr.h"
119#include "mark_corrupt.h"
120#include "mail_params.h"
121#include "mail_copy.h"
122#include "mbox_open.h"
123#include "dsn_buf.h"
124#include "sys_exits.h"
125
126/* mail_copy - copy message with extreme prejudice */
127
128int     mail_copy(const char *sender,
129		          const char *orig_rcpt,
130		          const char *delivered,
131		          VSTREAM *src, VSTREAM *dst,
132		          int flags, const char *eol, DSN_BUF *why)
133{
134    const char *myname = "mail_copy";
135    VSTRING *buf;
136    char   *bp;
137    off_t   orig_length;
138    int     read_error;
139    int     write_error;
140    int     corrupt_error = 0;
141    time_t  now;
142    int     type;
143    int     prev_type;
144    struct stat st;
145    off_t   size_limit;
146
147    /*
148     * Workaround 20090114. This will hopefully get someone's attention. The
149     * problem with file_size_limit < message_size_limit is that mail will be
150     * delivered again and again until someone removes it from the queue by
151     * hand, because Postfix cannot mark a recipient record as "completed".
152     */
153    if (fstat(vstream_fileno(src), &st) < 0)
154	msg_fatal("fstat: %m");
155    if ((size_limit = get_file_limit()) < st.st_size)
156	msg_panic("file size limit %lu < message size %lu. This "
157		  "causes large messages to be delivered repeatedly "
158		  "after they were submitted with \"sendmail -t\" "
159		  "or after recipients were added with the Milter "
160		  "SMFIR_ADDRCPT request",
161		  (unsigned long) size_limit,
162		  (unsigned long) st.st_size);
163
164    /*
165     * Initialize.
166     */
167#ifndef NO_TRUNCATE
168    if ((flags & MAIL_COPY_TOFILE) != 0)
169	if ((orig_length = vstream_fseek(dst, (off_t) 0, SEEK_END)) < 0)
170	    msg_fatal("seek file %s: %m", VSTREAM_PATH(dst));
171#endif
172    buf = vstring_alloc(100);
173
174    /*
175     * Prepend a bunch of headers to the message.
176     */
177    if (flags & (MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH)) {
178	if (sender == 0)
179	    msg_panic("%s: null sender", myname);
180	quote_822_local(buf, sender);
181	if (flags & MAIL_COPY_FROM) {
182	    time(&now);
183	    vstream_fprintf(dst, "From %s  %.24s%s", *sender == 0 ?
184			    MAIL_ADDR_MAIL_DAEMON : vstring_str(buf),
185			    asctime(localtime(&now)), eol);
186	}
187	if (flags & MAIL_COPY_RETURN_PATH) {
188	    vstream_fprintf(dst, "Return-Path: <%s>%s",
189			    *sender ? vstring_str(buf) : "", eol);
190	}
191    }
192    if (flags & MAIL_COPY_ORIG_RCPT) {
193	if (orig_rcpt == 0)
194	    msg_panic("%s: null orig_rcpt", myname);
195
196	/*
197	 * An empty original recipient record almost certainly means that
198	 * original recipient processing was disabled.
199	 */
200	if (*orig_rcpt) {
201	    quote_822_local(buf, orig_rcpt);
202	    vstream_fprintf(dst, "X-Original-To: %s%s", vstring_str(buf), eol);
203	}
204    }
205    if (flags & MAIL_COPY_DELIVERED) {
206	if (delivered == 0)
207	    msg_panic("%s: null delivered", myname);
208	quote_822_local(buf, delivered);
209	vstream_fprintf(dst, "Delivered-To: %s%s", vstring_str(buf), eol);
210    }
211
212    /*
213     * Copy the message. Escape lines that could be confused with the ugly
214     * From_ line. Make sure that there is a blank line at the end of the
215     * message so that the next ugly From_ can be found by mail reading
216     * software.
217     *
218     * XXX Rely on the front-end services to enforce record size limits.
219     */
220#define VSTREAM_FWRITE_BUF(s,b) \
221	vstream_fwrite((s),vstring_str(b),VSTRING_LEN(b))
222
223    prev_type = REC_TYPE_NORM;
224    while ((type = rec_get(src, buf, 0)) > 0) {
225	if (type != REC_TYPE_NORM && type != REC_TYPE_CONT)
226	    break;
227	bp = vstring_str(buf);
228	if (prev_type == REC_TYPE_NORM) {
229	    if ((flags & MAIL_COPY_QUOTE) && *bp == 'F' && !strncmp(bp, "From ", 5))
230		VSTREAM_PUTC('>', dst);
231	    if ((flags & MAIL_COPY_DOT) && *bp == '.')
232		VSTREAM_PUTC('.', dst);
233	}
234	if (VSTRING_LEN(buf) && VSTREAM_FWRITE_BUF(dst, buf) != VSTRING_LEN(buf))
235	    break;
236	if (type == REC_TYPE_NORM && vstream_fputs(eol, dst) == VSTREAM_EOF)
237	    break;
238	prev_type = type;
239    }
240    if (vstream_ferror(dst) == 0) {
241	if (var_fault_inj_code == 1)
242	    type = 0;
243	if (type != REC_TYPE_XTRA) {
244	    /* XXX Where is the queue ID? */
245	    msg_warn("bad record type: %d in message content", type);
246	    corrupt_error = mark_corrupt(src);
247	}
248	if (prev_type != REC_TYPE_NORM)
249	    vstream_fputs(eol, dst);
250	if (flags & MAIL_COPY_BLANK)
251	    vstream_fputs(eol, dst);
252    }
253    vstring_free(buf);
254
255    /*
256     * Make sure we read and wrote all. Truncate the file to its original
257     * length when the delivery failed. POSIX does not require ftruncate(),
258     * so we may have a portability problem. Note that fclose() may fail even
259     * while fflush and fsync() succeed. Think of remote file systems such as
260     * AFS that copy the file back to the server upon close. Oh well, no
261     * point optimizing the error case. XXX On systems that use flock()
262     * locking, we must truncate the file file before closing it (and losing
263     * the exclusive lock).
264     */
265    read_error = vstream_ferror(src);
266    write_error = vstream_fflush(dst);
267#ifdef HAS_FSYNC
268    if ((flags & MAIL_COPY_TOFILE) != 0)
269	write_error |= fsync(vstream_fileno(dst));
270#endif
271    if (var_fault_inj_code == 2) {
272	read_error = 1;
273	errno = ENOENT;
274    }
275    if (var_fault_inj_code == 3) {
276	write_error = 1;
277	errno = ENOENT;
278    }
279#ifndef NO_TRUNCATE
280    if ((flags & MAIL_COPY_TOFILE) != 0)
281	if (corrupt_error || read_error || write_error)
282	    /* Complain about ignored "undo" errors? So sue me. */
283	    (void) ftruncate(vstream_fileno(dst), orig_length);
284#endif
285    write_error |= vstream_fclose(dst);
286
287    /*
288     * Return the optional verbose error description.
289     */
290#define TRY_AGAIN_ERROR(errno) \
291	(errno == EAGAIN || errno == ESTALE)
292
293    if (why && read_error)
294	dsb_unix(why, TRY_AGAIN_ERROR(errno) ? "4.3.0" : "5.3.0",
295		 sys_exits_detail(EX_IOERR)->text,
296		 "error reading message: %m");
297    if (why && write_error)
298	dsb_unix(why, mbox_dsn(errno, "5.3.0"),
299		 sys_exits_detail(EX_IOERR)->text,
300		 "error writing message: %m");
301
302    /*
303     * Use flag+errno description when the optional verbose description is
304     * not desired.
305     */
306    return ((corrupt_error ? MAIL_COPY_STAT_CORRUPT : 0)
307	    | (read_error ? MAIL_COPY_STAT_READ : 0)
308	    | (write_error ? MAIL_COPY_STAT_WRITE : 0));
309}
310