int memcache_fread(VSTREAM *stream, VSTRING *buf, ssize_t todo) { /* * Sanity check. */ if (todo < 0) msg_panic("memcache_fread: negative todo %ld", (long) todo); /* * Do the I/O. */ if (vstream_fread_buf(stream, buf, todo) != todo || VSTREAM_GETC(stream) != '\r' || VSTREAM_GETC(stream) != '\n') { if (msg_verbose) msg_info("%s read: error", VSTREAM_PATH(stream)); return (-1); } else { VSTRING_TERMINATE(buf); if (msg_verbose) msg_info("%s read: %s", VSTREAM_PATH(stream), STR(buf)); return (0); } }
void dict_load_fp(const char *dict_name, VSTREAM *fp) { const char *myname = "dict_load_fp"; VSTRING *buf; char *member; char *val; int lineno; const char *err; struct stat st; DICT *dict; /* * Instantiate the dictionary even if the file is empty. */ DICT_FIND_FOR_UPDATE(dict, dict_name); buf = vstring_alloc(100); lineno = 0; if (fstat(vstream_fileno(fp), &st) < 0) msg_fatal("fstat %s: %m", VSTREAM_PATH(fp)); while (readlline(buf, fp, &lineno)) { if ((err = split_nameval(STR(buf), &member, &val)) != 0) msg_fatal("%s, line %d: %s: \"%s\"", VSTREAM_PATH(fp), lineno, err, STR(buf)); if (msg_verbose > 1) msg_info("%s: %s = %s", myname, member, val); if (dict->update(dict, member, val) != 0) msg_fatal("%s, line %d: unable to update %s:%s", VSTREAM_PATH(fp), lineno, dict->type, dict->name); } vstring_free(buf); dict->owner.uid = st.st_uid; dict->owner.status = (st.st_uid != 0); }
static int attr_scan_plain_string(VSTREAM *fp, VSTRING *plain_buf, int terminator, const char *context) { #if 0 extern int var_line_limit; /* XXX */ int limit = var_line_limit * 4; #endif int ch; VSTRING_RESET(plain_buf); while ((ch = VSTREAM_GETC(fp)) != '\n' && (terminator == 0 || ch != terminator)) { if (ch == VSTREAM_EOF) { msg_warn("%s on %s while reading %s", vstream_ftimeout(fp) ? "timeout" : "premature end-of-input", VSTREAM_PATH(fp), context); return (-1); } VSTRING_ADDCH(plain_buf, ch); #if 0 if (LEN(plain_buf) > limit) { msg_warn("string length > %d characters from %s while reading %s", limit, VSTREAM_PATH(fp), context); return (-1); } #endif } VSTRING_TERMINATE(plain_buf); if (msg_verbose) msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)"); return (ch); }
int mark_corrupt(VSTREAM *src) { const char *myname = "mark_corrupt"; uid_t saved_uid; gid_t saved_gid; /* * If not running as the mail system, change privileges first. */ if ((saved_uid = geteuid()) != var_owner_uid) { saved_gid = getegid(); set_eugid(var_owner_uid, var_owner_gid); } /* * For now, the result value is -1; this may become a bit mask, or * something even more advanced than that, when the delivery status * becomes more than just done/deferred. */ msg_warn("corrupted queue file: %s", VSTREAM_PATH(src)); if (fchmod(vstream_fileno(src), MAIL_QUEUE_STAT_CORRUPT)) msg_fatal("%s: fchmod %s: %m", myname, VSTREAM_PATH(src)); /* * Restore privileges. */ if (saved_uid != var_owner_uid) set_eugid(saved_uid, saved_gid); return (DEL_STAT_DEFER); }
static int smtpd_proxy_replay_setup(SMTPD_STATE *state) { const char *myname = "smtpd_proxy_replay_setup"; off_t file_offs; /* * Where possible reuse an existing replay logfile, because creating a * file is expensive compared to reading or writing. For security reasons * we must truncate the file before reuse. For performance reasons we * should truncate the file immediately after the end of a mail * transaction. We enforce the security guarantee upon reuse, by * requiring that no I/O happened since the file was truncated. This is * less expensive than truncating the file redundantly. */ if (smtpd_proxy_replay_stream != 0) { /* vstream_ftell() won't invoke the kernel, so all errors are mine. */ if ((file_offs = vstream_ftell(smtpd_proxy_replay_stream)) != 0) msg_panic("%s: bad before-queue filter speed-adjust log offset %lu", myname, (unsigned long) file_offs); vstream_clearerr(smtpd_proxy_replay_stream); if (msg_verbose) msg_info("%s: reuse speed-adjust stream fd=%d", myname, vstream_fileno(smtpd_proxy_replay_stream)); /* Here, smtpd_proxy_replay_stream != 0 */ } /* * Create a new replay logfile. */ if (smtpd_proxy_replay_stream == 0) { smtpd_proxy_replay_stream = mail_queue_enter(MAIL_QUEUE_INCOMING, 0, (struct timeval *) 0); if (smtpd_proxy_replay_stream == 0) return (smtpd_proxy_replay_rdwr_error(state)); if (unlink(VSTREAM_PATH(smtpd_proxy_replay_stream)) < 0) msg_warn("remove before-queue filter speed-adjust log %s: %m", VSTREAM_PATH(smtpd_proxy_replay_stream)); if (msg_verbose) msg_info("%s: new speed-adjust stream fd=%d", myname, vstream_fileno(smtpd_proxy_replay_stream)); } /* * Needed by our DATA-phase record emulation routines. */ vstream_control(smtpd_proxy_replay_stream, VSTREAM_CTL_CONTEXT, (char *) state, VSTREAM_CTL_END); return (0); }
int memcache_printf(VSTREAM *stream, const char *fmt,...) { va_list ap; int ret; va_start(ap, fmt); if (msg_verbose) { VSTRING *buf = vstring_alloc(100); va_list ap2; VA_COPY(ap2, ap); vstring_vsprintf(buf, fmt, ap2); va_end(ap2); msg_info("%s write: %s", VSTREAM_PATH(stream), STR(buf)); vstring_free(buf); } /* * Do the I/O. */ ret = memcache_vprintf(stream, fmt, ap); va_end(ap); return (ret); }
static int deliver_message(DELIVER_REQUEST *request) { char *myname = "deliver_message"; VSTREAM *src; int result = 0; int status; RECIPIENT *rcpt; int nrcpt; if (msg_verbose) msg_info("deliver_message: from %s", request->sender); /* * Sanity checks. */ if (request->nexthop[0] == 0) msg_fatal("empty nexthop hostname"); if (request->rcpt_list.len <= 0) msg_fatal("recipient count: %d", request->rcpt_list.len); /* * Open the queue file. Opening the file can fail for a variety of * reasons, such as the system running out of resources. Instead of * throwing away mail, we're raising a fatal error which forces the mail * system to back off, and retry later. */ src = mail_queue_open(request->queue_name, request->queue_id, O_RDWR, 0); if (src == 0) msg_fatal("%s: open %s %s: %m", myname, request->queue_name, request->queue_id); if (msg_verbose) msg_info("%s: file %s", myname, VSTREAM_PATH(src)); /* * Bounce all recipients. */ #define BOUNCE_FLAGS(request) DEL_REQ_TRACE_FLAGS(request->flags) for (nrcpt = 0; nrcpt < request->rcpt_list.len; nrcpt++) { rcpt = request->rcpt_list.info + nrcpt; if (rcpt->offset >= 0) { status = bounce_append(BOUNCE_FLAGS(request), request->queue_id, rcpt->orig_addr, rcpt->address, rcpt->offset, "none", request->arrival_time, "%s", request->nexthop); if (status == 0) deliver_completed(src, rcpt->offset); result |= status; } } /* * Clean up. */ if (vstream_fclose(src)) msg_warn("close %s %s: %m", request->queue_name, request->queue_id); return (result); }
static int qmgr_deliver_initial_reply(VSTREAM *stream) { int stat; if (peekfd(vstream_fileno(stream)) < 0) { msg_warn("%s: premature disconnect", VSTREAM_PATH(stream)); return (DELIVER_STAT_CRASH); } else if (attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_INT, MAIL_ATTR_STATUS, &stat, ATTR_TYPE_END) != 1) { msg_warn("%s: malformed response", VSTREAM_PATH(stream)); return (DELIVER_STAT_CRASH); } else { return (stat ? DELIVER_STAT_DEFER : 0); } }
static int deliver_message(DELIVER_REQUEST *request) { const char *myname = "deliver_message"; VSTREAM *src; int result = 0; int status; RECIPIENT *rcpt; int nrcpt; DSN_SPLIT dp; DSN dsn; if (msg_verbose) msg_info("deliver_message: from %s", request->sender); /* * Sanity checks. */ if (request->nexthop[0] == 0) msg_fatal("empty nexthop hostname"); if (request->rcpt_list.len <= 0) msg_fatal("recipient count: %d", request->rcpt_list.len); /* * Open the queue file. Opening the file can fail for a variety of * reasons, such as the system running out of resources. Instead of * throwing away mail, we're raising a fatal error which forces the mail * system to back off, and retry later. */ src = mail_queue_open(request->queue_name, request->queue_id, O_RDWR, 0); if (src == 0) msg_fatal("%s: open %s %s: %m", myname, request->queue_name, request->queue_id); if (msg_verbose) msg_info("%s: file %s", myname, VSTREAM_PATH(src)); /* * Discard all recipients. */ #define BOUNCE_FLAGS(request) DEL_REQ_TRACE_FLAGS(request->flags) dsn_split(&dp, "2.0.0", request->nexthop); (void) DSN_SIMPLE(&dsn, DSN_STATUS(dp.dsn), dp.text); for (nrcpt = 0; nrcpt < request->rcpt_list.len; nrcpt++) { rcpt = request->rcpt_list.info + nrcpt; status = sent(BOUNCE_FLAGS(request), request->queue_id, &request->msg_stats, rcpt, "none", &dsn); if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS)) deliver_completed(src, rcpt->offset); result |= status; } /* * Clean up. */ if (vstream_fclose(src)) msg_warn("close %s %s: %m", request->queue_name, request->queue_id); return (result); }
int memcache_get(VSTREAM *stream, VSTRING *vp, ssize_t bound) { int last_char; int next_char; last_char = (bound == 0 ? vstring_get(vp, stream) : vstring_get_bound(vp, stream, bound)); switch (last_char) { /* * Do some repair in the rare case that we stopped reading in the * middle of the CRLF record terminator. */ case '\r': if ((next_char = VSTREAM_GETC(stream)) == '\n') { VSTRING_ADDCH(vp, '\n'); /* FALLTRHOUGH */ } else { if (next_char != VSTREAM_EOF) vstream_ungetc(stream, next_char); /* * Input too long, or EOF */ default: if (msg_verbose) msg_info("%s got %s", VSTREAM_PATH(stream), LEN(vp) < bound ? "EOF" : "input too long"); return (-1); } /* * Strip off the record terminator: either CRLF or just bare LF. */ case '\n': vstring_truncate(vp, VSTRING_LEN(vp) - 1); if (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r') vstring_truncate(vp, VSTRING_LEN(vp) - 1); VSTRING_TERMINATE(vp); if (msg_verbose) msg_info("%s got: %s", VSTREAM_PATH(stream), STR(vp)); return (0); } }
static int attr_scan0_string(VSTREAM *fp, VSTRING *plain_buf, const char *context) { int ch; if ((ch = vstring_get_null(plain_buf, fp)) == VSTREAM_EOF) { msg_warn("%s on %s while reading %s", vstream_ftimeout(fp) ? "timeout" : "premature end-of-input", VSTREAM_PATH(fp), context); return (-1); } if (ch != 0) { msg_warn("unexpected end-of-input from %s while reading %s", VSTREAM_PATH(fp), context); return (-1); } if (msg_verbose) msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)"); return (ch); }
void dict_load_fp(const char *dict_name, VSTREAM *fp) { const char *myname = "dict_load_fp"; VSTRING *buf; char *member; char *val; const char *old; int old_lineno; int lineno; const char *err; struct stat st; DICT *dict; /* * Instantiate the dictionary even if the file is empty. */ DICT_FIND_FOR_UPDATE(dict, dict_name); buf = vstring_alloc(100); old_lineno = lineno = 0; if (fstat(vstream_fileno(fp), &st) < 0) msg_fatal("fstat %s: %m", VSTREAM_PATH(fp)); for ( /* void */ ; readlline(buf, fp, &lineno); old_lineno = lineno) { if ((err = split_nameval(STR(buf), &member, &val)) != 0) msg_fatal("%s, line %s: %s: \"%s\"", VSTREAM_PATH(fp), format_line_number((VSTRING *) 0, old_lineno + 1, lineno), err, STR(buf)); if (msg_verbose > 1) msg_info("%s: %s = %s", myname, member, val); if ((old = dict->lookup(dict, member)) != 0 && strcmp(old, val) != 0) msg_warn("%s, line %d: overriding earlier entry: %s=%s", VSTREAM_PATH(fp), lineno, member, old); if (dict->update(dict, member, val) != 0) msg_fatal("%s, line %d: unable to update %s:%s", VSTREAM_PATH(fp), lineno, dict->type, dict->name); } vstring_free(buf); dict->owner.uid = st.st_uid; dict->owner.status = (st.st_uid != 0); }
void qmgr_message_kill_record(QMGR_MESSAGE *message, long offset) { if (offset <= 0) msg_panic("qmgr_message_kill_record: bad offset 0x%lx", offset); if (qmgr_message_open(message) || rec_put_type(message->fp, REC_TYPE_KILL, offset) < 0 || vstream_fflush(message->fp)) msg_fatal("update queue file %s: %m", VSTREAM_PATH(message->fp)); qmgr_message_close(message); }
VSTRING *readlline(VSTRING *buf, VSTREAM *fp, int *lineno) { int ch; int next; int start; char *cp; VSTRING_RESET(buf); /* * Ignore comment lines, all whitespace lines, and empty lines. Terminate * at EOF or at the beginning of the next logical line. */ for (;;) { /* Read one line, possibly not newline terminated. */ start = LEN(buf); while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF && ch != '\n') VSTRING_ADDCH(buf, ch); if (ch == '\n' && lineno != 0) *lineno += 1; /* Ignore comment line, all whitespace line, or empty line. */ for (cp = STR(buf) + start; cp < END(buf) && ISSPACE(*cp); cp++) /* void */ ; if (cp == END(buf) || *cp == '#') vstring_truncate(buf, start); /* Terminate at EOF or at the beginning of the next logical line. */ if (ch == VSTREAM_EOF) break; if (LEN(buf) > 0) { if ((next = VSTREAM_GETC(fp)) != VSTREAM_EOF) vstream_ungetc(fp, next); if (next != '#' && !ISSPACE(next)) break; } } VSTRING_TERMINATE(buf); /* * Invalid input: continuing text without preceding text. Allowing this * would complicate "postconf -e", which implements its own multi-line * parsing routine. Do not abort, just warn, so that critical programs * like postmap do not leave behind a truncated table. */ if (LEN(buf) > 0 && ISSPACE(*STR(buf))) { msg_warn("%s: logical line must not start with whitespace: \"%.30s%s\"", VSTREAM_PATH(fp), STR(buf), LEN(buf) > 30 ? "..." : ""); return (readlline(buf, fp, lineno)); } /* * Done. */ return (LEN(buf) > 0 ? buf : 0); }
static int flush_request_receive(VSTREAM *client_stream, VSTRING *request) { int count; /* * Kluge: choose the protocol depending on the request size. */ if (read_wait(vstream_fileno(client_stream), var_ipc_timeout) < 0) { msg_warn("timeout while waiting for data from %s", VSTREAM_PATH(client_stream)); return (-1); } if ((count = peekfd(vstream_fileno(client_stream))) < 0) { msg_warn("cannot examine read buffer of %s: %m", VSTREAM_PATH(client_stream)); return (-1); } /* * Short request: master trigger. Use the string+null protocol. */ if (count <= 2) { if (vstring_get_null(request, client_stream) == VSTREAM_EOF) { msg_warn("end-of-input while reading request from %s: %m", VSTREAM_PATH(client_stream)); return (-1); } } /* * Long request: real flush client. Use the attribute list protocol. */ else { if (attr_scan(client_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_REQ, request), ATTR_TYPE_END) != 1) { return (-1); } } return (0); }
static int attr_scan_plain_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf, int terminator, const char *context) { char junk = 0; int ch; if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0) return (-1); if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) { msg_warn("malformed numerical data from %s while reading %s: %.100s", VSTREAM_PATH(fp), context, STR(str_buf)); return (-1); } return (ch); }
void qmgr_message_update_warn(QMGR_MESSAGE *message) { /* * XXX eventually this should let us schedule multiple warnings, right * now it just allows for one. */ if (qmgr_message_open(message) || vstream_fseek(message->fp, message->warn_offset, SEEK_SET) < 0 || rec_fprintf(message->fp, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT, REC_TYPE_WARN_ARG(0)) < 0 || vstream_fflush(message->fp)) msg_fatal("update queue file %s: %m", VSTREAM_PATH(message->fp)); qmgr_message_close(message); }
void qmgr_message_update_warn(QMGR_MESSAGE *message) { /* * After the "mail delayed" warning, optionally send a "delay cleared" * notification. */ if (qmgr_message_open(message) || vstream_fseek(message->fp, message->warn_offset, SEEK_SET) < 0 || rec_fprintf(message->fp, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT, REC_TYPE_WARN_ARG(-1)) < 0 || vstream_fflush(message->fp)) msg_fatal("update queue file %s: %m", VSTREAM_PATH(message->fp)); qmgr_message_close(message); }
DELIVERED_HDR_INFO *delivered_hdr_init(VSTREAM *fp, off_t offset, int flags) { char *cp; DELIVERED_HDR_INFO *info; const HEADER_OPTS *hdr; /* * Sanity check. */ info = (DELIVERED_HDR_INFO *) mymalloc(sizeof(*info)); info->flags = flags; info->buf = vstring_alloc(10); info->table = htable_create(0); if (vstream_fseek(fp, offset, SEEK_SET) < 0) msg_fatal("seek queue file %s: %m", VSTREAM_PATH(fp)); /* * XXX Assume that mail_copy() produces delivered-to headers that fit in * a REC_TYPE_NORM record. Lowercase the delivered-to addresses for * consistency. * * XXX Don't get bogged down by gazillions of delivered-to headers. */ #define DELIVERED_HDR_LIMIT 1000 while (rec_get(fp, info->buf, 0) == REC_TYPE_NORM && info->table->used < DELIVERED_HDR_LIMIT) { if (is_header(STR(info->buf))) { if ((hdr = header_opts_find(STR(info->buf))) != 0 && hdr->type == HDR_DELIVERED_TO) { cp = STR(info->buf) + strlen(hdr->name) + 1; while (ISSPACE(*cp)) cp++; if (info->flags & FOLD_ADDR_ALL) fold_addr(cp, info->flags); if (msg_verbose) msg_info("delivered_hdr_init: %s", cp); htable_enter(info->table, cp, (char *) 0); } } else if (ISSPACE(STR(info->buf)[0])) { continue; } else { break; } } return (info); }
static int attr_scan0_data(VSTREAM *fp, VSTRING *str_buf, const char *context) { static VSTRING *base64_buf = 0; int ch; if (base64_buf == 0) base64_buf = vstring_alloc(10); if ((ch = attr_scan0_string(fp, base64_buf, context)) < 0) return (-1); if (base64_decode(str_buf, STR(base64_buf), LEN(base64_buf)) == 0) { msg_warn("malformed base64 data from %s while reading %s: %.100s", VSTREAM_PATH(fp), context, STR(base64_buf)); return (-1); } return (ch); }
int memcache_fwrite(VSTREAM *stream, const char *cp, ssize_t todo) { /* * Sanity check. */ if (todo < 0) msg_panic("memcache_fwrite: negative todo %ld", (long) todo); /* * Do the I/O. */ if (msg_verbose) msg_info("%s write: %.*s", VSTREAM_PATH(stream), (int) todo, cp); if (vstream_fwrite(stream, cp, todo) != todo || vstream_fputs("\r\n", stream) == VSTREAM_EOF) return (-1); else return (0); }
void deliver_attr_dump(DELIVER_ATTR *attrp) { msg_info("level: %d", attrp->level); msg_info("path: %s", VSTREAM_PATH(attrp->fp)); msg_info("fp: 0x%lx", (long) attrp->fp); msg_info("queue_name: %s", attrp->queue_name ? attrp->queue_name : "null"); msg_info("queue_id: %s", attrp->queue_id ? attrp->queue_id : "null"); msg_info("offset: %ld", attrp->rcpt.offset); msg_info("sender: %s", attrp->sender ? attrp->sender : "null"); msg_info("recipient: %s", attrp->rcpt.address ? attrp->rcpt.address : "null"); msg_info("domain: %s", attrp->domain ? attrp->domain : "null"); msg_info("local: %s", attrp->local ? attrp->local : "null"); msg_info("user: %s", attrp->user ? attrp->user : "******"); msg_info("extension: %s", attrp->extension ? attrp->extension : "null"); msg_info("unmatched: %s", attrp->unmatched ? attrp->unmatched : "null"); msg_info("owner: %s", attrp->owner ? attrp->owner : "null"); msg_info("delivered: %s", attrp->delivered ? attrp->delivered : "null"); msg_info("relay: %s", attrp->relay ? attrp->relay : "null"); msg_info("exp_type: %d", attrp->exp_type); msg_info("exp_from: %s", attrp->exp_from ? attrp->exp_from : "null"); msg_info("why: %s", attrp->why ? "buffer" : "null"); }
static void cleanup_service(VSTREAM *src, char *unused_service, char **argv) { VSTRING *buf = vstring_alloc(100); CLEANUP_STATE *state; int flags; int type = 0; int status; /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * Open a queue file and initialize state. */ state = cleanup_open(src); /* * Send the queue id to the client. Read client processing options. If we * can't read the client processing options we can pretty much forget * about the whole operation. */ attr_print(src, ATTR_FLAG_NONE, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, state->queue_id, ATTR_TYPE_END); if (attr_scan(src, ATTR_FLAG_STRICT, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &flags, ATTR_TYPE_END) != 1) { state->errs |= CLEANUP_STAT_BAD; flags = 0; } cleanup_control(state, flags); /* * XXX Rely on the front-end programs to enforce record size limits. * * First, copy the envelope records to the queue file. Then, copy the * message content (headers and body). Finally, attach any information * extracted from message headers. */ while (CLEANUP_OUT_OK(state)) { if ((type = rec_get_raw(src, buf, 0, REC_FLAG_NONE)) < 0) { state->errs |= CLEANUP_STAT_BAD; break; } if (REC_GET_HIDDEN_TYPE(type)) { msg_warn("%s: record type %d not allowed - discarding this message", state->queue_id, type); state->errs |= CLEANUP_STAT_BAD; break; } CLEANUP_RECORD(state, type, vstring_str(buf), VSTRING_LEN(buf)); if (type == REC_TYPE_END) break; } /* * Keep reading in case of problems, until the sender is ready to receive * our status report. */ if (CLEANUP_OUT_OK(state) == 0 && type > 0) { while (type != REC_TYPE_END && (type = rec_get(src, buf, 0)) > 0) /* void */ ; } /* * Log something to make timeout errors easier to debug. */ if (vstream_ftimeout(src)) msg_warn("%s: read timeout on %s", state->queue_id, VSTREAM_PATH(src)); /* * Finish this message, and report the result status to the client. */ status = cleanup_flush(state); /* in case state is modified */ attr_print(src, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_STR, MAIL_ATTR_WHY, (state->flags & CLEANUP_FLAG_SMTP_REPLY) && state->smtp_reply ? state->smtp_reply : state->reason ? state->reason : "", ATTR_TYPE_END); cleanup_free(state); /* * Cleanup. */ vstring_free(buf); }
QMGR_MESSAGE *qmgr_message_alloc(const char *queue_name, const char *queue_id, int qflags, mode_t mode) { const char *myname = "qmgr_message_alloc"; QMGR_MESSAGE *message; if (msg_verbose) msg_info("%s: %s %s", myname, queue_name, queue_id); /* * Create an in-core message structure. */ message = qmgr_message_create(queue_name, queue_id, qflags); /* * Extract message envelope information: time of arrival, sender address, * recipient addresses. Skip files with malformed envelope information. */ #define QMGR_LOCK_MODE (MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) if (qmgr_message_open(message) < 0) { qmgr_message_free(message); return (0); } if (myflock(vstream_fileno(message->fp), INTERNAL_LOCK, QMGR_LOCK_MODE) < 0) { msg_info("%s: skipped, still being delivered", queue_id); qmgr_message_close(message); qmgr_message_free(message); return (QMGR_MESSAGE_LOCKED); } if (qmgr_message_read(message) < 0) { qmgr_message_close(message); qmgr_message_free(message); return (0); } else { /* * We have validated the queue file content, so it is safe to modify * the file properties now. */ if (mode != 0 && fchmod(vstream_fileno(message->fp), mode) < 0) msg_fatal("fchmod %s: %m", VSTREAM_PATH(message->fp)); /* * Reset the defer log. This code should not be here, but we must * reset the defer log *after* acquiring the exclusive lock on the * queue file and *before* resolving new recipients. Since all those * operations are encapsulated so nicely by this routine, the defer * log reset has to be done here as well. * * Note: it is safe to remove the defer logfile from a previous queue * run of this queue file, because the defer log contains information * about recipients that still exist in this queue file. */ if (mail_queue_remove(MAIL_QUEUE_DEFER, queue_id) && errno != ENOENT) msg_fatal("%s: %s: remove %s %s: %m", myname, queue_id, MAIL_QUEUE_DEFER, queue_id); qmgr_message_sort(message); qmgr_message_resolve(message); qmgr_message_sort(message); qmgr_message_assign(message); qmgr_message_close(message); if (message->rcpt_offset == 0) qmgr_message_move_limits(message); return (message); } }
MILTERS *milter_receive(VSTREAM *stream, int count) { MILTERS *milters; MILTER *head = 0; MILTER *tail = 0; MILTER *milter = 0; if (msg_verbose) msg_info("receive %d milters", count); /* * XXX We must instantiate a MILTERS structure even when the sender has * no active filters, otherwise the cleanup server would try to use its * own non_smtpd_milters settings. */ #define NO_MILTERS ((char *) 0) #define NO_TIMEOUTS 0, 0, 0 #define NO_PROTOCOL ((char *) 0) #define NO_ACTION ((char *) 0) #define NO_MACROS ((MILTER_MACROS *) 0) milters = milter_new(NO_MILTERS, NO_TIMEOUTS, NO_PROTOCOL, NO_ACTION, NO_MACROS); /* * XXX Optimization: don't send or receive further information when there * aren't any active filters. */ if (count <= 0) return (milters); /* * Receive the global macro name lists. */ milters->macros = milter_macros_alloc(MILTER_MACROS_ALLOC_ZERO); if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, ATTR_TYPE_FUNC, milter_macros_scan, (void *) milters->macros, ATTR_TYPE_END) != 1) { milter_free(milters); return (0); } /* * Receive the filters. */ for (; count > 0; count--) { if ((milter = milter8_receive(stream, milters)) == 0) { msg_warn("cannot receive milters via service %s socket", VSTREAM_PATH(stream)); milter_free(milters); return (0); } if (head == 0) { /* Coverity: milter_free() depends on milters->milter_list. */ milters->milter_list = head = milter; } else { tail->next = milter; } tail = milter; } /* * Over to you. */ (void) attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, 0, ATTR_TYPE_END); return (milters); }
int milter_send(MILTERS *milters, VSTREAM *stream) { MILTER *m; int status = 0; int count = 0; /* * XXX Optimization: send only the filters that are actually used in the * remote process. No point sending a filter that looks at HELO commands * to a cleanup server. For now we skip only the filters that are known * to be disabled (either in global error state or in global accept * state). * * XXX We must send *some* information, even when there are no active * filters, otherwise the cleanup server would try to apply its own * non_smtpd_milters settings. */ if (milters != 0) for (m = milters->milter_list; m != 0; m = m->next) if (m->active(m)) count++; (void) rec_fprintf(stream, REC_TYPE_MILT_COUNT, "%d", count); if (msg_verbose) msg_info("send %d milters", count); /* * XXX Optimization: don't send or receive further information when there * aren't any active filters. */ if (count <= 0) return (0); /* * Send the filter macro name lists. */ (void) attr_print(stream, ATTR_FLAG_MORE, ATTR_TYPE_FUNC, milter_macros_print, (void *) milters->macros, ATTR_TYPE_END); /* * Send the filter instances. */ for (m = milters->milter_list; m != 0; m = m->next) if (m->active(m) && (status = m->send(m, stream)) != 0) break; /* * Over to you. */ if (status != 0 || attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status, ATTR_TYPE_END) != 1 || status != 0) { msg_warn("cannot send milters to service %s", VSTREAM_PATH(stream)); return (-1); } return (0); }
int deliver_command(LOCAL_STATE state, USER_ATTR usr_attr, const char *command) { const char *myname = "deliver_command"; DSN_BUF *why = state.msg_attr.why; int cmd_status; int deliver_status; ARGV *env; int copy_flags; char **cpp; char *cp; ARGV *export_env; VSTRING *exec_dir; int expand_status; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE ELIMINATION * * Skip this command if it was already delivered to as this user. */ if (been_here(state.dup_filter, "command %s:%ld %s", state.msg_attr.user, (long) usr_attr.uid, command)) return (0); /* * Don't deliver a trace-only request. */ if (DEL_REQ_TRACE_ONLY(state.request->flags)) { dsb_simple(why, "2.0.0", "delivers to command: %s", command); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * DELIVERY RIGHTS * * Choose a default uid and gid when none have been selected (i.e. values * are still zero). */ if (usr_attr.uid == 0 && (usr_attr.uid = var_default_uid) == 0) msg_panic("privileged default user id"); if (usr_attr.gid == 0 && (usr_attr.gid = var_default_gid) == 0) msg_panic("privileged default group id"); /* * Deliver. */ copy_flags = MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH | MAIL_COPY_ORIG_RCPT; if (local_deliver_hdr_mask & DELIVER_HDR_CMD) copy_flags |= MAIL_COPY_DELIVERED; if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("%s: seek queue file %s: %m", myname, VSTREAM_PATH(state.msg_attr.fp)); /* * Pass additional environment information. XXX This should be * configurable. However, passing untrusted information via environment * parameters opens up a whole can of worms. Lesson from web servers: * don't let any network data even near a shell. It causes trouble. */ env = argv_alloc(1); if (usr_attr.home) argv_add(env, "HOME", usr_attr.home, ARGV_END); argv_add(env, "LOGNAME", state.msg_attr.user, "USER", state.msg_attr.user, "SENDER", state.msg_attr.sender, "RECIPIENT", state.msg_attr.rcpt.address, "LOCAL", state.msg_attr.local, ARGV_END); if (usr_attr.shell) argv_add(env, "SHELL", usr_attr.shell, ARGV_END); if (state.msg_attr.domain) argv_add(env, "DOMAIN", state.msg_attr.domain, ARGV_END); if (state.msg_attr.extension) argv_add(env, "EXTENSION", state.msg_attr.extension, ARGV_END); if (state.msg_attr.rcpt.orig_addr && state.msg_attr.rcpt.orig_addr[0]) argv_add(env, "ORIGINAL_RECIPIENT", state.msg_attr.rcpt.orig_addr, ARGV_END); #define EXPORT_REQUEST(name, value) \ if ((value)[0]) argv_add(env, (name), (value), ARGV_END); EXPORT_REQUEST("CLIENT_HOSTNAME", state.msg_attr.request->client_name); EXPORT_REQUEST("CLIENT_ADDRESS", state.msg_attr.request->client_addr); EXPORT_REQUEST("CLIENT_HELO", state.msg_attr.request->client_helo); EXPORT_REQUEST("CLIENT_PROTOCOL", state.msg_attr.request->client_proto); EXPORT_REQUEST("SASL_METHOD", state.msg_attr.request->sasl_method); EXPORT_REQUEST("SASL_SENDER", state.msg_attr.request->sasl_sender); EXPORT_REQUEST("SASL_USERNAME", state.msg_attr.request->sasl_username); argv_terminate(env); /* * Censor out undesirable characters from exported data. */ for (cpp = env->argv; *cpp; cpp += 2) for (cp = cpp[1]; *(cp += strspn(cp, var_cmd_exp_filter)) != 0;) *cp++ = '_'; /* * Evaluate the command execution directory. Defer delivery if expansion * fails. */ export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ); exec_dir = vstring_alloc(10); expand_status = local_expand(exec_dir, var_exec_directory, &state, &usr_attr, var_exec_exp_filter); if (expand_status & MAC_PARSE_ERROR) { cmd_status = PIPE_STAT_DEFER; dsb_simple(why, "4.3.5", "mail system configuration error"); msg_warn("bad parameter value syntax for %s: %s", VAR_EXEC_DIRECTORY, var_exec_directory); } else { cmd_status = pipe_command(state.msg_attr.fp, why, PIPE_CMD_UID, usr_attr.uid, PIPE_CMD_GID, usr_attr.gid, PIPE_CMD_COMMAND, command, PIPE_CMD_COPY_FLAGS, copy_flags, PIPE_CMD_SENDER, state.msg_attr.sender, PIPE_CMD_ORIG_RCPT, state.msg_attr.rcpt.orig_addr, PIPE_CMD_DELIVERED, state.msg_attr.delivered, PIPE_CMD_TIME_LIMIT, var_command_maxtime, PIPE_CMD_ENV, env->argv, PIPE_CMD_EXPORT, export_env->argv, PIPE_CMD_SHELL, var_local_cmd_shell, PIPE_CMD_CWD, *STR(exec_dir) ? STR(exec_dir) : (char *) 0, PIPE_CMD_END); } vstring_free(exec_dir); argv_free(export_env); argv_free(env); /* * Depending on the result, bounce or defer the message. */ switch (cmd_status) { case PIPE_STAT_OK: dsb_simple(why, "2.0.0", "delivered to command: %s", command); deliver_status = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); break; case PIPE_STAT_BOUNCE: case PIPE_STAT_DEFER: /* Account for possible owner- sender address override. */ deliver_status = bounce_workaround(state); break; case PIPE_STAT_CORRUPT: deliver_status = DEL_STAT_DEFER; break; default: msg_panic("%s: bad status %d", myname, cmd_status); /* NOTREACHED */ } return (deliver_status); }
static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) { const char *myname = "deliver_mailbox_file"; DSN_BUF *why = state.msg_attr.why; MBOX *mp; int mail_copy_status; int deliver_status; int copy_flags; long end; struct stat st; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * Don't deliver trace-only requests. */ if (DEL_REQ_TRACE_ONLY(state.request->flags)) { dsb_simple(why, "2.0.0", "delivers to mailbox"); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * Initialize. Assume the operation will fail. Set the delivered * attribute to reflect the final recipient. */ if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); state.msg_attr.delivered = state.msg_attr.rcpt.address; mail_copy_status = MAIL_COPY_STAT_WRITE; /* * Lock the mailbox and open/create the mailbox file. * * Write the file as the recipient, so that file quota work. */ copy_flags = MAIL_COPY_MBOX; set_eugid(usr_attr.uid, usr_attr.gid); mp = mbox_open(usr_attr.mailbox, O_APPEND | O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR, &st, -1, -1, virtual_mbox_lock_mask, "4.2.0", why); if (mp != 0) { if (S_ISREG(st.st_mode) == 0) { vstream_fclose(mp->fp); msg_warn("recipient %s: destination %s is not a regular file", state.msg_attr.rcpt.address, usr_attr.mailbox); dsb_simple(why, "5.3.5", "mail system configuration error"); } else if (var_strict_mbox_owner && st.st_uid != usr_attr.uid) { vstream_fclose(mp->fp); dsb_simple(why, "4.2.0", "destination %s is not owned by recipient", usr_attr.mailbox); msg_warn("specify \"%s = no\" to ignore mailbox ownership mismatch", VAR_STRICT_MBOX_OWNER); } else { end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END); mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, copy_flags, "\n", why); } mbox_release(mp); } set_eugid(var_owner_uid, var_owner_gid); /* * As the mail system, bounce, defer delivery, or report success. */ if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { deliver_status = DEL_STAT_DEFER; } else if (mail_copy_status != 0) { vstring_sprintf_prepend(why->reason, "delivery failed to mailbox %s: ", usr_attr.mailbox); deliver_status = (STR(why->status)[0] == '4' ? defer_append : bounce_append) (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { dsb_simple(why, "2.0.0", "delivered to mailbox"); deliver_status = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); } return (deliver_status); }
static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message) { VSTRING *buf; long orig_offset, extra_offset; int rec_type; char *start; /* * Initialize. No early returns or we have a memory leak. */ buf = vstring_alloc(100); if ((orig_offset = vstream_ftell(message->fp)) < 0) msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); /* * Rewind to the very beginning to make sure we see all records. */ if (vstream_fseek(message->fp, 0, SEEK_SET) < 0) msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); /* * Scan through the old style queue file. Count the total number of * recipients and find the data/extra sections offsets. Note that the new * queue files require that data_size equals extra_offset - data_offset, * so we set data_size to this as well and ignore the size record itself * completely. */ message->rcpt_unread = 0; for (;;) { rec_type = rec_get(message->fp, buf, 0); if (rec_type <= 0) /* Report missing end record later. */ break; start = vstring_str(buf); if (msg_verbose > 1) msg_info("old-style scan record %c %s", rec_type, start); if (rec_type == REC_TYPE_END) break; if (rec_type == REC_TYPE_DONE || rec_type == REC_TYPE_RCPT || rec_type == REC_TYPE_DRCP) { message->rcpt_unread++; continue; } if (rec_type == REC_TYPE_MESG) { if (message->data_offset == 0) { if ((message->data_offset = vstream_ftell(message->fp)) < 0) msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); if ((extra_offset = atol(start)) <= message->data_offset) msg_fatal("bad extra offset %s file %s", start, VSTREAM_PATH(message->fp)); if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0) msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); message->data_size = extra_offset - message->data_offset; } continue; } } /* * Clean up. */ if (vstream_fseek(message->fp, orig_offset, SEEK_SET) < 0) msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); vstring_free(buf); /* * Sanity checks. Verify that all required information was found, * including the queue file end marker. */ if (message->data_offset == 0 || rec_type != REC_TYPE_END) msg_fatal("%s: envelope records out of order", message->queue_id); }
static int qmgr_message_read(QMGR_MESSAGE *message) { VSTRING *buf; int rec_type; long curr_offset; long save_offset = message->rcpt_offset; /* save a flag */ int save_unread = message->rcpt_unread; /* save a count */ char *start; int recipient_limit; const char *error_text; char *name; char *value; char *orig_rcpt = 0; int count; int dsn_notify = 0; char *dsn_orcpt = 0; int n; int have_log_client_attr = 0; /* * Initialize. No early returns or we have a memory leak. */ buf = vstring_alloc(100); /* * If we re-open this file, skip over on-file recipient records that we * already looked at, and refill the in-core recipient address list. * * For the first time, the message recipient limit is calculated from the * global recipient limit. This is to avoid reading little recipients * when the active queue is near empty. When the queue becomes full, only * the necessary amount is read in core. Such priming is necessary * because there are no message jobs yet. * * For the next time, the recipient limit is based solely on the message * jobs' positions in the job lists and/or job stacks. */ if (message->rcpt_offset) { if (message->rcpt_list.len) msg_panic("%s: recipient list not empty on recipient reload", message->queue_id); if (vstream_fseek(message->fp, message->rcpt_offset, SEEK_SET) < 0) msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); message->rcpt_offset = 0; recipient_limit = message->rcpt_limit - message->rcpt_count; } else { recipient_limit = var_qmgr_rcpt_limit - qmgr_recipient_count; if (recipient_limit < message->rcpt_limit) recipient_limit = message->rcpt_limit; } /* Keep interrupt latency in check. */ if (recipient_limit > 5000) recipient_limit = 5000; if (recipient_limit <= 0) msg_panic("%s: no recipient slots available", message->queue_id); if (msg_verbose) msg_info("%s: recipient limit %d", message->queue_id, recipient_limit); /* * Read envelope records. XXX Rely on the front-end programs to enforce * record size limits. Read up to recipient_limit recipients from the * queue file, to protect against memory exhaustion. Recipient records * may appear before or after the message content, so we keep reading * from the queue file until we have enough recipients (rcpt_offset != 0) * and until we know all the non-recipient information. * * Note that the total recipient count record is accurate only for fresh * queue files. After some of the recipients are marked as done and the * queue file is deferred, it can be used as upper bound estimate only. * Fortunately, this poses no major problem on the scheduling algorithm, * as the only impact is that the already deferred messages are not * chosen by qmgr_job_candidate() as often as they could. * * On the first open, we must examine all non-recipient records. * * Optimization: when we know that recipient records are not mixed with * non-recipient records, as is typical with mailing list mail, then we * can avoid having to examine all the queue file records before we can * start deliveries. This avoids some file system thrashing with huge * mailing lists. */ for (;;) { if ((curr_offset = vstream_ftell(message->fp)) < 0) msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); if (curr_offset == message->data_offset && curr_offset > 0) { if (vstream_fseek(message->fp, message->data_size, SEEK_CUR) < 0) msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); curr_offset += message->data_size; } rec_type = rec_get_raw(message->fp, buf, 0, REC_FLAG_NONE); start = vstring_str(buf); if (msg_verbose > 1) msg_info("record %c %s", rec_type, start); if (rec_type == REC_TYPE_PTR) { if ((rec_type = rec_goto(message->fp, start)) == REC_TYPE_ERROR) break; /* Need to update curr_offset after pointer jump. */ continue; } if (rec_type <= 0) { msg_warn("%s: message rejected: missing end record", message->queue_id); break; } if (rec_type == REC_TYPE_END) { message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT; break; } /* * Map named attributes to pseudo record types, so that we don't have * to pollute the queue file with records that are incompatible with * past Postfix versions. Preferably, people should be able to back * out from an upgrade without losing mail. */ if (rec_type == REC_TYPE_ATTR) { if ((error_text = split_nameval(start, &name, &value)) != 0) { msg_warn("%s: ignoring bad attribute: %s: %.200s", message->queue_id, error_text, start); rec_type = REC_TYPE_ERROR; break; } if ((n = rec_attr_map(name)) != 0) { start = value; rec_type = n; } } /* * Process recipient records. */ if (rec_type == REC_TYPE_RCPT) { /* See also below for code setting orig_rcpt etc. */ if (message->rcpt_offset == 0) { message->rcpt_unread--; recipient_list_add(&message->rcpt_list, curr_offset, dsn_orcpt ? dsn_orcpt : "", dsn_notify ? dsn_notify : 0, orig_rcpt ? orig_rcpt : "", start); if (dsn_orcpt) { myfree(dsn_orcpt); dsn_orcpt = 0; } if (orig_rcpt) { myfree(orig_rcpt); orig_rcpt = 0; } if (dsn_notify) dsn_notify = 0; if (message->rcpt_list.len >= recipient_limit) { if ((message->rcpt_offset = vstream_ftell(message->fp)) < 0) msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); if (message->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT) /* We already examined all non-recipient records. */ break; if (message->rflags & QMGR_READ_FLAG_MIXED_RCPT_OTHER) /* Examine all remaining non-recipient records. */ continue; /* Optimizations for "pure recipient" record sections. */ if (curr_offset > message->data_offset) { /* We already examined all non-recipient records. */ message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT; break; } /* Examine non-recipient records in extracted segment. */ if (vstream_fseek(message->fp, message->data_offset + message->data_size, SEEK_SET) < 0) msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); continue; } } continue; } if (rec_type == REC_TYPE_DONE || rec_type == REC_TYPE_DRCP) { if (message->rcpt_offset == 0) { message->rcpt_unread--; if (dsn_orcpt) { myfree(dsn_orcpt); dsn_orcpt = 0; } if (orig_rcpt) { myfree(orig_rcpt); orig_rcpt = 0; } if (dsn_notify) dsn_notify = 0; } continue; } if (rec_type == REC_TYPE_DSN_ORCPT) { /* See also above for code clearing dsn_orcpt. */ if (dsn_orcpt != 0) { msg_warn("%s: ignoring out-of-order DSN original recipient address <%.200s>", message->queue_id, dsn_orcpt); myfree(dsn_orcpt); dsn_orcpt = 0; } if (message->rcpt_offset == 0) dsn_orcpt = mystrdup(start); continue; } if (rec_type == REC_TYPE_DSN_NOTIFY) { /* See also above for code clearing dsn_notify. */ if (dsn_notify != 0) { msg_warn("%s: ignoring out-of-order DSN notify flags <%d>", message->queue_id, dsn_notify); dsn_notify = 0; } if (message->rcpt_offset == 0) { if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_NOTIFY_OK(n)) msg_warn("%s: ignoring malformed DSN notify flags <%.200s>", message->queue_id, start); else dsn_notify = n; continue; } } if (rec_type == REC_TYPE_ORCP) { /* See also above for code clearing orig_rcpt. */ if (orig_rcpt != 0) { msg_warn("%s: ignoring out-of-order original recipient <%.200s>", message->queue_id, orig_rcpt); myfree(orig_rcpt); orig_rcpt = 0; } if (message->rcpt_offset == 0) orig_rcpt = mystrdup(start); continue; } /* * Process non-recipient records. */ if (message->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT) /* We already examined all non-recipient records. */ continue; if (rec_type == REC_TYPE_SIZE) { if (message->data_offset == 0) { if ((count = sscanf(start, "%ld %ld %d %d %ld %d", &message->data_size, &message->data_offset, &message->rcpt_unread, &message->rflags, &message->cont_length, &message->smtputf8)) >= 3) { /* Postfix >= 1.0 (a.k.a. 20010228). */ if (message->data_offset <= 0 || message->data_size <= 0) { msg_warn("%s: invalid size record: %.100s", message->queue_id, start); rec_type = REC_TYPE_ERROR; break; } if (message->rflags & ~QMGR_READ_FLAG_USER) { msg_warn("%s: invalid flags in size record: %.100s", message->queue_id, start); rec_type = REC_TYPE_ERROR; break; } } else if (count == 1) { /* Postfix < 1.0 (a.k.a. 20010228). */ qmgr_message_oldstyle_scan(message); } else { /* Can't happen. */ msg_warn("%s: message rejected: weird size record", message->queue_id); rec_type = REC_TYPE_ERROR; break; } } /* Postfix < 2.4 compatibility. */ if (message->cont_length == 0) { message->cont_length = message->data_size; } else if (message->cont_length < 0) { msg_warn("%s: invalid size record: %.100s", message->queue_id, start); rec_type = REC_TYPE_ERROR; break; } continue; } if (rec_type == REC_TYPE_TIME) { if (message->arrival_time.tv_sec == 0) REC_TYPE_TIME_SCAN(start, message->arrival_time); continue; } if (rec_type == REC_TYPE_CTIME) { if (message->create_time == 0) message->create_time = atol(start); continue; } if (rec_type == REC_TYPE_FILT) { if (message->filter_xport != 0) myfree(message->filter_xport); message->filter_xport = mystrdup(start); continue; } if (rec_type == REC_TYPE_INSP) { if (message->inspect_xport != 0) myfree(message->inspect_xport); message->inspect_xport = mystrdup(start); continue; } if (rec_type == REC_TYPE_RDR) { if (message->redirect_addr != 0) myfree(message->redirect_addr); message->redirect_addr = mystrdup(start); continue; } if (rec_type == REC_TYPE_FROM) { if (message->sender == 0) { message->sender = mystrdup(start); opened(message->queue_id, message->sender, message->cont_length, message->rcpt_unread, "queue %s", message->queue_name); } continue; } if (rec_type == REC_TYPE_DSN_ENVID) { if (message->dsn_envid == 0) message->dsn_envid = mystrdup(start); } if (rec_type == REC_TYPE_DSN_RET) { if (message->dsn_ret == 0) { if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_RET_OK(n)) msg_warn("%s: ignoring malformed DSN RET flags in queue file record:%.100s", message->queue_id, start); else message->dsn_ret = n; } } if (rec_type == REC_TYPE_ATTR) { /* Allow extra segment to override envelope segment info. */ if (strcmp(name, MAIL_ATTR_ENCODING) == 0) { if (message->encoding != 0) myfree(message->encoding); message->encoding = mystrdup(value); } /* * Backwards compatibility. Before Postfix 2.3, the logging * attributes were called client_name, etc. Now they are called * log_client_name. etc., and client_name is used for the actual * client information. To support old queue files we accept both * names for the purpose of logging; the new name overrides the * old one. * * XXX Do not use the "legacy" client_name etc. attribute values for * initializing the logging attributes, when this file already * contains the "modern" log_client_name etc. logging attributes. * Otherwise, logging attributes that are not present in the * queue file would be set with information from the real client. */ else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_NAME) == 0) { if (have_log_client_attr == 0 && message->client_name == 0) message->client_name = mystrdup(value); } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_ADDR) == 0) { if (have_log_client_attr == 0 && message->client_addr == 0) message->client_addr = mystrdup(value); } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_PORT) == 0) { if (have_log_client_attr == 0 && message->client_port == 0) message->client_port = mystrdup(value); } else if (strcmp(name, MAIL_ATTR_ACT_PROTO_NAME) == 0) { if (have_log_client_attr == 0 && message->client_proto == 0) message->client_proto = mystrdup(value); } else if (strcmp(name, MAIL_ATTR_ACT_HELO_NAME) == 0) { if (have_log_client_attr == 0 && message->client_helo == 0) message->client_helo = mystrdup(value); } /* Original client attributes. */ else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_NAME) == 0) { if (message->client_name != 0) myfree(message->client_name); message->client_name = mystrdup(value); have_log_client_attr = 1; } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_ADDR) == 0) { if (message->client_addr != 0) myfree(message->client_addr); message->client_addr = mystrdup(value); have_log_client_attr = 1; } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_PORT) == 0) { if (message->client_port != 0) myfree(message->client_port); message->client_port = mystrdup(value); have_log_client_attr = 1; } else if (strcmp(name, MAIL_ATTR_LOG_PROTO_NAME) == 0) { if (message->client_proto != 0) myfree(message->client_proto); message->client_proto = mystrdup(value); have_log_client_attr = 1; } else if (strcmp(name, MAIL_ATTR_LOG_HELO_NAME) == 0) { if (message->client_helo != 0) myfree(message->client_helo); message->client_helo = mystrdup(value); have_log_client_attr = 1; } else if (strcmp(name, MAIL_ATTR_SASL_METHOD) == 0) { if (message->sasl_method == 0) message->sasl_method = mystrdup(value); else msg_warn("%s: ignoring multiple %s attribute: %s", message->queue_id, MAIL_ATTR_SASL_METHOD, value); } else if (strcmp(name, MAIL_ATTR_SASL_USERNAME) == 0) { if (message->sasl_username == 0) message->sasl_username = mystrdup(value); else msg_warn("%s: ignoring multiple %s attribute: %s", message->queue_id, MAIL_ATTR_SASL_USERNAME, value); } else if (strcmp(name, MAIL_ATTR_SASL_SENDER) == 0) { if (message->sasl_sender == 0) message->sasl_sender = mystrdup(value); else msg_warn("%s: ignoring multiple %s attribute: %s", message->queue_id, MAIL_ATTR_SASL_SENDER, value); } else if (strcmp(name, MAIL_ATTR_LOG_IDENT) == 0) { if (message->log_ident == 0) message->log_ident = mystrdup(value); else msg_warn("%s: ignoring multiple %s attribute: %s", message->queue_id, MAIL_ATTR_LOG_IDENT, value); } else if (strcmp(name, MAIL_ATTR_RWR_CONTEXT) == 0) { if (message->rewrite_context == 0) message->rewrite_context = mystrdup(value); else msg_warn("%s: ignoring multiple %s attribute: %s", message->queue_id, MAIL_ATTR_RWR_CONTEXT, value); } /* * Optional tracing flags (verify, sendmail -v, sendmail -bv). * This record is killed after a trace logfile report is sent and * after the logfile is deleted. */ else if (strcmp(name, MAIL_ATTR_TRACE_FLAGS) == 0) { message->tflags = DEL_REQ_TRACE_FLAGS(atoi(value)); if (message->tflags == DEL_REQ_FLAG_RECORD) message->tflags_offset = curr_offset; else message->tflags_offset = 0; } continue; } if (rec_type == REC_TYPE_WARN) { if (message->warn_offset == 0) { message->warn_offset = curr_offset; REC_TYPE_WARN_SCAN(start, message->warn_time); } continue; } if (rec_type == REC_TYPE_VERP) { if (message->verp_delims == 0) { if (message->sender == 0 || message->sender[0] == 0) { msg_warn("%s: ignoring VERP request for null sender", message->queue_id); } else if (verp_delims_verify(start) != 0) { msg_warn("%s: ignoring bad VERP request: \"%.100s\"", message->queue_id, start); } else { if (msg_verbose) msg_info("%s: enabling VERP for sender \"%.100s\"", message->queue_id, message->sender); message->single_rcpt = 1; message->verp_delims = mystrdup(start); } } continue; } } /* * Grr. */ if (dsn_orcpt != 0) { if (rec_type > 0) msg_warn("%s: ignoring out-of-order DSN original recipient <%.200s>", message->queue_id, dsn_orcpt); myfree(dsn_orcpt); } if (orig_rcpt != 0) { if (rec_type > 0) msg_warn("%s: ignoring out-of-order original recipient <%.200s>", message->queue_id, orig_rcpt); myfree(orig_rcpt); } /* * After sending a "delayed" warning, request sender notification when * message delivery is completed. While "mail delayed" notifications are * bad enough because they multiply the amount of email traffic, "delay * cleared" notifications are even worse because they come in a sudden * burst when the queue drains after a network outage. */ if (var_dsn_delay_cleared && message->warn_time < 0) message->tflags |= DEL_REQ_FLAG_REC_DLY_SENT; /* * Remember when we have read the last recipient batch. Note that we do * it here after reading as reading might have used considerable amount * of time. */ message->refill_time = sane_time(); /* * Avoid clumsiness elsewhere in the program. When sending data across an * IPC channel, sending an empty string is more convenient than sending a * null pointer. */ if (message->dsn_envid == 0) message->dsn_envid = mystrdup(""); if (message->encoding == 0) message->encoding = mystrdup(MAIL_ATTR_ENC_NONE); if (message->client_name == 0) message->client_name = mystrdup(""); if (message->client_addr == 0) message->client_addr = mystrdup(""); if (message->client_port == 0) message->client_port = mystrdup(""); if (message->client_proto == 0) message->client_proto = mystrdup(""); if (message->client_helo == 0) message->client_helo = mystrdup(""); if (message->sasl_method == 0) message->sasl_method = mystrdup(""); if (message->sasl_username == 0) message->sasl_username = mystrdup(""); if (message->sasl_sender == 0) message->sasl_sender = mystrdup(""); if (message->log_ident == 0) message->log_ident = mystrdup(""); if (message->rewrite_context == 0) message->rewrite_context = mystrdup(MAIL_ATTR_RWR_LOCAL); /* Postfix < 2.3 compatibility. */ if (message->create_time == 0) message->create_time = message->arrival_time.tv_sec; /* * Clean up. */ vstring_free(buf); /* * Sanity checks. Verify that all required information was found, * including the queue file end marker. */ if (message->rcpt_unread < 0 || (message->rcpt_offset == 0 && message->rcpt_unread != 0)) { msg_warn("%s: rcpt count mismatch (%d)", message->queue_id, message->rcpt_unread); message->rcpt_unread = 0; } if (rec_type <= 0) { /* Already logged warning. */ } else if (message->arrival_time.tv_sec == 0) { msg_warn("%s: message rejected: missing arrival time record", message->queue_id); } else if (message->sender == 0) { msg_warn("%s: message rejected: missing sender record", message->queue_id); } else if (message->data_offset == 0) { msg_warn("%s: message rejected: missing size record", message->queue_id); } else { return (0); } message->rcpt_offset = save_offset; /* restore flag */ message->rcpt_unread = save_unread; /* restore count */ recipient_list_free(&message->rcpt_list); recipient_list_init(&message->rcpt_list, RCPT_LIST_INIT_QUEUE); return (-1); }