7 /* copy message with extreme prejudice
9 /* #include <mail_copy.h>
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;
21 /* mail_copy() copies a mail message from record stream to stream-lf
22 /* stream, and attempts to detect all possible I/O errors.
26 /* The sender envelope address.
28 /* Null pointer or delivered-to: header address.
30 /* The source record stream, positioned at the beginning of the
33 /* The destination byte stream (in stream-lf format). If the message
34 /* ends in an incomplete line, a newline character is appended to
37 /* The binary OR of zero or more of the following:
39 /* .IP MAIL_COPY_QUOTE
40 /* Prepend a `>' character to lines beginning with `From '.
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.
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.
58 /* .IP MAIL_COPY_RETURN_PATH
59 /* Prepend a Return-Path: header with the value of the
60 /* \fIsender\fR attribute.
62 /* The manifest constant MAIL_COPY_MBOX is a convenient shorthand for
63 /* all MAIL_COPY_XXX options that are appropriate for mailbox delivery.
64 /* Use MAIL_COPY_NONE to copy a message without any options enabled.
66 /* Record delimiter, for example, LF or CF LF.
68 /* A null pointer, or storage for the reason of failure in
69 /* the form of a DSN detail code plus free text.
71 /* A non-zero result means the operation failed. Warnings: corrupt
72 /* message file. A corrupt message is marked as corrupt.
74 /* The result is the bit-wise OR of zero or more of the following:
75 /* .IP MAIL_COPY_STAT_CORRUPT
76 /* The queue file is marked as corrupt.
77 /* .IP MAIL_COPY_STAT_READ
78 /* A read error was detected; errno specifies the nature of the problem.
79 /* .IP MAIL_COPY_STAT_WRITE
80 /* A write error was detected; errno specifies the nature of the problem.
82 /* mark_corrupt(3), mark queue file as corrupted.
86 /* The Secure Mailer license must be distributed with this software.
89 /* IBM T.J. Watson Research
91 /* Yorktown Heights, NY 10598, USA
103 /* Utility library. */
109 #include <vstring_vstream.h>
110 #include <stringops.h>
113 /* Global library. */
115 #include "quote_822_local.h"
117 #include "rec_type.h"
118 #include "mail_queue.h"
119 #include "mail_addr.h"
120 #include "mark_corrupt.h"
121 #include "mail_params.h"
122 #include "mail_copy.h"
123 #include "mbox_open.h"
125 #include "sys_exits.h"
127 /* mail_copy - copy message with extreme prejudice */
129 int mail_copy(const char *sender
,
130 const char *orig_rcpt
,
131 const char *delivered
,
132 VSTREAM
*src
, VSTREAM
*dst
,
133 int flags
, const char *eol
, DSN_BUF
*why
)
135 const char *myname
= "mail_copy";
141 int corrupt_error
= 0;
149 * Workaround 20090114. This will hopefully get someone's attention. The
150 * problem with file_size_limit < message_size_limit is that mail will be
151 * delivered again and again until someone removes it from the queue by
152 * hand, because Postfix cannot mark a recipient record as "completed".
154 if (fstat(vstream_fileno(src
), &st
) < 0)
155 msg_fatal("fstat: %m");
156 if ((size_limit
= get_file_limit()) < st
.st_size
)
157 msg_panic("file size limit %lu < message size %lu. This "
158 "causes large messages to be delivered repeatedly "
159 "after they were submitted with \"sendmail -t\" "
160 "or after recipients were added with the Milter "
161 "SMFIR_ADDRCPT request",
162 (unsigned long) size_limit
,
163 (unsigned long) st
.st_size
);
169 if ((flags
& MAIL_COPY_TOFILE
) != 0)
170 if ((orig_length
= vstream_fseek(dst
, (off_t
) 0, SEEK_END
)) < 0)
171 msg_fatal("seek file %s: %m", VSTREAM_PATH(dst
));
173 buf
= vstring_alloc(100);
176 * Prepend a bunch of headers to the message.
178 if (flags
& (MAIL_COPY_FROM
| MAIL_COPY_RETURN_PATH
)) {
180 msg_panic("%s: null sender", myname
);
181 quote_822_local(buf
, sender
);
182 if (flags
& MAIL_COPY_FROM
) {
184 vstream_fprintf(dst
, "From %s %.24s%s", *sender
== 0 ?
185 MAIL_ADDR_MAIL_DAEMON
: vstring_str(buf
),
186 asctime(localtime(&now
)), eol
);
188 if (flags
& MAIL_COPY_RETURN_PATH
) {
189 vstream_fprintf(dst
, "Return-Path: <%s>%s",
190 *sender
? vstring_str(buf
) : "", eol
);
193 if (flags
& MAIL_COPY_ORIG_RCPT
) {
195 msg_panic("%s: null orig_rcpt", myname
);
198 * An empty original recipient record almost certainly means that
199 * original recipient processing was disabled.
202 quote_822_local(buf
, orig_rcpt
);
203 vstream_fprintf(dst
, "X-Original-To: %s%s", vstring_str(buf
), eol
);
206 if (flags
& MAIL_COPY_DELIVERED
) {
208 msg_panic("%s: null delivered", myname
);
209 quote_822_local(buf
, delivered
);
210 vstream_fprintf(dst
, "Delivered-To: %s%s", vstring_str(buf
), eol
);
214 * Copy the message. Escape lines that could be confused with the ugly
215 * From_ line. Make sure that there is a blank line at the end of the
216 * message so that the next ugly From_ can be found by mail reading
219 * XXX Rely on the front-end services to enforce record size limits.
221 #define VSTREAM_FWRITE_BUF(s,b) \
222 vstream_fwrite((s),vstring_str(b),VSTRING_LEN(b))
224 prev_type
= REC_TYPE_NORM
;
225 while ((type
= rec_get(src
, buf
, 0)) > 0) {
226 if (type
!= REC_TYPE_NORM
&& type
!= REC_TYPE_CONT
)
228 bp
= vstring_str(buf
);
229 if (prev_type
== REC_TYPE_NORM
) {
230 if ((flags
& MAIL_COPY_QUOTE
) && *bp
== 'F' && !strncmp(bp
, "From ", 5))
231 VSTREAM_PUTC('>', dst
);
232 if ((flags
& MAIL_COPY_DOT
) && *bp
== '.')
233 VSTREAM_PUTC('.', dst
);
235 if (VSTRING_LEN(buf
) && VSTREAM_FWRITE_BUF(dst
, buf
) != VSTRING_LEN(buf
))
237 if (type
== REC_TYPE_NORM
&& vstream_fputs(eol
, dst
) == VSTREAM_EOF
)
241 if (vstream_ferror(dst
) == 0) {
242 if (var_fault_inj_code
== 1)
244 if (type
!= REC_TYPE_XTRA
) {
245 /* XXX Where is the queue ID? */
246 msg_warn("bad record type: %d in message content", type
);
247 corrupt_error
= mark_corrupt(src
);
249 if (prev_type
!= REC_TYPE_NORM
)
250 vstream_fputs(eol
, dst
);
251 if (flags
& MAIL_COPY_BLANK
)
252 vstream_fputs(eol
, dst
);
257 * Make sure we read and wrote all. Truncate the file to its original
258 * length when the delivery failed. POSIX does not require ftruncate(),
259 * so we may have a portability problem. Note that fclose() may fail even
260 * while fflush and fsync() succeed. Think of remote file systems such as
261 * AFS that copy the file back to the server upon close. Oh well, no
262 * point optimizing the error case. XXX On systems that use flock()
263 * locking, we must truncate the file file before closing it (and losing
264 * the exclusive lock).
266 read_error
= vstream_ferror(src
);
267 write_error
= vstream_fflush(dst
);
269 if ((flags
& MAIL_COPY_TOFILE
) != 0)
270 write_error
|= fsync(vstream_fileno(dst
));
272 if (var_fault_inj_code
== 2) {
276 if (var_fault_inj_code
== 3) {
281 if ((flags
& MAIL_COPY_TOFILE
) != 0)
282 if (corrupt_error
|| read_error
|| write_error
)
283 ftruncate(vstream_fileno(dst
), orig_length
);
285 write_error
|= vstream_fclose(dst
);
288 * Return the optional verbose error description.
290 #define TRY_AGAIN_ERROR(errno) \
291 (errno == EAGAIN || errno == ESTALE)
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");
303 * Use flag+errno description when the optional verbose description is
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));