int mail_copy(const char *sender, const char *orig_rcpt, const char *delivered, VSTREAM *src, VSTREAM *dst, int flags, const char *eol, DSN_BUF *why) { const char *myname = "mail_copy"; VSTRING *buf; char *bp; off_t orig_length; int read_error; int write_error; int corrupt_error = 0; time_t now; int type; int prev_type; struct stat st; off_t size_limit; /* * Workaround 20090114. This will hopefully get someone's attention. The * problem with file_size_limit < message_size_limit is that mail will be * delivered again and again until someone removes it from the queue by * hand, because Postfix cannot mark a recipient record as "completed". */ if (fstat(vstream_fileno(src), &st) < 0) msg_fatal("fstat: %m"); if ((size_limit = get_file_limit()) < st.st_size) msg_panic("file size limit %lu < message size %lu. This " "causes large messages to be delivered repeatedly " "after they were submitted with \"sendmail -t\" " "or after recipients were added with the Milter " "SMFIR_ADDRCPT request", (unsigned long) size_limit, (unsigned long) st.st_size); /* * Initialize. */ #ifndef NO_TRUNCATE if ((flags & MAIL_COPY_TOFILE) != 0) if ((orig_length = vstream_fseek(dst, (off_t) 0, SEEK_END)) < 0) msg_fatal("seek file %s: %m", VSTREAM_PATH(dst)); #endif buf = vstring_alloc(100); /* * Prepend a bunch of headers to the message. */ if (flags & (MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH)) { if (sender == 0) msg_panic("%s: null sender", myname); quote_822_local(buf, sender); if (flags & MAIL_COPY_FROM) { time(&now); vstream_fprintf(dst, "From %s %.24s%s", *sender == 0 ? MAIL_ADDR_MAIL_DAEMON : vstring_str(buf), asctime(localtime(&now)), eol); } if (flags & MAIL_COPY_RETURN_PATH) { vstream_fprintf(dst, "Return-Path: <%s>%s", *sender ? vstring_str(buf) : "", eol); } } if (flags & MAIL_COPY_ORIG_RCPT) { if (orig_rcpt == 0) msg_panic("%s: null orig_rcpt", myname); /* * An empty original recipient record almost certainly means that * original recipient processing was disabled. */ if (*orig_rcpt) { quote_822_local(buf, orig_rcpt); vstream_fprintf(dst, "X-Original-To: %s%s", vstring_str(buf), eol); } } if (flags & MAIL_COPY_DELIVERED) { if (delivered == 0) msg_panic("%s: null delivered", myname); quote_822_local(buf, delivered); vstream_fprintf(dst, "Delivered-To: %s%s", vstring_str(buf), eol); } /* * Copy the message. Escape lines that could be confused with the ugly * From_ line. Make sure that there is a blank line at the end of the * message so that the next ugly From_ can be found by mail reading * software. * * XXX Rely on the front-end services to enforce record size limits. */ #define VSTREAM_FWRITE_BUF(s,b) \ vstream_fwrite((s),vstring_str(b),VSTRING_LEN(b)) prev_type = REC_TYPE_NORM; while ((type = rec_get(src, buf, 0)) > 0) { if (type != REC_TYPE_NORM && type != REC_TYPE_CONT) break; bp = vstring_str(buf); if (prev_type == REC_TYPE_NORM) { if ((flags & MAIL_COPY_QUOTE) && *bp == 'F' && !strncmp(bp, "From ", 5)) VSTREAM_PUTC('>', dst); if ((flags & MAIL_COPY_DOT) && *bp == '.') VSTREAM_PUTC('.', dst); } if (VSTRING_LEN(buf) && VSTREAM_FWRITE_BUF(dst, buf) != VSTRING_LEN(buf)) break; if (type == REC_TYPE_NORM && vstream_fputs(eol, dst) == VSTREAM_EOF) break; prev_type = type; } if (vstream_ferror(dst) == 0) { if (var_fault_inj_code == 1) type = 0; if (type != REC_TYPE_XTRA) { /* XXX Where is the queue ID? */ msg_warn("bad record type: %d in message content", type); corrupt_error = mark_corrupt(src); } if (prev_type != REC_TYPE_NORM) vstream_fputs(eol, dst); if (flags & MAIL_COPY_BLANK) vstream_fputs(eol, dst); } vstring_free(buf); /* * Make sure we read and wrote all. Truncate the file to its original * length when the delivery failed. POSIX does not require ftruncate(), * so we may have a portability problem. Note that fclose() may fail even * while fflush and fsync() succeed. Think of remote file systems such as * AFS that copy the file back to the server upon close. Oh well, no * point optimizing the error case. XXX On systems that use flock() * locking, we must truncate the file file before closing it (and losing * the exclusive lock). */ read_error = vstream_ferror(src); write_error = vstream_fflush(dst); #ifdef HAS_FSYNC if ((flags & MAIL_COPY_TOFILE) != 0) write_error |= fsync(vstream_fileno(dst)); #endif if (var_fault_inj_code == 2) { read_error = 1; errno = ENOENT; } if (var_fault_inj_code == 3) { write_error = 1; errno = ENOENT; } #ifndef NO_TRUNCATE if ((flags & MAIL_COPY_TOFILE) != 0) if (corrupt_error || read_error || write_error) /* Complain about ignored "undo" errors? So sue me. */ (void) ftruncate(vstream_fileno(dst), orig_length); #endif write_error |= vstream_fclose(dst); /* * Return the optional verbose error description. */ #define TRY_AGAIN_ERROR(errno) \ (errno == EAGAIN || errno == ESTALE) if (why && read_error) dsb_unix(why, TRY_AGAIN_ERROR(errno) ? "4.3.0" : "5.3.0", sys_exits_detail(EX_IOERR)->text, "error reading message: %m"); if (why && write_error) dsb_unix(why, mbox_dsn(errno, "5.3.0"), sys_exits_detail(EX_IOERR)->text, "error writing message: %m"); /* * Use flag+errno description when the optional verbose description is * not desired. */ return ((corrupt_error ? MAIL_COPY_STAT_CORRUPT : 0) | (read_error ? MAIL_COPY_STAT_READ : 0) | (write_error ? MAIL_COPY_STAT_WRITE : 0)); }
static int forward_send(FORWARD_INFO *info, DELIVER_REQUEST *request, DELIVER_ATTR attr, char *delivered) { const char *myname = "forward_send"; VSTRING *buffer = vstring_alloc(100); VSTRING *folded; int status; int rec_type = 0; /* * Start the message content segment. Prepend our Delivered-To: header to * the message data. Stop at the first error. XXX Rely on the front-end * services to enforce record size limits. */ rec_fputs(info->cleanup, REC_TYPE_MESG, ""); vstring_strcpy(buffer, delivered); rec_fprintf(info->cleanup, REC_TYPE_NORM, "Received: by %s (%s)", var_myhostname, var_mail_name); rec_fprintf(info->cleanup, REC_TYPE_NORM, "\tid %s; %s", info->queue_id, mail_date(info->posting_time.tv_sec)); if (local_deliver_hdr_mask & DELIVER_HDR_FWD) { folded = vstring_alloc(100); rec_fprintf(info->cleanup, REC_TYPE_NORM, "Delivered-To: %s", casefold(folded, (STR(buffer)))); vstring_free(folded); } if ((status = vstream_ferror(info->cleanup)) == 0) if (vstream_fseek(attr.fp, attr.offset, SEEK_SET) < 0) msg_fatal("%s: seek queue file %s: %m:", myname, VSTREAM_PATH(attr.fp)); while (status == 0 && (rec_type = rec_get(attr.fp, buffer, 0)) > 0) { if (rec_type != REC_TYPE_CONT && rec_type != REC_TYPE_NORM) break; status = (REC_PUT_BUF(info->cleanup, rec_type, buffer) != rec_type); } if (status == 0 && rec_type != REC_TYPE_XTRA) { msg_warn("%s: bad record type: %d in message content", info->queue_id, rec_type); status |= mark_corrupt(attr.fp); } /* * Send the end-of-data marker only when there were no errors. */ if (status == 0) { rec_fputs(info->cleanup, REC_TYPE_XTRA, ""); rec_fputs(info->cleanup, REC_TYPE_END, ""); } /* * Retrieve the cleanup service completion status only if there are no * problems. */ if (status == 0) if (vstream_fflush(info->cleanup) || attr_scan(info->cleanup, ATTR_FLAG_MISSING, RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), ATTR_TYPE_END) != 1) status = 1; /* * Log successful forwarding. * * XXX DSN alias and .forward expansion already report SUCCESS, so don't do * it again here. */ if (status == 0) { attr.rcpt.dsn_notify = (attr.rcpt.dsn_notify == DSN_NOTIFY_SUCCESS ? DSN_NOTIFY_NEVER : attr.rcpt.dsn_notify & ~DSN_NOTIFY_SUCCESS); dsb_update(attr.why, "2.0.0", "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY, "forwarded as %s", info->queue_id); status = sent(BOUNCE_FLAGS(request), SENT_ATTR(attr)); } /* * Cleanup. */ vstring_free(buffer); return (status); }