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