diff options
-rw-r--r-- | usr.sbin/smtpd/mda.c | 479 | ||||
-rw-r--r-- | usr.sbin/smtpd/runner.c | 123 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.c | 576 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.h | 42 |
4 files changed, 482 insertions, 738 deletions
diff --git a/usr.sbin/smtpd/mda.c b/usr.sbin/smtpd/mda.c index ed4cbf8e665..b3c66065197 100644 --- a/usr.sbin/smtpd/mda.c +++ b/usr.sbin/smtpd/mda.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mda.c,v 1.37 2010/03/03 10:52:31 jacekm Exp $ */ +/* $OpenBSD: mda.c,v 1.38 2010/04/19 08:14:07 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -33,20 +33,22 @@ #include <string.h> #include <time.h> #include <unistd.h> +#include <vis.h> #include "smtpd.h" -__dead void mda_shutdown(void); -void mda_sig_handler(int, short, void *); -void mda_dispatch_parent(int, short, void *); -void mda_dispatch_queue(int, short, void *); -void mda_dispatch_runner(int, short, void *); -void mda_setup_events(struct smtpd *); -void mda_disable_events(struct smtpd *); -void mda_store(struct batch *); -void mda_event(int, short, void *); -void mda_store_done(struct batch *); -void mda_done(struct smtpd *, struct batch *); +__dead void mda_shutdown(void); +void mda_sig_handler(int, short, void *); +void mda_dispatch_parent(int, short, void *); +void mda_dispatch_queue(int, short, void *); +void mda_dispatch_runner(int, short, void *); +void mda_setup_events(struct smtpd *); +void mda_disable_events(struct smtpd *); +void mda_store(struct mda_session *); +void mda_store_event(int, short, void *); +struct mda_session *mda_lookup(struct smtpd *, u_int32_t); + +int mda_id; void mda_sig_handler(int sig, short event, void *p) @@ -96,74 +98,126 @@ mda_dispatch_parent(int sig, short event, void *p) break; switch (imsg.hdr.type) { - case IMSG_PARENT_MAILBOX_OPEN: { - struct batch *b = imsg.data; - - IMSG_SIZE_CHECK(b); - - if ((b = batch_by_id(env, b->id)) == NULL) - fatalx("mda: internal inconsistency"); - - /* parent ensures mboxfd is valid */ - if (imsg.fd == -1) - fatalx("mda: mboxfd pass failure"); + case IMSG_PARENT_FORK_MDA: { + struct mda_session *s; - /* got user's mbox fd */ - if ((b->mboxfp = fdopen(imsg.fd, "w")) == NULL) { - log_warn("mda: fdopen"); - mda_done(env, b); - break; - } + s = mda_lookup(env, imsg.hdr.peerid); - /* - * From now on, delivery session must be deinited in - * the parent process as well as in mda. - */ - b->cleanup_parent = 1; + if (imsg.fd < 0) + fatalx("mda: fd pass fail"); + s->w.fd = imsg.fd; - /* get message content fd */ - imsg_compose_event(env->sc_ievs[PROC_PARENT], - IMSG_PARENT_MESSAGE_OPEN, 0, 0, -1, b, - sizeof(*b)); + /* send message content to the helper process */ + mda_store(s); break; } - case IMSG_PARENT_MESSAGE_OPEN: { - struct batch *b = imsg.data; + case IMSG_MDA_DONE: { + char output[64]; + struct mda_session *s; + struct path *path; + char *error, *parent_error; - IMSG_SIZE_CHECK(b); + s = mda_lookup(env, imsg.hdr.peerid); - if ((b = batch_by_id(env, b->id)) == NULL) - fatalx("mda: internal inconsistency"); - - if (imsg.fd == -1) { - mda_done(env, b); - break; + /* + * Grab last line of mda stdout/stderr if available. + */ + output[0] = '\0'; + if (imsg.fd != -1) { + char *ln, *buf; + FILE *fp; + size_t len; + + buf = NULL; + if (lseek(imsg.fd, 0, SEEK_SET) < 0) + fatalx("lseek"); + fp = fdopen(imsg.fd, "r"); + if (fp == NULL) + fatal("mda: fdopen"); + while ((ln = fgetln(fp, &len))) { + if (ln[len - 1] == '\n') + ln[len - 1] = '\0'; + else { + buf = malloc(len + 1); + if (buf == NULL) + fatal(NULL); + memcpy(buf, ln, len); + buf[len] = '\0'; + ln = buf; + } + strlcpy(output, "\"", sizeof output); + strnvis(output + 1, ln, + sizeof(output) - 2, + VIS_SAFE | VIS_CSTYLE); + strlcat(output, "\"", sizeof output); + log_debug("mda_out: %s", output); + } + free(buf); + fclose(fp); } - if ((b->datafp = fdopen(imsg.fd, "r")) == NULL) { - log_warn("mda: fdopen"); - mda_done(env, b); - break; + /* + * Choose between parent's description of error and + * child's output, the latter having preference over + * the former. + */ + error = NULL; + parent_error = imsg.data; + if (strcmp(parent_error, "exited okay") == 0) { + if (!feof(s->datafp) || s->w.queued) + error = "mda exited prematurely"; + } else { + if (output[0]) + error = output; + else + error = parent_error; } - /* got message content, copy it to mbox */ - mda_store(b); - break; - } - - case IMSG_MDA_FINALIZE: { - struct batch *b = imsg.data; - enum message_status status; - - IMSG_SIZE_CHECK(b); + /* update queue entry */ + if (error == NULL) + s->msg.status = S_MESSAGE_ACCEPTED; + else + strlcpy(s->msg.session_errorline, error, + sizeof s->msg.session_errorline); + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_QUEUE_MESSAGE_UPDATE, 0, 0, -1, &s->msg, + sizeof s->msg); - status = b->message.status; - if ((b = batch_by_id(env, b->id)) == NULL) - fatalx("mda: internal inconsistency"); - b->message.status = status; - - mda_done(env, b); + /* + * XXX: which struct path gets used for logging depends + * on whether lka did aliases or .forward processing; + * lka may need to be changed to present data in more + * unified way. + */ + if (s->msg.recipient.rule.r_action == A_MAILDIR || + s->msg.recipient.rule.r_action == A_MBOX) + path = &s->msg.recipient; + else + path = &s->msg.session_rcpt; + + /* log status */ + if (error && asprintf(&error, "Error (%s)", error) < 0) + fatal("mda: asprintf"); + log_info("%s: to=<%s@%s>, delay=%d, stat=%s", + s->msg.message_id, path->user, path->domain, + time(NULL) - s->msg.creation, + error ? error : "Sent"); + free(error); + + /* destroy session */ + LIST_REMOVE(s, entry); + if (s->w.fd != -1) + close(s->w.fd); + if (s->datafp) + fclose(s->datafp); + msgbuf_clear(&s->w); + event_del(&s->ev); + free(s); + + /* update runner's session count */ + imsg_compose_event(env->sc_ievs[PROC_RUNNER], + IMSG_MDA_SESS_NEW, 0, 0, -1, NULL, 0); break; } @@ -176,6 +230,7 @@ mda_dispatch_parent(int sig, short event, void *p) log_verbose(verbose); break; } + default: log_warnx("mda_dispatch_parent: got imsg %d", imsg.hdr.type); @@ -266,54 +321,69 @@ mda_dispatch_runner(int sig, short event, void *p) break; switch (imsg.hdr.type) { - case IMSG_BATCH_CREATE: { - struct batch *req = imsg.data; - struct batch *b; - - IMSG_SIZE_CHECK(req); - - /* runner opens delivery session */ - if ((b = malloc(sizeof(*b))) == NULL) + case IMSG_MDA_SESS_NEW: { + struct deliver deliver; + struct mda_session *s; + struct path *path; + + /* make new session based on provided args */ + s = calloc(1, sizeof *s); + if (s == NULL) fatal(NULL); - *b = *req; - msgbuf_init(&b->w); - b->env = env; - b->mboxfp = NULL; - b->datafp = NULL; - SPLAY_INSERT(batchtree, &env->batch_queue, b); - break; - } - - case IMSG_BATCH_APPEND: { - struct message *append = imsg.data; - struct batch *b; - - IMSG_SIZE_CHECK(append); + msgbuf_init(&s->w); + s->msg = *(struct message *)imsg.data; + s->msg.status = S_MESSAGE_TEMPFAILURE; + s->id = mda_id++; + s->datafp = fdopen(imsg.fd, "r"); + if (s->datafp == NULL) + fatalx("mda: fdopen"); + LIST_INSERT_HEAD(&env->mda_sessions, s, entry); + + /* request parent to fork a helper process */ + path = &s->msg.recipient; + switch (path->rule.r_action) { + case A_EXT: + deliver.mode = A_EXT; + strlcpy(deliver.user, path->pw_name, + sizeof deliver.user); + strlcpy(deliver.to, path->rule.r_value.path, + sizeof deliver.to); + break; - /* runner submits the message to deliver */ - if ((b = batch_by_id(env, append->batch_id)) == NULL) - fatalx("mda: internal inconsistency"); - if (b->message.message_id[0]) - fatal("mda: runner submitted extra msg"); - b->message = *append; + case A_MBOX: + deliver.mode = A_EXT; + strlcpy(deliver.user, "root", + sizeof deliver.user); + snprintf(deliver.to, sizeof deliver.to, + "%s -f %s@%s %s", PATH_MAILLOCAL, + s->msg.sender.user, s->msg.sender.domain, + path->pw_name); + break; - /* safe default */ - b->message.status = S_MESSAGE_TEMPFAILURE; - break; - } + case A_MAILDIR: + deliver.mode = A_MAILDIR; + strlcpy(deliver.user, path->pw_name, + sizeof deliver.user); + strlcpy(deliver.to, path->rule.r_value.path, + sizeof deliver.to); + break; - case IMSG_BATCH_CLOSE: { - struct batch *b = imsg.data; + case A_FILENAME: + deliver.mode = A_FILENAME; + /* XXX: unconditional SMTPD_USER is wrong. */ + strlcpy(deliver.user, SMTPD_USER, + sizeof deliver.user); + strlcpy(deliver.to, path->u.filename, + sizeof deliver.to); + break; - IMSG_SIZE_CHECK(b); + default: + fatalx("unknown rule action"); + } - /* runner finished opening delivery session; - * request user's mbox fd */ - if ((b = batch_by_id(env, b->id)) == NULL) - fatalx("mda: internal inconsistency"); imsg_compose_event(env->sc_ievs[PROC_PARENT], - IMSG_PARENT_MAILBOX_OPEN, 0, 0, -1, b, - sizeof(*b)); + IMSG_PARENT_FORK_MDA, s->id, 0, -1, &deliver, + sizeof deliver); break; } default: @@ -391,7 +461,7 @@ mda(struct smtpd *env) fatal("mda: cannot drop privileges"); #endif - SPLAY_INIT(&env->batch_queue); + LIST_INIT(&env->mda_sessions); event_init(); @@ -413,187 +483,80 @@ mda(struct smtpd *env) } void -mda_store(struct batch *b) +mda_store(struct mda_session *s) { char *p; struct buf *buf; - int ch, len; + int len; - if (b->message.sender.user[0] && b->message.sender.domain[0]) + if (s->msg.sender.user[0] && s->msg.sender.domain[0]) /* XXX: remove user provided Return-Path, if any */ len = asprintf(&p, "Return-Path: %s@%s\nDelivered-To: %s@%s\n", - b->message.sender.user, - b->message.sender.domain, - b->message.session_rcpt.user, - b->message.session_rcpt.domain); + s->msg.sender.user, s->msg.sender.domain, + s->msg.session_rcpt.user, s->msg.session_rcpt.domain); else len = asprintf(&p, "Delivered-To: %s@%s\n", - b->message.session_rcpt.user, - b->message.session_rcpt.domain); + s->msg.session_rcpt.user, s->msg.session_rcpt.domain); if (len == -1) fatal("mda_store: asprintf"); - if (b->message.recipient.rule.r_action == A_MAILDIR) { - fprintf(b->mboxfp, "%s", p); - while ((ch = fgetc(b->datafp)) != EOF) - if (fputc(ch, b->mboxfp) == EOF) - break; - if (ferror(b->datafp)) - fatal("mda_store: cannot read message in queue"); - if (fflush(b->mboxfp) || ferror(b->mboxfp)) - fatal("mda_store: cannot write to file"); - mda_store_done(b); - } else { - b->w.fd = fileno(b->mboxfp); - session_socket_blockmode(b->w.fd, BM_NONBLOCK); - if ((buf = buf_open(len)) == NULL) - fatal(NULL); - if (buf_add(buf, p, len) < 0) - fatal(NULL); - buf_close(&b->w, buf); - event_set(&b->ev, b->w.fd, EV_WRITE, mda_event, b); - event_add(&b->ev, NULL); - } - + session_socket_blockmode(s->w.fd, BM_NONBLOCK); + if ((buf = buf_open(len)) == NULL) + fatal(NULL); + if (buf_add(buf, p, len) < 0) + fatal(NULL); + buf_close(&s->w, buf); + event_set(&s->ev, s->w.fd, EV_WRITE, mda_store_event, s); + event_add(&s->ev, NULL); free(p); } void -mda_event(int fd, short event, void *p) +mda_store_event(int fd, short event, void *p) { - char tmp[16384]; - struct batch *b = p; - struct buf *buf; - size_t len; + char tmp[16384]; + struct mda_session *s = p; + struct buf *buf; + size_t len; - if (b->w.queued == 0) { - if ((buf = buf_dynamic(0, sizeof(tmp))) == NULL) + if (s->w.queued == 0) { + if ((buf = buf_dynamic(0, sizeof tmp)) == NULL) fatal(NULL); - len = fread(tmp, 1, sizeof(tmp), b->datafp); - if (ferror(b->datafp)) - fatal("mda_event: fread failed"); - if (feof(b->datafp) && len == 0) { - mda_store_done(b); + len = fread(tmp, 1, sizeof tmp, s->datafp); + if (ferror(s->datafp)) + fatal("mda_store_event: fread failed"); + if (feof(s->datafp) && len == 0) { + close(s->w.fd); + s->w.fd = -1; return; } if (buf_add(buf, tmp, len) < 0) fatal(NULL); - buf_close(&b->w, buf); + buf_close(&s->w, buf); } - if (buf_write(&b->w) < 0) { - /* XXX: if $? is zero, message is considered delivered despite - * write error. */ - log_warn("mda_event: write failed"); - mda_store_done(b); + if (buf_write(&s->w) < 0) { + close(s->w.fd); + s->w.fd = -1; return; } - event_set(&b->ev, fd, EV_WRITE, mda_event, b); - event_add(&b->ev, NULL); + event_set(&s->ev, fd, EV_WRITE, mda_store_event, s); + event_add(&s->ev, NULL); } -void -mda_store_done(struct batch *b) +struct mda_session * +mda_lookup(struct smtpd *env, u_int32_t id) { - fclose(b->datafp); - b->datafp = NULL; - - /* closing mboxfd will trigger EOF in forked mda */ - fsync(fileno(b->mboxfp)); - fclose(b->mboxfp); - b->mboxfp = NULL; - - /* ... unless it is maildir, in which case we need to - * "trigger EOF" differently */ - if (b->message.recipient.rule.r_action == A_MAILDIR) - imsg_compose_event(b->env->sc_ievs[PROC_PARENT], - IMSG_PARENT_MAILDIR_RENAME, 0, 0, -1, b, - sizeof(*b)); - - /* Waiting for IMSG_MDA_FINALIZE... */ - b->cleanup_parent = 0; -} + struct mda_session *s; -void -mda_done(struct smtpd *env, struct batch *b) -{ - if (b->cleanup_parent) { - /* - * Error has occured while both parent and mda maintain some - * state for this delivery session. Need to deinit both. - * Deinit parent first. - */ - - if (b->message.recipient.rule.r_action == A_MAILDIR) { - /* - * In case of maildir, deiniting parent's state consists - * of removing the file in tmp. - */ - imsg_compose_event(env->sc_ievs[PROC_PARENT], - IMSG_PARENT_MAILDIR_FAIL, 0, 0, -1, b, - sizeof(*b)); - } else { - /* - * In all other cases, ie. mbox and external, deiniting - * parent's state consists of killing its child, and - * freeing associated struct child. - * - * Requesting that parent does this cleanup involves - * racing issues. The race-free way is to simply wait. - * Eventually, child timeout in parent will be hit. - */ - } + LIST_FOREACH(s, &env->mda_sessions, entry) + if (s->id == id) + break; - /* - * Either way, parent will eventually send IMSG_MDA_FINALIZE. - * Then, the mda deinit will happen. - */ - b->cleanup_parent = 0; - } else { - /* - * Deinit mda. - */ - - /* update runner (currently: via queue) */ - imsg_compose_event(env->sc_ievs[PROC_QUEUE], - IMSG_QUEUE_MESSAGE_UPDATE, 0, 0, -1, - &b->message, sizeof(b->message)); - - imsg_compose_event(env->sc_ievs[PROC_RUNNER], - IMSG_BATCH_DONE, 0, 0, -1, NULL, 0); - - /* log status */ - if (b->message.recipient.rule.r_action != A_MAILDIR && - b->message.recipient.rule.r_action != A_MBOX) { - log_info("%s: to=<%s@%s>, delay=%d, stat=%s", - b->message.message_id, - b->message.session_rcpt.user, - b->message.session_rcpt.domain, - time(NULL) - b->message.creation, - b->message.status & S_MESSAGE_PERMFAILURE ? "MdaPermError" : - b->message.status & S_MESSAGE_TEMPFAILURE ? "MdaTempError" : - "Sent"); - } - else { - log_info("%s: to=<%s@%s>, delay=%d, stat=%s", - b->message.message_id, - b->message.recipient.user, - b->message.recipient.domain, - time(NULL) - b->message.creation, - b->message.status & S_MESSAGE_PERMFAILURE ? "MdaPermError" : - b->message.status & S_MESSAGE_TEMPFAILURE ? "MdaTempError" : - "Sent"); - } + if (s == NULL) + fatalx("mda: bogus session id"); - /* deallocate resources */ - SPLAY_REMOVE(batchtree, &env->batch_queue, b); - if (b->mboxfp) - fclose(b->mboxfp); - if (b->datafp) - fclose(b->datafp); - msgbuf_clear(&b->w); - free(b); - } + return s; } diff --git a/usr.sbin/smtpd/runner.c b/usr.sbin/smtpd/runner.c index a2f9b1ac808..acbf095a648 100644 --- a/usr.sbin/smtpd/runner.c +++ b/usr.sbin/smtpd/runner.c @@ -1,4 +1,4 @@ -/* $OpenBSD: runner.c,v 1.78 2010/01/10 16:42:35 gilles Exp $ */ +/* $OpenBSD: runner.c,v 1.79 2010/04/19 08:14:07 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -68,8 +68,6 @@ void runner_process_queue(struct smtpd *); void runner_process_runqueue(struct smtpd *); void runner_process_batchqueue(struct smtpd *); -void runner_batch_dispatch(struct smtpd *, struct batch *, time_t); - int runner_message_schedule(struct message *, time_t); void runner_purge_run(void); @@ -330,7 +328,7 @@ runner_dispatch_mda(int sig, short event, void *p) break; switch (imsg.hdr.type) { - case IMSG_BATCH_DONE: + case IMSG_MDA_SESS_NEW: env->stats->mda.sessions_active--; break; @@ -781,68 +779,56 @@ runner_process_runqueue(struct smtpd *env) void runner_process_batchqueue(struct smtpd *env) { - time_t curtime; - struct batch *batchp, *nxt; - - curtime = time(NULL); - for (batchp = SPLAY_MIN(batchtree, &env->batch_queue); - batchp != NULL; - batchp = nxt) { - nxt = SPLAY_NEXT(batchtree, &env->batch_queue, batchp); + struct batch *batchp; + struct message *m; + int fd; + + while ((batchp = SPLAY_MIN(batchtree, &env->batch_queue)) != NULL) { + switch (batchp->type) { + case T_BOUNCE_BATCH: + while ((m = TAILQ_FIRST(&batchp->messages))) { + bounce_process(env, m); + TAILQ_REMOVE(&batchp->messages, m, entry); + free(m); + } + env->stats->runner.bounces_active++; + env->stats->runner.bounces++; + break; - runner_batch_dispatch(env, batchp, curtime); + case T_MDA_BATCH: + m = TAILQ_FIRST(&batchp->messages); + fd = queue_open_message_file(m->message_id); + imsg_compose_event(env->sc_ievs[PROC_MDA], + IMSG_MDA_SESS_NEW, 0, 0, fd, m, sizeof *m); + TAILQ_REMOVE(&batchp->messages, m, entry); + free(m); + env->stats->mda.sessions_active++; + env->stats->mda.sessions++; + break; - SPLAY_REMOVE(batchtree, &env->batch_queue, batchp); - bzero(batchp, sizeof(struct batch)); - free(batchp); - } -} + case T_MTA_BATCH: + imsg_compose_event(env->sc_ievs[PROC_MTA], + IMSG_BATCH_CREATE, 0, 0, -1, batchp, + sizeof *batchp); + while ((m = TAILQ_FIRST(&batchp->messages))) { + imsg_compose_event(env->sc_ievs[PROC_MTA], + IMSG_BATCH_APPEND, 0, 0, -1, m, sizeof *m); + TAILQ_REMOVE(&batchp->messages, m, entry); + free(m); + } + imsg_compose_event(env->sc_ievs[PROC_MTA], + IMSG_BATCH_CLOSE, 0, 0, -1, batchp, sizeof *batchp); + env->stats->mta.sessions_active++; + env->stats->mta.sessions++; + break; -void -runner_batch_dispatch(struct smtpd *env, struct batch *batchp, time_t curtime) -{ - u_int8_t proctype; - struct message *messagep; - - if ((batchp->type & (T_BOUNCE_BATCH|T_MDA_BATCH|T_MTA_BATCH)) == 0) - fatal("runner_batch_dispatch: unknown batch type"); - - log_debug("in batch dispatch"); - if (batchp->type == T_BOUNCE_BATCH) { - while ((messagep = TAILQ_FIRST(&batchp->messages))) { - bounce_process(env, messagep); - TAILQ_REMOVE(&batchp->messages, messagep, entry); - bzero(messagep, sizeof(*messagep)); - free(messagep); + default: + fatalx("runner_process_batchqueue: unknown type"); } - env->stats->runner.bounces_active++; - env->stats->runner.bounces++; - return; - } - if (batchp->type & T_MDA_BATCH) { - proctype = PROC_MDA; - env->stats->mda.sessions_active++; - env->stats->mda.sessions++; - } else if (batchp->type & T_MTA_BATCH) { - proctype = PROC_MTA; - env->stats->mta.sessions_active++; - env->stats->mta.sessions++; - } - - imsg_compose_event(env->sc_ievs[proctype], IMSG_BATCH_CREATE, 0, 0, -1, - batchp, sizeof (struct batch)); - - while ((messagep = TAILQ_FIRST(&batchp->messages))) { - imsg_compose_event(env->sc_ievs[proctype], IMSG_BATCH_APPEND, 0, 0, - -1, messagep, sizeof (struct message)); - TAILQ_REMOVE(&batchp->messages, messagep, entry); - bzero(messagep, sizeof(struct message)); - free(messagep); + SPLAY_REMOVE(batchtree, &env->batch_queue, batchp); + free(batchp); } - - imsg_compose_event(env->sc_ievs[proctype], IMSG_BATCH_CLOSE, 0, 0, -1, - batchp, sizeof(struct batch)); } int @@ -873,19 +859,12 @@ runner_message_schedule(struct message *messagep, time_t tm) // recompute path if (messagep->type == T_MDA_MESSAGE || - messagep->type == T_BOUNCE_MESSAGE) { - if (messagep->status & S_MESSAGE_LOCKFAILURE) { - if (messagep->retry < 128) - return 1; - delay = (messagep->retry * 60) + arc4random_uniform(60); - } - else { - if (messagep->retry < 5) - return 1; + messagep->type == T_BOUNCE_MESSAGE) { + if (messagep->retry < 5) + return 1; - if (messagep->retry < 15) - delay = (messagep->retry * 60) + arc4random_uniform(60); - } + if (messagep->retry < 15) + delay = (messagep->retry * 60) + arc4random_uniform(60); } if (messagep->type == T_MTA_MESSAGE) { diff --git a/usr.sbin/smtpd/smtpd.c b/usr.sbin/smtpd/smtpd.c index ffd185692f8..85ab80a664e 100644 --- a/usr.sbin/smtpd/smtpd.c +++ b/usr.sbin/smtpd/smtpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.c,v 1.96 2010/03/01 13:04:03 gilles Exp $ */ +/* $OpenBSD: smtpd.c,v 1.97 2010/04/19 08:14:07 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -61,12 +61,9 @@ void parent_dispatch_runner(int, short, void *); void parent_dispatch_queue(int, short, void *); void parent_dispatch_control(int, short, void *); void parent_sig_handler(int, short, void *); -int parent_open_message_file(struct batch *); -int parent_mailbox_open(char *, struct passwd *, struct batch *); -int parent_filename_open(char *, struct passwd *, struct batch *); -int parent_maildir_open(char *, struct passwd *, struct batch *); -int parent_maildir_init(struct passwd *, char *); -int parent_external_mda(char *, struct passwd *, struct batch *); + +void forkmda(struct smtpd *, struct imsgev *, u_int32_t, + struct deliver *); int parent_enqueue_offline(struct smtpd *, char *); int parent_forward_open(char *); int setup_spool(uid_t, gid_t); @@ -78,10 +75,6 @@ struct child *child_add(struct smtpd *, pid_t, int, int); void child_del(struct smtpd *, pid_t); struct child *child_lookup(struct smtpd *, pid_t); -void parent_mda_permfail(struct smtpd *, struct batch *); -void parent_mda_tempfail(struct smtpd *, struct batch *); -void parent_mda_success(struct smtpd *, struct batch *); - extern char **environ; int __b64_pton(char const *, unsigned char *, size_t); @@ -420,162 +413,10 @@ parent_dispatch_mda(int imsgfd, short event, void *p) break; switch (imsg.hdr.type) { - case IMSG_PARENT_MAILBOX_OPEN: { - struct batch *batchp = imsg.data; - struct path *path; - struct passwd *pw; - char *pw_name; - char *file; - u_int8_t i; - int fd; - struct action_handler { - enum action_type action; - int (*handler)(char *, struct passwd *, struct batch *); - } action_hdl_table[] = { - { A_MBOX, parent_mailbox_open }, - { A_MAILDIR, parent_maildir_open }, - { A_EXT, parent_external_mda }, - { A_FILENAME, parent_filename_open } - }; - - IMSG_SIZE_CHECK(batchp); - - batchp->env = env; - path = &batchp->message.recipient; - if (batchp->type & T_BOUNCE_BATCH) { - path = &batchp->message.sender; - } - - for (i = 0; i < nitems(action_hdl_table); ++i) - if (action_hdl_table[i].action == path->rule.r_action) - break; - if (i == nitems(action_hdl_table)) - fatalx("parent_dispatch_mda: unknown action"); - - file = path->rule.r_value.path; - - if (path->rule.r_action == A_FILENAME) { - file = path->u.filename; - pw_name = SMTPD_USER; - } - else if (path->rule.r_user != NULL) - pw_name = path->rule.r_user; - else - pw_name = path->pw_name; - - errno = 0; - pw = getpwnam(pw_name); - if (pw == NULL) { - if (errno) { - log_warn("%s: getpwnam: %s", - batchp->message.message_id, - pw_name); - parent_mda_tempfail(env, batchp); - } else { - log_warnx("%s: getpwnam: %s: user does not exist", - batchp->message.message_id, - pw_name); - parent_mda_permfail(env, batchp); - } - break; - } - - if (setegid(pw->pw_gid) || seteuid(pw->pw_uid)) - fatal("privdrop failed"); - - fd = action_hdl_table[i].handler(file, pw, batchp); - - if (setegid(0) || seteuid(0)) - fatal("privraise failed"); - - if (fd == -1) { - log_warnx("%s: could not init delivery for %s", - batchp->message.message_id, pw_name); - parent_mda_tempfail(env, batchp); - } else - imsg_compose_event(iev, - IMSG_PARENT_MAILBOX_OPEN, 0, 0, fd, batchp, - sizeof(*batchp)); - break; - } - case IMSG_PARENT_MESSAGE_OPEN: { - struct batch *batchp = imsg.data; - int fd; - - IMSG_SIZE_CHECK(batchp); - - fd = parent_open_message_file(batchp); - imsg_compose_event(iev, IMSG_PARENT_MESSAGE_OPEN, - 0, 0, fd, batchp, sizeof(struct batch)); - + case IMSG_PARENT_FORK_MDA: + forkmda(env, iev, imsg.hdr.peerid, imsg.data); break; - } - case IMSG_PARENT_MAILDIR_FAIL: - case IMSG_PARENT_MAILDIR_RENAME: { - char tmp[MAXPATHLEN], new[MAXPATHLEN]; - struct batch *batchp = imsg.data; - struct path *path; - struct passwd *pw; - int ret; - char *pw_name; - - IMSG_SIZE_CHECK(batchp); - - path = &batchp->message.recipient; - if (batchp->type & T_BOUNCE_BATCH) { - path = &batchp->message.sender; - } - - if (path->rule.r_user != NULL) - pw_name = path->rule.r_user; - else - pw_name = path->pw_name; - - errno = 0; - pw = getpwnam(pw_name); - if (pw == NULL) { - if (errno) { - log_warn("%s: getpwnam: %s", - batchp->message.message_id, - pw_name); - parent_mda_tempfail(env, batchp); - } else { - log_warnx("%s: getpwnam: %s: user does not exist", - batchp->message.message_id, - pw_name); - parent_mda_permfail(env, batchp); - } - break; - } - - if (! bsnprintf(tmp, sizeof(tmp), "%s/tmp/%s", - path->rule.r_value.path, batchp->message.message_uid)) - fatal("parent_dispatch_mda: snprintf"); - if (! bsnprintf(new, sizeof(new), "%s/new/%s", - path->rule.r_value.path, batchp->message.message_uid)) - fatal("parent_dispatch_mda: snprintf"); - if (seteuid(pw->pw_uid) == -1) - fatal("privdrop failed"); - - if (imsg.hdr.type == IMSG_PARENT_MAILDIR_FAIL) { - unlink(tmp); - ret = 0; - } else - ret = rename(tmp, new); - - if (seteuid(0) == -1) - fatal("privraise failed"); - - if (ret < 0) { - log_warn("%s: %s: cannot rename to the 'new' directory", - batchp->message.message_id, tmp); - parent_mda_tempfail(env, batchp); - unlink(tmp); - } else - parent_mda_success(env, batchp); - break; - } default: log_warnx("parent_dispatch_mda: got imsg %d", imsg.hdr.type); @@ -888,13 +729,9 @@ parent_sig_handler(int sig, short event, void *p) free(cause); asprintf(&cause, "terminated; timeout"); } - if (fail) { - log_warnx("external mda %s", cause); - parent_mda_tempfail(env, &child->mda_batch); - } else { - log_debug("external mda %s", cause); - parent_mda_success(env, &child->mda_batch); - } + imsg_compose_event(env->sc_ievs[PROC_MDA], + IMSG_MDA_DONE, child->mda_id, 0, + child->mda_out, cause, strlen(cause) + 1); break; case CHILD_ENQUEUE_OFFLINE: @@ -1302,192 +1139,207 @@ imsg_compose_event(struct imsgev *iev, u_int16_t type, u_int32_t peerid, return (ret); } -int -parent_open_message_file(struct batch *batchp) +void +forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, + struct deliver *deliver) { - int fd; - char pathname[MAXPATHLEN]; - u_int16_t hval; - struct message *messagep; + char ebuf[128], sfn[32]; + struct passwd *pw; + struct child *child; + pid_t pid; + int n, allout, pipefd[2]; - messagep = &batchp->message; - hval = queue_hash(messagep->message_id); + log_debug("forkmda: to %s as %s", deliver->to, deliver->user); - if (! bsnprintf(pathname, sizeof(pathname), "%s%s/%d/%s/message", - PATH_SPOOL, PATH_QUEUE, hval, batchp->message_id)) - fatal("parent_open_message_file: snprintf"); + errno = 0; + pw = getpwnam(deliver->user); + if (pw == NULL) { + n = snprintf(ebuf, sizeof ebuf, "getpwnam: %s", + errno ? strerror(errno) : "no such user"); + imsg_compose_event(iev, IMSG_MDA_DONE, id, 0, -1, ebuf, n + 1); + return; + } - fd = open(pathname, O_RDONLY); - return fd; -} + /* lower privs early to allow fork fail due to ulimit */ + if (seteuid(pw->pw_uid) < 0) + fatal("cannot lower privileges"); -int -parent_mailbox_open(char *path, struct passwd *pw, struct batch *batchp) -{ - pid_t pid; - int pipefd[2]; - struct child *child; - char sender[MAX_PATH_SIZE]; - - /* This can never happen, but better safe than sorry. */ - if (! bsnprintf(sender, MAX_PATH_SIZE, "%s@%s", - batchp->message.sender.user, - batchp->message.sender.domain)) - fatal("parent_mailbox_open: bogus email length"); - - log_debug("executing mail.local"); - if (socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd) == -1) - return -1; + if (pipe(pipefd) < 0) { + n = snprintf(ebuf, sizeof ebuf, "pipe: %s", strerror(errno)); + if (seteuid(0) < 0) + fatal("forkmda: cannot restore privileges"); + imsg_compose_event(iev, IMSG_MDA_DONE, id, 0, -1, ebuf, n + 1); + return; + } - /* raise privileges because mail.local needs root to - * deliver to user mailboxes. - */ - if (seteuid(0) == -1) - fatal("privraise failed"); + /* prepare file which captures stdout and stderr */ + strlcpy(sfn, "/tmp/smtpd.out.XXXXXXXXXXX", sizeof(sfn)); + allout = mkstemp(sfn); + if (allout < 0) { + n = snprintf(ebuf, sizeof ebuf, "mkstemp: %s", strerror(errno)); + if (seteuid(0) < 0) + fatal("forkmda: cannot restore privileges"); + imsg_compose_event(iev, IMSG_MDA_DONE, id, 0, -1, ebuf, n + 1); + close(pipefd[0]); + close(pipefd[1]); + return; + } + unlink(sfn); pid = fork(); - if (pid == -1) { + if (pid < 0) { + n = snprintf(ebuf, sizeof ebuf, "fork: %s", strerror(errno)); + if (seteuid(0) < 0) + fatal("forkmda: cannot restore privileges"); + imsg_compose_event(iev, IMSG_MDA_DONE, id, 0, -1, ebuf, n + 1); close(pipefd[0]); close(pipefd[1]); - return -1; + close(allout); + return; } - if (pid == 0) { + /* parent passes the child fd over to mda */ + if (pid > 0) { + if (seteuid(0) < 0) + fatal("forkmda: cannot restore privileges"); + child = child_add(env, pid, CHILD_MDA, -1); + child->mda_out = allout; + child->mda_id = id; close(pipefd[0]); - close(STDOUT_FILENO); - close(STDERR_FILENO); - dup2(pipefd[1], 0); - - /* avoid hangs by setting a 5m timeout */ - alarm(300); - - execlp(PATH_MAILLOCAL, "mail.local", "-f", sender, pw->pw_name, - (void *)NULL); - _exit(1); + imsg_compose_event(iev, IMSG_PARENT_FORK_MDA, id, 0, pipefd[1], + NULL, 0); + return; } - if (seteuid(pw->pw_uid) == -1) - fatal("privdrop failed"); - - child = child_add(batchp->env, pid, CHILD_MDA, -1); - - /* Each child relates to a batch; record this relationship. */ - child->mda_batch = *batchp; - - close(pipefd[1]); - return pipefd[0]; -} - -int -parent_maildir_init(struct passwd *pw, char *root) -{ - u_int8_t i; - char pathname[MAXPATHLEN]; - char *subdir[] = { "/", "/tmp", "/cur", "/new" }; - - for (i = 0; i < nitems(subdir); ++i) { - if (! bsnprintf(pathname, sizeof(pathname), "%s%s", root, - subdir[i])) - return 0; - if (mkdir(pathname, 0700) == -1) - if (errno != EEXIST) - return 0; +#define error(m) { printf("%s: %s\n", m, strerror(errno)); exit(1); } + if (seteuid(0) < 0) + fatal("forkmda: cannot restore privileges"); + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("forkmda: cannot drop privileges"); + if (dup2(pipefd[0], STDIN_FILENO) < 0 || + dup2(allout, STDOUT_FILENO) < 0 || + dup2(allout, STDERR_FILENO) < 0) + fatal("forkmda: dup2"); + if (setsid() < 0) + error("setsid"); + if (signal(SIGPIPE, SIG_DFL) == SIG_ERR || + signal(SIGINT, SIG_DFL) == SIG_ERR || + signal(SIGTERM, SIG_DFL) == SIG_ERR || + signal(SIGCHLD, SIG_DFL) == SIG_ERR || + signal(SIGHUP, SIG_DFL) == SIG_ERR) + error("signal"); + if (chdir(pw->pw_dir) < 0 && chdir("/") < 0) + error("chdir"); + if (closefrom(STDERR_FILENO + 1) < 0) + error("closefrom"); + + /* avoid hangs by setting 5m timeout */ + alarm(300); + + if (deliver->mode == A_EXT) { + char *environ_new[2]; + + environ_new[0] = "PATH=" _PATH_DEFPATH; + environ_new[1] = (char *)NULL; + environ = environ_new; + execle("/bin/sh", "/bin/sh", "-c", deliver->to, (char *)NULL, + environ_new); + error("execle"); } - return 1; -} - -int -parent_maildir_open(char *path, struct passwd *pw, struct batch *batchp) -{ - char tmp[MAXPATHLEN]; - int mode = O_CREAT|O_RDWR|O_TRUNC|O_SYNC; - - if (! parent_maildir_init(pw, path)) - return -1; - - if (! bsnprintf(tmp, sizeof(tmp), "%s/tmp/%s", path, - batchp->message.message_uid)) - return -1; - - return open(tmp, mode, 0600); -} - -int -parent_external_mda(char *path, struct passwd *pw, struct batch *batchp) -{ - struct child *child; - pid_t pid; - int pipefd[2]; - arglist args; - char *word; - char *envp[2]; - - log_debug("executing filter as user: %s", pw->pw_name); - - if (socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd) == -1) { - if (errno == ENFILE) { - log_warn("parent_external_mda: socketpair"); - return -1; - } - fatal("parent_external_mda: socketpair"); - } + if (deliver->mode == A_MAILDIR) { + char tmp[PATH_MAX], new[PATH_MAX]; + int ch, fd; + FILE *fp; - pid = fork(); - if (pid == -1) { - log_warn("parent_external_mda: fork"); - close(pipefd[0]); - close(pipefd[1]); - return -1; +#define error2(m) { n = errno; unlink(tmp); errno = n; error(m); } + setproctitle("maildir delivery"); + if (mkdir(deliver->to, 0700) < 0 && errno != EEXIST) + error("cannot mkdir maildir"); + if (chdir(deliver->to) < 0) + error("cannot cd to maildir"); + if (mkdir("cur", 0700) < 0 && errno != EEXIST) + error("mkdir cur failed"); + if (mkdir("tmp", 0700) < 0 && errno != EEXIST) + error("mkdir tmp failed"); + if (mkdir("new", 0700) < 0 && errno != EEXIST) + error("mkdir new failed"); + snprintf(tmp, sizeof tmp, "tmp/%d.%d.%s", time(NULL), + getpid(), env->sc_hostname); + fd = open(tmp, O_CREAT | O_EXCL | O_WRONLY, 0600); + if (fd < 0) + error("cannot open tmp file"); + fp = fdopen(fd, "w"); + if (fp == NULL) + error2("fdopen"); + while ((ch = getc(stdin)) != EOF) + if (putc(ch, fp) == EOF) + break; + if (ferror(stdin)) + error2("read error"); + if (fflush(fp) == EOF || ferror(fp)) + error2("write error"); + if (fsync(fd) < 0) + error2("fsync"); + if (fclose(fp) == EOF) + error2("fclose"); + snprintf(new, sizeof new, "new/%s", tmp + 4); + if (rename(tmp, new) < 0) + error2("cannot rename tmp->new"); + exit(0); } - - if (pid == 0) { - if (seteuid(0) == -1) - fatal("privraise failed"); - if (setgroups(1, &pw->pw_gid) || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) - fatal("cannot drop privileges"); - - bzero(&args, sizeof(args)); - while ((word = strsep(&path, " \t")) != NULL) - if (*word != '\0') - addargs(&args, "%s", word); - - if (setsid() == -1) - fatal("setsid"); - - if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) - fatal("signal"); - - if (dup2(pipefd[0], STDIN_FILENO) == -1) - fatal("dup2"); - - if (chdir(pw->pw_dir) == -1 && chdir("/") == -1) - fatal("chdir"); - - if (closefrom(STDERR_FILENO + 1) == -1) - fatal("closefrom"); - - /* avoid hangs by setting a 5m timeout */ - alarm(300); - - envp[0] = "PATH=" _PATH_DEFPATH; - envp[1] = (char *)NULL; - environ = envp; - - execvp(args.list[0], args.list); - _exit(1); +#undef error2 + + if (deliver->mode == A_FILENAME) { + struct stat sb; + time_t now; + size_t len; + int fd; + FILE *fp; + char *ln; + +#define error2(m) { n = errno; ftruncate(fd, sb.st_size); errno = n; error(m); } + setproctitle("file delivery"); + fd = open(deliver->to, O_CREAT | O_APPEND | O_WRONLY, 0600); + if (fd < 0) + error("open"); + if (fstat(fd, &sb) < 0) + error("fstat"); + if (S_ISREG(sb.st_flags) && flock(fd, LOCK_EX) < 0) + error("flock"); + fp = fdopen(fd, "a"); + if (fp == NULL) + error("fdopen"); + time(&now); + fprintf(fp, "From %s@%s %s", SMTPD_USER, env->sc_hostname, + ctime(&now)); + while ((ln = fgetln(stdin, &len)) != NULL) { + if (ln[len - 1] == '\n') + len--; + if (len >= 5 && memcmp(ln, "From ", 5) == 0) + putc('>', fp); + fprintf(fp, "%.*s\n", (int)len, ln); + if (ferror(fp)) + break; + } + if (ferror(stdin)) + error2("read error"); + putc('\n', fp); + if (fflush(fp) == EOF || ferror(fp)) + error2("write error"); + if (fsync(fd) < 0) + error2("fsync"); + if (fclose(fp) == EOF) + error2("fclose"); + exit(0); } - child = child_add(batchp->env, pid, CHILD_MDA, -1); - - /* Each child relates to a batch; record this relationship. */ - child->mda_batch = *batchp; - - close(pipefd[0]); - return pipefd[1]; + fatalx("forkmda: unknown mode"); } +#undef error +#undef error2 int parent_enqueue_offline(struct smtpd *env, char *runner_path) @@ -1600,35 +1452,6 @@ parent_enqueue_offline(struct smtpd *env, char *runner_path) } int -parent_filename_open(char *path, struct passwd *pw, struct batch *batchp) -{ - int fd; - int mode = O_CREAT|O_APPEND|O_RDWR|O_SYNC|O_NONBLOCK; - - fd = open(path, mode, 0600); - if (fd == -1) { - if (errno == EWOULDBLOCK) - goto lockfail; - return -1; - } - - if (flock(fd, LOCK_EX|LOCK_NB) == -1) { - if (errno == EWOULDBLOCK) - goto lockfail; - fatal("flock"); - } - - return fd; - -lockfail: - if (fd != -1) - close(fd); - - batchp->message.status |= S_MESSAGE_LOCKFAILURE; - return -1; -} - -int parent_forward_open(char *username) { struct passwd *pw; @@ -1683,27 +1506,4 @@ child_cmp(struct child *c1, struct child *c2) return (0); } -void -parent_mda_permfail(struct smtpd *env, struct batch *b) -{ - b->message.status |= S_MESSAGE_PERMFAILURE; - imsg_compose_event(env->sc_ievs[PROC_MDA], IMSG_MDA_FINALIZE, - 0, 0, -1, b, sizeof(*b)); -} - -void -parent_mda_tempfail(struct smtpd *env, struct batch *b) -{ - imsg_compose_event(env->sc_ievs[PROC_MDA], IMSG_MDA_FINALIZE, - 0, 0, -1, b, sizeof(*b)); -} - -void -parent_mda_success(struct smtpd *env, struct batch *b) -{ - b->message.status &= ~S_MESSAGE_TEMPFAILURE; - imsg_compose_event(env->sc_ievs[PROC_MDA], IMSG_MDA_FINALIZE, - 0, 0, -1, b, sizeof(*b)); -} - SPLAY_GENERATE(childtree, child, entry, child_cmp); diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h index 339a26d733f..1ad585d78ac 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.171 2010/04/11 22:46:28 jacekm Exp $ */ +/* $OpenBSD: smtpd.h,v 1.172 2010/04/19 08:14:07 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -137,7 +137,8 @@ enum imsg_type { IMSG_LKA_RCPT, IMSG_LKA_SECRET, IMSG_LKA_RULEMATCH, - IMSG_MDA_FINALIZE, + IMSG_MDA_SESS_NEW, + IMSG_MDA_DONE, IMSG_MFA_RCPT, IMSG_MFA_MAIL, @@ -166,10 +167,7 @@ enum imsg_type { IMSG_PARENT_ENQUEUE_OFFLINE, IMSG_PARENT_FORWARD_OPEN, - IMSG_PARENT_MAILBOX_OPEN, - IMSG_PARENT_MESSAGE_OPEN, - IMSG_PARENT_MAILDIR_RENAME, - IMSG_PARENT_MAILDIR_FAIL, + IMSG_PARENT_FORK_MDA, IMSG_PARENT_STATS, IMSG_PARENT_AUTHENTICATE, @@ -409,7 +407,6 @@ enum message_type { }; enum message_status { - S_MESSAGE_LOCKFAILURE = 0x1, S_MESSAGE_PERMFAILURE = 0x2, S_MESSAGE_TEMPFAILURE = 0x4, S_MESSAGE_REJECTED = 0x8, @@ -467,24 +464,13 @@ enum batch_type { struct batch { SPLAY_ENTRY(batch) b_nodes; - u_int64_t id; enum batch_type type; struct rule rule; - struct smtpd *env; - char message_id[MAX_ID_SIZE]; char hostname[MAXHOSTNAMELEN]; - - struct message message; TAILQ_HEAD(, message) messages; - - FILE *mboxfp; - FILE *datafp; - struct msgbuf w; - int cleanup_parent; - struct event ev; }; enum child_type { @@ -496,11 +482,11 @@ enum child_type { struct child { SPLAY_ENTRY(child) entry; - pid_t pid; enum child_type type; enum smtp_proc_type title; - struct batch mda_batch; + int mda_out; + u_int32_t mda_id; }; enum session_state { @@ -633,6 +619,7 @@ struct smtpd { SPLAY_HEAD(childtree, child) children; SPLAY_HEAD(lkatree, lkasession) lka_sessions; SPLAY_HEAD(mtatree, mta_session) mta_sessions; + LIST_HEAD(mdalist, mda_session) mda_sessions; struct stats *stats; }; @@ -746,6 +733,21 @@ struct secret { char secret[MAX_LINE_SIZE]; }; +struct mda_session { + LIST_ENTRY(mda_session) entry; + struct message msg; + struct msgbuf w; + struct event ev; + u_int32_t id; + FILE *datafp; +}; + +struct deliver { + char to[PATH_MAX]; + char user[MAXLOGNAME]; + short mode; +}; + struct rulematch { u_int64_t id; struct submit_status ss; |