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