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