1 /* $NetBSD: mail_copy.c,v 1.1.1.2 2010/06/17 18:06:49 tron 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. 58 /* .IP MAIL_COPY_RETURN_PATH 59 /* Prepend a Return-Path: header with the value of the 60 /* \fIsender\fR attribute. 61 /* .RE 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. 65 /* .IP eol 66 /* Record delimiter, for example, LF or CF LF. 67 /* .IP why 68 /* A null pointer, or storage for the reason of failure in 69 /* the form of a DSN detail code plus free text. 70 /* DIAGNOSTICS 71 /* A non-zero result means the operation failed. Warnings: corrupt 72 /* message file. A corrupt message is marked as corrupt. 73 /* 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. 81 /* SEE ALSO 82 /* mark_corrupt(3), mark queue file as corrupted. 83 /* LICENSE 84 /* .ad 85 /* .fi 86 /* The Secure Mailer license must be distributed with this software. 87 /* AUTHOR(S) 88 /* Wietse Venema 89 /* IBM T.J. Watson Research 90 /* P.O. Box 704 91 /* Yorktown Heights, NY 10598, USA 92 /*--*/ 93 94 /* System library. */ 95 96 #include <sys_defs.h> 97 #include <sys/stat.h> 98 #include <string.h> 99 #include <unistd.h> 100 #include <time.h> 101 #include <errno.h> 102 103 /* Utility library. */ 104 105 #include <msg.h> 106 #include <htable.h> 107 #include <vstream.h> 108 #include <vstring.h> 109 #include <vstring_vstream.h> 110 #include <stringops.h> 111 #include <iostuff.h> 112 113 /* Global library. */ 114 115 #include "quote_822_local.h" 116 #include "record.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" 124 #include "dsn_buf.h" 125 #include "sys_exits.h" 126 127 /* mail_copy - copy message with extreme prejudice */ 128 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) 134 { 135 const char *myname = "mail_copy"; 136 VSTRING *buf; 137 char *bp; 138 off_t orig_length; 139 int read_error; 140 int write_error; 141 int corrupt_error = 0; 142 time_t now; 143 int type; 144 int prev_type; 145 struct stat st; 146 off_t size_limit; 147 148 /* 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". 153 */ 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); 164 165 /* 166 * Initialize. 167 */ 168 #ifndef NO_TRUNCATE 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)); 172 #endif 173 buf = vstring_alloc(100); 174 175 /* 176 * Prepend a bunch of headers to the message. 177 */ 178 if (flags & (MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH)) { 179 if (sender == 0) 180 msg_panic("%s: null sender", myname); 181 quote_822_local(buf, sender); 182 if (flags & MAIL_COPY_FROM) { 183 time(&now); 184 vstream_fprintf(dst, "From %s %.24s%s", *sender == 0 ? 185 MAIL_ADDR_MAIL_DAEMON : vstring_str(buf), 186 asctime(localtime(&now)), eol); 187 } 188 if (flags & MAIL_COPY_RETURN_PATH) { 189 vstream_fprintf(dst, "Return-Path: <%s>%s", 190 *sender ? vstring_str(buf) : "", eol); 191 } 192 } 193 if (flags & MAIL_COPY_ORIG_RCPT) { 194 if (orig_rcpt == 0) 195 msg_panic("%s: null orig_rcpt", myname); 196 197 /* 198 * An empty original recipient record almost certainly means that 199 * original recipient processing was disabled. 200 */ 201 if (*orig_rcpt) { 202 quote_822_local(buf, orig_rcpt); 203 vstream_fprintf(dst, "X-Original-To: %s%s", vstring_str(buf), eol); 204 } 205 } 206 if (flags & MAIL_COPY_DELIVERED) { 207 if (delivered == 0) 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); 211 } 212 213 /* 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 217 * software. 218 * 219 * XXX Rely on the front-end services to enforce record size limits. 220 */ 221 #define VSTREAM_FWRITE_BUF(s,b) \ 222 vstream_fwrite((s),vstring_str(b),VSTRING_LEN(b)) 223 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) 227 break; 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); 234 } 235 if (VSTRING_LEN(buf) && VSTREAM_FWRITE_BUF(dst, buf) != VSTRING_LEN(buf)) 236 break; 237 if (type == REC_TYPE_NORM && vstream_fputs(eol, dst) == VSTREAM_EOF) 238 break; 239 prev_type = type; 240 } 241 if (vstream_ferror(dst) == 0) { 242 if (var_fault_inj_code == 1) 243 type = 0; 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); 248 } 249 if (prev_type != REC_TYPE_NORM) 250 vstream_fputs(eol, dst); 251 if (flags & MAIL_COPY_BLANK) 252 vstream_fputs(eol, dst); 253 } 254 vstring_free(buf); 255 256 /* 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). 265 */ 266 read_error = vstream_ferror(src); 267 write_error = vstream_fflush(dst); 268 #ifdef HAS_FSYNC 269 if ((flags & MAIL_COPY_TOFILE) != 0) 270 write_error |= fsync(vstream_fileno(dst)); 271 #endif 272 if (var_fault_inj_code == 2) { 273 read_error = 1; 274 errno = ENOENT; 275 } 276 if (var_fault_inj_code == 3) { 277 write_error = 1; 278 errno = ENOENT; 279 } 280 #ifndef NO_TRUNCATE 281 if ((flags & MAIL_COPY_TOFILE) != 0) 282 if (corrupt_error || read_error || write_error) 283 /* Complain about ignored "undo" errors? So sue me. */ 284 (void) ftruncate(vstream_fileno(dst), orig_length); 285 #endif 286 write_error |= vstream_fclose(dst); 287 288 /* 289 * Return the optional verbose error description. 290 */ 291 #define TRY_AGAIN_ERROR(errno) \ 292 (errno == EAGAIN || errno == ESTALE) 293 294 if (why && read_error) 295 dsb_unix(why, TRY_AGAIN_ERROR(errno) ? "4.3.0" : "5.3.0", 296 sys_exits_detail(EX_IOERR)->text, 297 "error reading message: %m"); 298 if (why && write_error) 299 dsb_unix(why, mbox_dsn(errno, "5.3.0"), 300 sys_exits_detail(EX_IOERR)->text, 301 "error writing message: %m"); 302 303 /* 304 * Use flag+errno description when the optional verbose description is 305 * not desired. 306 */ 307 return ((corrupt_error ? MAIL_COPY_STAT_CORRUPT : 0) 308 | (read_error ? MAIL_COPY_STAT_READ : 0) 309 | (write_error ? MAIL_COPY_STAT_WRITE : 0)); 310 } 311