diff options
author | Jacek Masiulaniec <jacekm@cvs.openbsd.org> | 2010-06-01 19:47:10 +0000 |
---|---|---|
committer | Jacek Masiulaniec <jacekm@cvs.openbsd.org> | 2010-06-01 19:47:10 +0000 |
commit | d481f4e5ff358273fff595ce92426d61e61f20ef (patch) | |
tree | 549f1b72b9d56ecb755a3a41b8249d6e1a46528d /usr.sbin | |
parent | fdc0afec05f06bcbcb993907421d9070c9987324 (diff) |
New queue doesn't compile on gcc2, back out. Spotted by deraadt@
Diffstat (limited to 'usr.sbin')
30 files changed, 3417 insertions, 2667 deletions
diff --git a/usr.sbin/smtpd/aliases.c b/usr.sbin/smtpd/aliases.c index 25c232fb264..f9dfa1fcee7 100644 --- a/usr.sbin/smtpd/aliases.c +++ b/usr.sbin/smtpd/aliases.c @@ -1,4 +1,4 @@ -/* $OpenBSD: aliases.c,v 1.34 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: aliases.c,v 1.35 2010/06/01 19:47:08 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -67,13 +67,20 @@ aliases_exist(struct smtpd *env, objid_t mapid, char *username) } int -aliases_get(struct smtpd *env, objid_t mapid, struct expandtree *tree, char *username) +aliases_get(struct smtpd *env, objid_t mapid, struct expandtree *expandtree, char *username) { + struct map *map; struct map_alias *map_alias; struct expandnode *expnode; + char buf[MAXLOGNAME]; size_t nbaliases; - map_alias = map_lookup(env, mapid, username, K_ALIAS); + map = map_find(env, mapid); + if (map == NULL) + return 0; + + lowercase(buf, username, sizeof(buf)); + map_alias = map_lookup(env, mapid, buf, K_ALIAS); if (map_alias == NULL) return 0; @@ -81,9 +88,9 @@ aliases_get(struct smtpd *env, objid_t mapid, struct expandtree *tree, char *use nbaliases = 0; RB_FOREACH(expnode, expandtree, &map_alias->expandtree) { if (expnode->type == EXPAND_INCLUDE) - nbaliases += aliases_expand_include(tree, expnode->u.filename); + nbaliases += aliases_expand_include(expandtree, expnode->u.filename); else { - expandtree_increment_node(tree, expnode); + expandtree_increment_node(expandtree, expnode); nbaliases++; } } diff --git a/usr.sbin/smtpd/bounce.c b/usr.sbin/smtpd/bounce.c new file mode 100644 index 00000000000..0ad6cd5795c --- /dev/null +++ b/usr.sbin/smtpd/bounce.c @@ -0,0 +1,167 @@ +/* $OpenBSD: bounce.c,v 1.21 2010/06/01 19:47:08 jacekm Exp $ */ + +/* + * Copyright (c) 2009 Gilles Chehade <gilles@openbsd.org> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/param.h> +#include <sys/socket.h> + +#include <err.h> +#include <event.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "smtpd.h" +#include "client.h" + +struct client_ctx { + struct event ev; + struct message m; + struct smtp_client *pcb; + struct smtpd *env; +}; + +void bounce_event(int, short, void *); + +int +bounce_session(struct smtpd *env, int fd, struct message *messagep) +{ + struct client_ctx *cc = NULL; + int msgfd = -1; + char *reason; + + /* get message content */ + if ((msgfd = queue_open_message_file(messagep->message_id)) == -1) + goto fail; + + /* init smtp session */ + if ((cc = calloc(1, sizeof(*cc))) == NULL) + goto fail; + cc->pcb = client_init(fd, msgfd, env->sc_hostname, 1); + cc->env = env; + cc->m = *messagep; + + client_ssl_optional(cc->pcb); + client_sender(cc->pcb, ""); + client_rcpt(cc->pcb, NULL, "%s@%s", messagep->sender.user, + messagep->sender.domain); + + /* Construct an appropriate reason line. */ + reason = messagep->session_errorline; + if (strlen(reason) > 4 && (*reason == '1' || *reason == '6')) + reason += 4; + + /* create message header */ + /* XXX - The Date: header should be added during SMTP pickup. */ + client_printf(cc->pcb, + "Subject: Delivery status notification\n" + "From: Mailer Daemon <MAILER-DAEMON@%s>\n" + "To: %s@%s\n" + "Date: %s\n" + "\n" + "Hi !\n" + "\n" + "This is the MAILER-DAEMON, please DO NOT REPLY to this e-mail.\n" + "An error has occurred while attempting to deliver a message.\n" + "\n" + "Recipient: %s@%s\n" + "Reason:\n" + "%s\n" + "\n" + "Below is a copy of the original message:\n" + "\n", + env->sc_hostname, + messagep->sender.user, messagep->sender.domain, + time_to_text(time(NULL)), + messagep->recipient.user, messagep->recipient.domain, + reason); + + /* setup event */ + session_socket_blockmode(fd, BM_NONBLOCK); + event_set(&cc->ev, fd, EV_READ|EV_WRITE, bounce_event, cc); + event_add(&cc->ev, &cc->pcb->timeout); + + return 1; +fail: + close(msgfd); + if (cc && cc->pcb) + client_close(cc->pcb); + free(cc); + return 0; +} + +void +bounce_event(int fd, short event, void *p) +{ + struct client_ctx *cc = p; + char *ep = NULL; + + if (event & EV_TIMEOUT) { + ep = "150 timeout"; + goto out; + } + + switch (client_talk(cc->pcb, event & EV_WRITE)) { + case CLIENT_STOP_WRITE: + goto ro; + case CLIENT_WANT_WRITE: + goto rw; + case CLIENT_RCPT_FAIL: + ep = cc->pcb->reply; + break; + case CLIENT_DONE: + ep = cc->pcb->status; + break; + default: + fatalx("bounce_event: unexpected code"); + } + +out: + if (*ep == '2') + queue_remove_envelope(&cc->m); + else { + if (*ep == '5' || *ep == '6') + cc->m.status = S_MESSAGE_PERMFAILURE; + else + cc->m.status = S_MESSAGE_TEMPFAILURE; + message_set_errormsg(&cc->m, "%s", ep); + queue_message_update(&cc->m); + } + + cc->env->stats->runner.active--; + cc->env->stats->runner.bounces_active--; + client_close(cc->pcb); + free(cc); + return; + +ro: + event_set(&cc->ev, fd, EV_READ, bounce_event, cc); + event_add(&cc->ev, &cc->pcb->timeout); + return; + +rw: + event_set(&cc->ev, fd, EV_READ|EV_WRITE, bounce_event, cc); + event_add(&cc->ev, &cc->pcb->timeout); +} diff --git a/usr.sbin/smtpd/client.c b/usr.sbin/smtpd/client.c index c9687750c1f..1f60c642df3 100644 --- a/usr.sbin/smtpd/client.c +++ b/usr.sbin/smtpd/client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: client.c,v 1.29 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: client.c,v 1.30 2010/06/01 19:47:08 jacekm Exp $ */ /* * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> @@ -955,7 +955,5 @@ buf_read(int fd, struct ibuf_read *r) r->wpos += n; - //log_debug("%s: '%.*s'", __func__, sizeof(r->buf) - r->wpos, r->buf); - return (0); } diff --git a/usr.sbin/smtpd/control.c b/usr.sbin/smtpd/control.c index 4776727d3a4..3129a9abc04 100644 --- a/usr.sbin/smtpd/control.c +++ b/usr.sbin/smtpd/control.c @@ -1,4 +1,4 @@ -/* $OpenBSD: control.c,v 1.50 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: control.c,v 1.51 2010/06/01 19:47:08 jacekm Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -36,7 +36,6 @@ #include <unistd.h> #include "smtpd.h" -#include "queue_backend.h" #define CONTROL_BACKLOG 5 @@ -64,7 +63,8 @@ control_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { struct ctl_conn *c; struct reload *reload; - int error; + struct remove *rem; + struct sched *sched; if (iev->proc == PROC_SMTP) { switch (imsg->hdr.type) { @@ -81,17 +81,23 @@ control_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_QUEUE) { switch (imsg->hdr.type) { case IMSG_QUEUE_SCHEDULE: + sched = imsg->data; + c = control_connbyfd(sched->fd); + if (c == NULL) + return; + imsg_compose_event(&c->iev, + sched->ret ? IMSG_CTL_OK : IMSG_CTL_FAIL, 0, 0, -1, + NULL, 0); + return; + case IMSG_QUEUE_REMOVE: - c = control_connbyfd(imsg->hdr.peerid); + rem = imsg->data; + c = control_connbyfd(rem->fd); if (c == NULL) return; - memcpy(&error, imsg->data, sizeof error); - if (error) - imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 0, 0, - -1, NULL, 0); - else - imsg_compose_event(&c->iev, IMSG_CTL_OK, 0, 0, - -1, NULL, 0); + imsg_compose_event(&c->iev, + rem->ret ? IMSG_CTL_OK : IMSG_CTL_FAIL, 0, 0, + -1, NULL, 0); return; } } @@ -388,14 +394,47 @@ control_dispatch_ext(int fd, short event, void *arg) imsg_compose_event(&c->iev, IMSG_STATS, 0, 0, -1, env->stats, sizeof(struct stats)); break; - case IMSG_QUEUE_SCHEDULE: - case IMSG_QUEUE_REMOVE: - if (euid || IMSG_DATA_SIZE(&imsg) != sizeof(u_int64_t)) + case IMSG_QUEUE_SCHEDULE: { + struct sched *s = imsg.data; + + if (euid) goto badcred; - imsg_compose_event(env->sc_ievs[PROC_QUEUE], - imsg.hdr.type, fd, 0, -1, imsg.data, - sizeof(u_int64_t)); + + if (IMSG_DATA_SIZE(&imsg) != sizeof(*s)) + goto badcred; + + s->fd = fd; + + if (! valid_message_id(s->mid) && ! valid_message_uid(s->mid)) { + imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 0, 0, -1, + NULL, 0); + break; + } + + imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_SCHEDULE, 0, 0, -1, s, sizeof(*s)); break; + } + + case IMSG_QUEUE_REMOVE: { + struct remove *s = imsg.data; + + if (euid) + goto badcred; + + if (IMSG_DATA_SIZE(&imsg) != sizeof(*s)) + goto badcred; + + s->fd = fd; + + if (! valid_message_id(s->mid) && ! valid_message_uid(s->mid)) { + imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 0, 0, -1, + NULL, 0); + break; + } + + imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_REMOVE, 0, 0, -1, s, sizeof(*s)); + break; + } /* case IMSG_CONF_RELOAD: { struct reload r; @@ -452,19 +491,17 @@ control_dispatch_ext(int fd, short event, void *arg) if (euid) goto badcred; -#if 0 if (env->sc_flags & SMTPD_MDA_PAUSED) { imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); break; } -#endif env->sc_flags |= SMTPD_MDA_PAUSED; imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_PAUSE_LOCAL, 0, 0, -1, NULL, 0); imsg_compose_event(&c->iev, IMSG_CTL_OK, 0, 0, -1, NULL, 0); break; - case IMSG_QUEUE_PAUSE_RELAY: + case IMSG_QUEUE_PAUSE_OUTGOING: if (euid) goto badcred; @@ -475,7 +512,7 @@ control_dispatch_ext(int fd, short event, void *arg) } env->sc_flags |= SMTPD_MTA_PAUSED; imsg_compose_event(env->sc_ievs[PROC_QUEUE], - IMSG_QUEUE_PAUSE_RELAY, 0, 0, -1, NULL, 0); + IMSG_QUEUE_PAUSE_OUTGOING, 0, 0, -1, NULL, 0); imsg_compose_event(&c->iev, IMSG_CTL_OK, 0, 0, -1, NULL, 0); break; case IMSG_SMTP_PAUSE: @@ -496,19 +533,17 @@ control_dispatch_ext(int fd, short event, void *arg) if (euid) goto badcred; -#if 0 if (! (env->sc_flags & SMTPD_MDA_PAUSED)) { imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); break; } -#endif env->sc_flags &= ~SMTPD_MDA_PAUSED; imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_RESUME_LOCAL, 0, 0, -1, NULL, 0); imsg_compose_event(&c->iev, IMSG_CTL_OK, 0, 0, -1, NULL, 0); break; - case IMSG_QUEUE_RESUME_RELAY: + case IMSG_QUEUE_RESUME_OUTGOING: if (euid) goto badcred; @@ -519,7 +554,7 @@ control_dispatch_ext(int fd, short event, void *arg) } env->sc_flags &= ~SMTPD_MTA_PAUSED; imsg_compose_event(env->sc_ievs[PROC_QUEUE], - IMSG_QUEUE_RESUME_RELAY, 0, 0, -1, NULL, 0); + IMSG_QUEUE_RESUME_OUTGOING, 0, 0, -1, NULL, 0); imsg_compose_event(&c->iev, IMSG_CTL_OK, 0, 0, -1, NULL, 0); break; case IMSG_SMTP_RESUME: diff --git a/usr.sbin/smtpd/enqueue.c b/usr.sbin/smtpd/enqueue.c index 9544425b451..ae9ba28db44 100644 --- a/usr.sbin/smtpd/enqueue.c +++ b/usr.sbin/smtpd/enqueue.c @@ -1,4 +1,4 @@ -/* $OpenBSD: enqueue.c,v 1.34 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: enqueue.c,v 1.35 2010/06/01 19:47:08 jacekm Exp $ */ /* * Copyright (c) 2005 Henning Brauer <henning@bulabula.org> @@ -163,6 +163,7 @@ enqueue(int argc, char *argv[]) case 'x': break; case 'q': + /* XXX: implement "process all now" */ return (0); default: usage(); diff --git a/usr.sbin/smtpd/expand.c b/usr.sbin/smtpd/expand.c index 07a4b371c83..bc2df9c4e62 100644 --- a/usr.sbin/smtpd/expand.c +++ b/usr.sbin/smtpd/expand.c @@ -1,4 +1,4 @@ -/* $OpenBSD: expand.c,v 1.6 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: expand.c,v 1.7 2010/06/01 19:47:08 jacekm Exp $ */ /* * Copyright (c) 2009 Gilles Chehade <gilles@openbsd.org> @@ -33,26 +33,27 @@ #include "smtpd.h" struct expandnode * -expandtree_lookup(struct expandtree *tree, struct expandnode *node) +expandtree_lookup(struct expandtree *expandtree, struct expandnode *node) { struct expandnode key; key = *node; - return RB_FIND(expandtree, tree, &key); + return RB_FIND(expandtree, expandtree, &key); } void -expandtree_increment_node(struct expandtree *tree, struct expandnode *node) +expandtree_increment_node(struct expandtree *expandtree, struct expandnode *node) { struct expandnode *p; - p = expandtree_lookup(tree, node); + p = expandtree_lookup(expandtree, node); if (p == NULL) { - p = malloc(sizeof *node); + p = calloc(1, sizeof(struct expandnode)); if (p == NULL) - fatal(NULL); - *p = *node; /* XXX p->refcnt == node->refcnt */ - RB_INSERT(expandtree, tree, p); + fatal("calloc"); + *p = *node; + if (RB_INSERT(expandtree, expandtree, p)) + fatalx("expandtree_increment_node: node already exists"); } p->refcnt++; } @@ -85,8 +86,10 @@ void expandtree_free_nodes(struct expandtree *expandtree) { struct expandnode *p; + struct expandnode *nxt; - while ((p = RB_MIN(expandtree, expandtree))) { + for (p = RB_MIN(expandtree, expandtree); p != NULL; p = nxt) { + nxt = RB_NEXT(expandtree, expandtree, p); RB_REMOVE(expandtree, expandtree, p); free(p); } diff --git a/usr.sbin/smtpd/lka.c b/usr.sbin/smtpd/lka.c index 996e3bc6a25..b1247893e51 100644 --- a/usr.sbin/smtpd/lka.c +++ b/usr.sbin/smtpd/lka.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka.c,v 1.111 2010/06/01 02:19:56 jacekm Exp $ */ +/* $OpenBSD: lka.c,v 1.112 2010/06/01 19:47:08 jacekm Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -52,35 +52,30 @@ int lka_resolve_node(struct smtpd *, char *tag, struct path *, struct expandnod int lka_verify_mail(struct smtpd *, struct path *); struct rule *ruleset_match(struct smtpd *, char *, struct path *, struct sockaddr_storage *); int lka_resolve_path(struct smtpd *, struct lkasession *, struct path *); -struct lkasession *lka_session_init(struct smtpd *, struct message *); -void lka_request_forwardfile(struct smtpd *, struct lkasession *, struct path *); +struct lkasession *lka_session_init(struct smtpd *, struct submit_status *); +void lka_request_forwardfile(struct smtpd *, struct lkasession *, char *); void lka_clear_expandtree(struct expandtree *); void lka_clear_deliverylist(struct deliverylist *); -char *lka_encode_secret(struct map_secret *); +int lka_encode_credentials(char *, size_t, struct map_secret *); size_t lka_expand(char *, size_t, struct path *); void lka_rcpt_action(struct smtpd *, char *, struct path *); void lka_session_destroy(struct smtpd *, struct lkasession *); void lka_expansion_done(struct smtpd *, struct lkasession *); -void lka_session_fail(struct smtpd *, struct lkasession *); -void lka_queue_append(struct smtpd *, struct lkasession *, int); - -u_int32_t lka_id; +void lka_session_fail(struct smtpd *, struct lkasession *, struct submit_status *); void lka_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { struct lkasession skey; + struct submit_status *ss; struct forward_req *fwreq; struct lkasession *s; - struct message *m; + struct secret *secret; struct mapel *mapel; struct rule *rule; struct path *path; struct map *map; - struct map_secret *map_secret; - char *secret; void *tmp; - int status; if (imsg->hdr.type == IMSG_DNS_A || imsg->hdr.type == IMSG_DNS_MX || imsg->hdr.type == IMSG_DNS_PTR) { @@ -91,67 +86,67 @@ lka_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_MFA) { switch (imsg->hdr.type) { case IMSG_LKA_MAIL: - m = imsg->data; - status = 0; - if (m->sender.user[0] || m->sender.domain[0]) - if (! lka_verify_mail(env, &m->sender)) - status = S_MESSAGE_PERMFAILURE; - imsg_compose_event(iev, IMSG_LKA_MAIL, - m->id, 0, -1, &status, sizeof status); + ss = imsg->data; + ss->code = 530; + if (ss->u.path.user[0] == '\0' && + ss->u.path.domain[0] == '\0') + ss->code = 250; + else + if (lka_verify_mail(env, &ss->u.path)) + ss->code = 250; + imsg_compose_event(iev, IMSG_LKA_MAIL, 0, 0, -1, ss, + sizeof *ss); return; - case IMSG_LKA_RCPT: - m = imsg->data; - rule = ruleset_match(env, m->tag, &m->recipient, &m->session_ss); - if (rule == NULL) { - log_debug("lka: rule not found"); - status = S_MESSAGE_PERMFAILURE; - imsg_compose_event(iev, IMSG_LKA_RULEMATCH, m->id, 0, -1, - &status, sizeof status); - return; + case IMSG_LKA_RULEMATCH: + ss = imsg->data; + ss->code = 530; + rule = ruleset_match(env, ss->msg.tag, &ss->u.path, + &ss->ss); + if (rule) { + ss->code = 250; + ss->u.path.rule = *rule; } - m->recipient.rule = *rule; - s = lka_session_init(env, m); - if (! lka_resolve_path(env, s, &m->recipient)) - lka_session_fail(env, s); - else - lka_expand_pickup(env, s); + imsg_compose_event(iev, IMSG_LKA_RULEMATCH, 0, 0, -1, + ss, sizeof *ss); return; - } - } - if (iev->proc == PROC_QUEUE) { - switch (imsg->hdr.type) { - case IMSG_QUEUE_APPEND: - skey.id = imsg->hdr.peerid; - s = SPLAY_FIND(lkatree, &env->lka_sessions, &skey); - if (s == NULL) - fatalx("lka: session missing"); - memcpy(&status, imsg->data, sizeof status); - lka_queue_append(env, s, status); + case IMSG_LKA_RCPT: + ss = imsg->data; + ss->code = 250; + path = &ss->u.path; + s = lka_session_init(env, ss); + if (! lka_resolve_path(env, s, path)) + lka_session_fail(env, s, ss); + else + lka_expand_pickup(env, s); return; } } if (iev->proc == PROC_MTA) { switch (imsg->hdr.type) { - case IMSG_LKA_SECRET: + case IMSG_LKA_SECRET: { + struct map_secret *map_secret; + secret = imsg->data; map = map_findbyname(env, "secrets"); if (map == NULL) fatalx("lka: secrets map not found"); - map_secret = map_lookup(env, map->m_id, imsg->data, K_SECRET); - if (map_secret) - secret = lka_encode_secret(map_secret); - else - secret = ""; - if (*secret == '\0') - log_warnx("%s secret not found", (char *)imsg->data); - imsg_compose_event(iev, IMSG_LKA_SECRET, - imsg->hdr.peerid, 0, -1, secret, - strlen(secret) + 1); + map_secret = map_lookup(env, map->m_id, secret->host, K_SECRET); + log_debug("lka: %s secret lookup (%d)", secret->host, + map_secret != NULL); + secret->secret[0] = '\0'; + if (map_secret == NULL) + log_warnx("%s secret not found", secret->host); + else if (lka_encode_credentials(secret->secret, + sizeof secret->secret, map_secret) == 0) + log_warnx("%s secret parse fail", secret->host); + imsg_compose_event(iev, IMSG_LKA_SECRET, 0, 0, -1, secret, + sizeof *secret); free(map_secret); return; } + } } if (iev->proc == PROC_PARENT) { @@ -506,7 +501,7 @@ lka_resolve_node(struct smtpd *env, char *tag, struct path *path, struct expandn sizeof(path->user)) >= sizeof(path->user)) return 0; - if (1 || psave.domain[0] == '\0') { + if (psave.domain[0] == '\0') { if (strlcpy(path->domain, env->sc_hostname, sizeof(path->domain)) >= sizeof(path->domain)) return 0; @@ -562,34 +557,32 @@ lka_resolve_node(struct smtpd *env, char *tag, struct path *path, struct expandn } void -lka_expand_pickup(struct smtpd *env, struct lkasession *s) +lka_expand_pickup(struct smtpd *env, struct lkasession *lkasession) { int ret; - if (s->pending) - return; - - if (s->flags & F_ERROR) { - lka_expansion_done(env, s); - return; - } - /* we want to do five iterations of lka_expand_resume() but * we need to be interruptible in case lka_expand_resume() * has sent an imsg and expects an answer. */ - while (s->iterations < 5) { - s->iterations++; - ret = lka_expand_resume(env, s); - if (ret == -1) - s->flags |= F_ERROR; - if (s->pending) - return; - if (ret == 0) + ret = 0; + while (! (lkasession->flags & F_ERROR) && + ! lkasession->pending && lkasession->iterations < 5) { + ++lkasession->iterations; + ret = lka_expand_resume(env, lkasession); + if (ret == -1) { + lkasession->ss.code = 530; + lkasession->flags |= F_ERROR; + } + + if (lkasession->pending || ret <= 0) break; } - lka_expansion_done(env, s); + if (lkasession->pending) + return; + + lka_expansion_done(env, lkasession); } int @@ -638,75 +631,45 @@ lka_expand_resume(struct smtpd *env, struct lkasession *lkasession) } void -lka_expansion_done(struct smtpd *env, struct lkasession *s) +lka_expansion_done(struct smtpd *env, struct lkasession *lkasession) { - int status; + struct message message; + struct path *path; /* delivery list is empty OR expansion led to an error, reject */ - if (TAILQ_EMPTY(&s->deliverylist) || s->flags & F_ERROR) { - if (TAILQ_EMPTY(&s->deliverylist)) - log_debug("lka_expansion_done: list empty"); - else - log_debug("lka_expansion_done: session error"); - status = S_MESSAGE_PERMFAILURE; - imsg_compose_event(env->sc_ievs[PROC_MFA], IMSG_LKA_RCPT, - s->message.id, 0, -1, &status, sizeof status); - lka_clear_expandtree(&s->expandtree); - lka_clear_deliverylist(&s->deliverylist); - lka_session_destroy(env, s); - } else - lka_queue_append(env, s, 0); -} - -void -lka_queue_append(struct smtpd *env, struct lkasession *s, int status) -{ - struct path *path; - struct message message; - struct passwd *pw; - const char *errstr; - char *sep; - uid_t uid; - - path = TAILQ_FIRST(&s->deliverylist); - - if (path == NULL || status) { - imsg_compose_event(env->sc_ievs[PROC_MFA], IMSG_LKA_RCPT, - s->message.id, 0, -1, &status, sizeof status); - lka_clear_expandtree(&s->expandtree); - lka_clear_deliverylist(&s->deliverylist); - lka_session_destroy(env, s); - return; + if (TAILQ_FIRST(&lkasession->deliverylist) == NULL || + lkasession->flags & F_ERROR) { + imsg_compose_event(env->sc_ievs[PROC_MFA], IMSG_LKA_RCPT, 0, 0, + -1, &lkasession->ss, sizeof(struct submit_status)); + goto done; } - /* send next item to queue */ - message = s->message; - lka_expand(path->rule.r_value.path, sizeof(path->rule.r_value.path), path); - message.recipient = *path; - sep = strchr(message.session_hostname, '@'); - if (sep) { - *sep = '\0'; - uid = strtonum(message.session_hostname, 0, UID_MAX, &errstr); - if (errstr) - fatalx("lka: invalid uid"); - pw = getpwuid(uid); - if (pw == NULL) - fatalx("lka: non-existent uid"); /* XXX */ - strlcpy(message.sender.pw_name, pw->pw_name, sizeof message.sender.pw_name); + /* process the delivery list and submit envelopes to queue */ + message = lkasession->message; + while ((path = TAILQ_FIRST(&lkasession->deliverylist)) != NULL) { + lka_expand(path->rule.r_value.path, + sizeof(path->rule.r_value.path), path); + message.recipient = *path; + queue_submit_envelope(env, &message); + + TAILQ_REMOVE(&lkasession->deliverylist, path, entry); + free(path); } - imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_APPEND, - s->id, 0, -1, &message, sizeof message); - TAILQ_REMOVE(&s->deliverylist, path, entry); - free(path); + queue_commit_envelopes(env, &message); + +done: + lka_clear_expandtree(&lkasession->expandtree); + lka_clear_deliverylist(&lkasession->deliverylist); + lka_session_destroy(env, lkasession); } int -lka_resolve_path(struct smtpd *env, struct lkasession *s, struct path *path) +lka_resolve_path(struct smtpd *env, struct lkasession *lkasession, struct path *path) { if (IS_RELAY(*path)) { path = path_dup(path); path->flags |= F_PATH_RELAY; - TAILQ_INSERT_TAIL(&s->deliverylist, path, entry); + TAILQ_INSERT_TAIL(&lkasession->deliverylist, path, entry); return 1; } @@ -726,27 +689,37 @@ lka_resolve_path(struct smtpd *env, struct lkasession *s, struct path *path) if (aliases_exist(env, path->rule.r_amap, username)) { path->flags |= F_PATH_ALIAS; - return aliases_get(env, path->rule.r_amap, - &s->expandtree, username); + if (! aliases_get(env, path->rule.r_amap, + &lkasession->expandtree, path->user)) + return 0; + return 1; } + if (strlen(username) >= MAXLOGNAME) + return 0; + path->flags |= F_PATH_ACCOUNT; pw = getpwnam(username); if (pw == NULL) return 0; - strlcpy(path->pw_name, pw->pw_name, sizeof path->pw_name); + (void)strlcpy(path->pw_name, pw->pw_name, + sizeof(path->pw_name)); if (path->flags & F_PATH_FORWARDED) - TAILQ_INSERT_TAIL(&s->deliverylist, path, entry); + TAILQ_INSERT_TAIL(&lkasession->deliverylist, path, entry); else - lka_request_forwardfile(env, s, path); + lka_request_forwardfile(env, lkasession, path->pw_name); + return 1; } case C_VDOM: { if (aliases_virtual_exist(env, path->rule.r_condition.c_map, path)) { path->flags |= F_PATH_VIRTUAL; - return aliases_virtual_get(env, path->rule.r_condition.c_map, &s->expandtree, path); + if (! aliases_virtual_get(env, path->rule.r_condition.c_map, + &lkasession->expandtree, path)) + return 0; + return 1; } break; } @@ -812,75 +785,72 @@ lka_clear_deliverylist(struct deliverylist *deliverylist) } } -char * -lka_encode_secret(struct map_secret *map_secret) +int +lka_encode_credentials(char *dst, size_t size, struct map_secret *map_secret) { - static char dst[1024]; - char *src; - int src_sz; + char *buf; + int buflen; - src_sz = asprintf(&src, "%c%s%c%s", '\0', map_secret->username, '\0', - map_secret->password); - if (src_sz == -1) + if ((buflen = asprintf(&buf, "%c%s%c%s", '\0', map_secret->username, + '\0', map_secret->password)) == -1) fatal(NULL); - if (__b64_ntop(src, src_sz, dst, sizeof dst) == -1) { - free(src); - return NULL; + + if (__b64_ntop((unsigned char *)buf, buflen, dst, size) == -1) { + free(buf); + return 0; } - dst[sizeof(dst) - 1] = '\0'; - return dst; + free(buf); + return 1; } struct lkasession * -lka_session_init(struct smtpd *env, struct message *m) +lka_session_init(struct smtpd *env, struct submit_status *ss) { - struct lkasession *s; - - s = calloc(1, sizeof *s); - if (s == NULL) - fatal(NULL); - - s->id = lka_id++; - s->path = m->recipient; - s->message = *m; - - RB_INIT(&s->expandtree); - TAILQ_INIT(&s->deliverylist); - SPLAY_INSERT(lkatree, &env->lka_sessions, s); - - return s; + struct lkasession *lkasession; + + lkasession = calloc(1, sizeof(struct lkasession)); + if (lkasession == NULL) + fatal("lka_session_init: calloc"); + + lkasession->id = generate_uid(); + lkasession->path = ss->u.path; + lkasession->message = ss->msg; + lkasession->ss = *ss; + + RB_INIT(&lkasession->expandtree); + TAILQ_INIT(&lkasession->deliverylist); + SPLAY_INSERT(lkatree, &env->lka_sessions, lkasession); + + return lkasession; } void -lka_session_fail(struct smtpd *env, struct lkasession *s) +lka_session_fail(struct smtpd *env, struct lkasession *lkasession, struct submit_status *ss) { - int status; - - log_debug("lka: initina lka_resolve_path failed"); - status = S_MESSAGE_PERMFAILURE; - imsg_compose_event(env->sc_ievs[PROC_MFA], IMSG_LKA_RCPT, - s->message.id, 0, -1, &status, sizeof status); - lka_session_destroy(env, s); + ss->code = 530; + imsg_compose_event(env->sc_ievs[PROC_MFA], IMSG_LKA_RCPT, 0, 0, -1, + ss, sizeof(*ss)); + lka_session_destroy(env, lkasession); } void -lka_session_destroy(struct smtpd *env, struct lkasession *s) +lka_session_destroy(struct smtpd *env, struct lkasession *lkasession) { - SPLAY_REMOVE(lkatree, &env->lka_sessions, s); - free(s); + SPLAY_REMOVE(lkatree, &env->lka_sessions, lkasession); + free(lkasession); } void -lka_request_forwardfile(struct smtpd *env, struct lkasession *s, struct path *path) +lka_request_forwardfile(struct smtpd *env, struct lkasession *lkasession, char *username) { struct forward_req fwreq; - fwreq.id = s->id; - strlcpy(fwreq.pw_name, path->pw_name, sizeof fwreq.pw_name); + fwreq.id = lkasession->id; + (void)strlcpy(fwreq.pw_name, username, sizeof(fwreq.pw_name)); imsg_compose_event(env->sc_ievs[PROC_PARENT], IMSG_PARENT_FORWARD_OPEN, 0, 0, -1, - &fwreq, sizeof fwreq); - s->pending++; + &fwreq, sizeof(fwreq)); + ++lkasession->pending; } SPLAY_GENERATE(lkatree, lkasession, nodes, lkasession_cmp); diff --git a/usr.sbin/smtpd/log.c b/usr.sbin/smtpd/log.c index 50986b6d2c2..b26079195b5 100644 --- a/usr.sbin/smtpd/log.c +++ b/usr.sbin/smtpd/log.c @@ -1,4 +1,4 @@ -/* $OpenBSD: log.c,v 1.6 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: log.c,v 1.7 2010/06/01 19:47:09 jacekm Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> @@ -51,7 +51,7 @@ log_init(int n_debug) verbose = n_debug; if (!debug) - openlog(__progname, LOG_NDELAY, LOG_MAIL); + openlog(__progname, LOG_PID | LOG_NDELAY, LOG_MAIL); tzset(); } diff --git a/usr.sbin/smtpd/map.c b/usr.sbin/smtpd/map.c index dba969a76a1..14aec231708 100644 --- a/usr.sbin/smtpd/map.c +++ b/usr.sbin/smtpd/map.c @@ -1,4 +1,4 @@ -/* $OpenBSD: map.c,v 1.17 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: map.c,v 1.18 2010/06/01 19:47:09 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -64,12 +64,13 @@ map_find(struct smtpd *env, objid_t id) void * map_lookup(struct smtpd *env, objid_t mapid, char *key, enum map_kind kind) { - void *hdl; - char *result, *tmp; + void *hdl = NULL; + char *result = NULL; + char *ret = NULL; size_t len; struct map *map; - struct map_backend *backend; - struct map_parser *parser; + struct map_backend *backend = NULL; + struct map_parser *parser = NULL; map = map_find(env, mapid); if (map == NULL) @@ -84,17 +85,16 @@ map_lookup(struct smtpd *env, objid_t mapid, char *key, enum map_kind kind) return NULL; } - result = backend->get(hdl, key, &len); - if (result == NULL) + ret = result = backend->get(hdl, key, &len); + if (ret == NULL) goto end; - if (parser->extract) { - tmp = result; - result = parser->extract(key, result, len); - free(tmp); + if (parser->extract != NULL) { + ret = parser->extract(key, result, len); + free(result); } end: backend->close(hdl); - return result; + return ret; } diff --git a/usr.sbin/smtpd/mda.c b/usr.sbin/smtpd/mda.c index 31e4197df55..7bb30913f3a 100644 --- a/usr.sbin/smtpd/mda.c +++ b/usr.sbin/smtpd/mda.c @@ -1,9 +1,9 @@ -/* $OpenBSD: mda.c,v 1.45 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: mda.c,v 1.46 2010/06/01 19:47:09 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> - * Copyright (c) 2009-2010 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -36,7 +36,6 @@ #include <vis.h> #include "smtpd.h" -#include "queue_backend.h" void mda_imsg(struct smtpd *, struct imsgev *, struct imsg *); __dead void mda_shutdown(void); @@ -47,65 +46,74 @@ void mda_store(struct mda_session *); void mda_store_event(int, short, void *); struct mda_session *mda_lookup(struct smtpd *, u_int32_t); +u_int32_t mda_id; + void mda_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { char output[128], *error, *parent_error; struct deliver deliver; struct mda_session *s; - struct action *action; - size_t action_sz; + struct path *path; if (iev->proc == PROC_QUEUE) { switch (imsg->hdr.type) { - case IMSG_BATCH_CREATE: - s = malloc(sizeof *s); + case IMSG_MDA_SESS_NEW: + /* make new session based on provided args */ + s = calloc(1, sizeof *s); if (s == NULL) fatal(NULL); msgbuf_init(&s->w); - bzero(&s->ev, sizeof s->ev); - s->id = imsg->hdr.peerid; - s->content_id = *(u_int64_t *)imsg->data; + 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); - return; - - case IMSG_BATCH_APPEND: - LIST_FOREACH(s, &env->mda_sessions, entry) - if (s->id == imsg->hdr.peerid) - break; - if (s == NULL) - fatalx("mda: bogus append"); - action = imsg->data; - s->action_id = action->id; - s->auxraw = strdup(action->arg); - if (s->auxraw == NULL) - fatal(NULL); - auxsplit(&s->aux, s->auxraw); - return; - case IMSG_BATCH_CLOSE: - LIST_FOREACH(s, &env->mda_sessions, entry) - if (s->id == imsg->hdr.peerid) - break; - if (s == NULL) - fatalx("mda: bogus close"); - memcpy(&s->birth, imsg->data, sizeof s->birth); - - /* request helper process from parent */ - if (s->aux.mode[0] == 'M') { - deliver.mode = 'P'; - strlcpy(deliver.user, "root", sizeof deliver.user); + /* 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; + + case A_MBOX: + deliver.mode = A_EXT; + strlcpy(deliver.user, "root", + sizeof deliver.user); snprintf(deliver.to, sizeof deliver.to, - "exec /usr/libexec/mail.local %s", - s->aux.user_to); - } else { - deliver.mode = s->aux.mode[0]; - strlcpy(deliver.user, s->aux.user_to, sizeof deliver.user); - strlcpy(deliver.to, s->aux.path, sizeof deliver.to); + "%s -f %s@%s %s", PATH_MAILLOCAL, + s->msg.sender.user, s->msg.sender.domain, + path->pw_name); + 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 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; + + default: + fatalx("mda: unknown rule action"); } + imsg_compose_event(env->sc_ievs[PROC_PARENT], IMSG_PARENT_FORK_MDA, s->id, 0, -1, &deliver, sizeof deliver); @@ -128,10 +136,6 @@ mda_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) case IMSG_MDA_DONE: s = mda_lookup(env, imsg->hdr.peerid); - /* all parent errors are temporary */ - if (asprintf(&parent_error, "100 %s", (char *)imsg->data) < 0) - fatal("mda: asprintf"); - /* * Grab last line of mda stdout/stderr if available. */ @@ -158,11 +162,12 @@ mda_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) buf[len] = '\0'; ln = buf; } - strlcpy(output, "100 \"", sizeof output); - strnvis(output + 5, ln, - sizeof(output) - 6, + 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); @@ -173,11 +178,11 @@ mda_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) * child's output, the latter having preference over * the former. */ - if (strcmp(parent_error + 4, "exited okay") == 0) { + error = NULL; + parent_error = imsg->data; + if (strcmp(parent_error, "exited okay") == 0) { if (!feof(s->datafp) || s->w.queued) - error = "100 mda exited prematurely"; - else - error = "200 ok"; + error = "mda exited prematurely"; } else { if (output[0]) error = output; @@ -186,24 +191,35 @@ mda_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) } /* update queue entry */ - action_sz = sizeof *action + strlen(error) + 1; - action = malloc(action_sz); - if (action == NULL) - fatal(NULL); - action->id = s->action_id; - strlcpy(action->arg, error, action_sz - sizeof *action); + 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_BATCH_UPDATE, s->id, 0, -1, action, action_sz); - imsg_compose_event(env->sc_ievs[PROC_QUEUE], - IMSG_BATCH_DONE, s->id, 0, -1, NULL, 0); + IMSG_QUEUE_MESSAGE_UPDATE, 0, 0, -1, &s->msg, + sizeof s->msg); + + /* + * 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 */ - log_info("%s: to=%s, delay=%d, stat=%s%s%s", - queue_be_decode(s->content_id), rcpt_pretty(&s->aux), - time(NULL) - s->birth, - *error == '2' ? "Sent" : "Error (", - *error == '2' ? "" : error + 4, - *error == '2' ? "" : ")"); + 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); @@ -213,9 +229,11 @@ mda_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) fclose(s->datafp); msgbuf_clear(&s->w); event_del(&s->ev); - free(s->auxraw); free(s); - free(parent_error); + + /* update queue's session count */ + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_MDA_SESS_NEW, 0, 0, -1, NULL, 0); return; case IMSG_CTL_VERBOSE: @@ -307,13 +325,13 @@ mda(struct smtpd *env) signal_add(&ev_sigint, NULL); signal_add(&ev_sigterm, NULL); signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); config_pipes(env, peers, nitems(peers)); config_peers(env, peers, nitems(peers)); mda_setup_events(env); - if (event_dispatch() < 0) - log_warn("event_dispatch"); + event_dispatch(); mda_shutdown(); return (0); @@ -326,12 +344,14 @@ mda_store(struct mda_session *s) struct ibuf *buf; int len; - /* XXX: remove user provided Return-Path, if any */ - if (s->aux.mail_from[0]) - len = asprintf(&p, "Return-Path: %s\nDelivered-To: %s\n", - s->aux.mail_from, s->aux.rcpt_to); + 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", + 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\n", s->aux.rcpt_to); + len = asprintf(&p, "Delivered-To: %s@%s\n", + s->msg.session_rcpt.user, s->msg.session_rcpt.domain); if (len == -1) fatal("mda_store: asprintf"); diff --git a/usr.sbin/smtpd/mfa.c b/usr.sbin/smtpd/mfa.c index 454b632b70a..d2289f3498a 100644 --- a/usr.sbin/smtpd/mfa.c +++ b/usr.sbin/smtpd/mfa.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mfa.c,v 1.46 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: mfa.c,v 1.47 2010/06/01 19:47:09 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -42,6 +42,7 @@ void mfa_disable_events(struct smtpd *); void mfa_test_mail(struct smtpd *, struct message *); void mfa_test_rcpt(struct smtpd *, struct message *); +void mfa_test_rcpt_resume(struct smtpd *, struct submit_status *); int strip_source_route(char *, size_t); @@ -65,15 +66,14 @@ mfa_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_LKA) { switch (imsg->hdr.type) { case IMSG_LKA_MAIL: + case IMSG_LKA_RCPT: imsg_compose_event(env->sc_ievs[PROC_SMTP], - IMSG_MFA_MAIL, imsg->hdr.peerid, 0, -1, imsg->data, - imsg->hdr.len - sizeof imsg->hdr); + IMSG_MFA_MAIL, 0, 0, -1, imsg->data, + sizeof(struct submit_status)); return; - case IMSG_LKA_RCPT: - imsg_compose_event(env->sc_ievs[PROC_SMTP], - IMSG_MFA_RCPT, imsg->hdr.peerid, 0, -1, imsg->data, - imsg->hdr.len - sizeof imsg->hdr); + case IMSG_LKA_RULEMATCH: + mfa_test_rcpt_resume(env, imsg->data); return; } } @@ -181,20 +181,38 @@ mfa(struct smtpd *env) return (0); } +int +msg_cmp(struct message *m1, struct message *m2) +{ + /* + * do not return u_int64_t's + */ + if (m1->id - m2->id > 0) + return (1); + else if (m1->id - m2->id < 0) + return (-1); + else + return (0); +} + void mfa_test_mail(struct smtpd *env, struct message *m) { - int status; + struct submit_status ss; - if (strip_source_route(m->sender.user, sizeof(m->sender.user))) + ss.id = m->id; + ss.code = 530; + ss.u.path = m->sender; + + if (strip_source_route(ss.u.path.user, sizeof(ss.u.path.user))) goto refuse; - if (! valid_localpart(m->sender.user) || - ! valid_domainpart(m->sender.domain)) { + if (! valid_localpart(ss.u.path.user) || + ! valid_domainpart(ss.u.path.domain)) { /* * "MAIL FROM:<>" is the exception we allow. */ - if (!(m->sender.user[0] == '\0' && m->sender.domain[0] == '\0')) + if (!(ss.u.path.user[0] == '\0' && ss.u.path.domain[0] == '\0')) goto refuse; } @@ -202,39 +220,61 @@ mfa_test_mail(struct smtpd *env, struct message *m) goto accept; refuse: - status = S_MESSAGE_PERMFAILURE; - imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_MAIL, m->id, 0, -1, - &status, sizeof status); + imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_MAIL, 0, 0, -1, &ss, + sizeof(ss)); return; accept: + ss.code = 250; imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_MAIL, 0, - 0, -1, m, sizeof *m); + 0, -1, &ss, sizeof(ss)); } void mfa_test_rcpt(struct smtpd *env, struct message *m) { - int status; + struct submit_status ss; + + if (! valid_message_id(m->message_id)) + fatalx("mfa_test_rcpt: received corrupted message_id"); - m->recipient = m->session_rcpt; + ss.id = m->session_id; + ss.code = 530; + ss.u.path = m->session_rcpt; + ss.ss = m->session_ss; + ss.msg = *m; + ss.msg.recipient = m->session_rcpt; + ss.flags = m->flags; - strip_source_route(m->recipient.user, sizeof(m->recipient.user)); + strip_source_route(ss.u.path.user, sizeof(ss.u.path.user)); - if (! valid_localpart(m->recipient.user) || - ! valid_domainpart(m->recipient.domain)) + if (! valid_localpart(ss.u.path.user) || + ! valid_domainpart(ss.u.path.domain)) goto refuse; - if (m->flags & F_MESSAGE_AUTHENTICATED) - m->recipient.flags |= F_PATH_AUTHENTICATED; + if (ss.flags & F_MESSAGE_AUTHENTICATED) + ss.u.path.flags |= F_PATH_AUTHENTICATED; + + imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_RULEMATCH, 0, 0, -1, + &ss, sizeof(ss)); - imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_RCPT, 0, 0, -1, - m, sizeof *m); return; refuse: - status = S_MESSAGE_PERMFAILURE; - imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_RCPT, m->id, 0, -1, - &status, sizeof status); + imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_RCPT, 0, 0, -1, &ss, + sizeof(ss)); +} + +void +mfa_test_rcpt_resume(struct smtpd *env, struct submit_status *ss) { + if (ss->code != 250) { + imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_RCPT, 0, 0, -1, ss, + sizeof(*ss)); + return; + } + + ss->msg.recipient = ss->u.path; + imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_RCPT, 0, 0, -1, + ss, sizeof(*ss)); } int diff --git a/usr.sbin/smtpd/mta.c b/usr.sbin/smtpd/mta.c index 51cf24721da..164e293c2b7 100644 --- a/usr.sbin/smtpd/mta.c +++ b/usr.sbin/smtpd/mta.c @@ -1,9 +1,9 @@ -/* $OpenBSD: mta.c,v 1.89 2010/06/01 11:05:12 jacekm Exp $ */ +/* $OpenBSD: mta.c,v 1.90 2010/06/01 19:47:09 jacekm Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> - * Copyright (c) 2009-2010 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -40,83 +40,114 @@ #include "smtpd.h" #include "client.h" -#include "queue_backend.h" void mta_imsg(struct smtpd *, struct imsgev *, struct imsg *); __dead void mta_shutdown(void); void mta_sig_handler(int, short, void *); -struct mta_session *mta_lookup(struct smtpd *, u_int32_t); +struct mta_session *mta_lookup(struct smtpd *, u_int64_t); void mta_enter_state(struct mta_session *, int, void *); void mta_pickup(struct mta_session *, void *); void mta_event(int, short, void *); void mta_status(struct mta_session *, const char *, ...); -void mta_rcpt_status(struct recipient *, char *); -void mta_rcpt_log(struct mta_session *, struct recipient *); -void mta_rcpt_done(struct mta_session *, struct recipient *); +void mta_message_status(struct message *, char *); +void mta_message_log(struct mta_session *, struct message *); +void mta_message_done(struct mta_session *, struct message *); void mta_connect_done(int, short, void *); +void mta_request_datafd(struct mta_session *); void mta_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { - struct aux aux; struct mta_session *s; - struct recipient *rcpt; struct mta_relay *relay; - struct action *action; + struct message *m; + struct secret *secret; + struct batch *b; struct dns *dns; struct ssl *ssl; - size_t rcpt_sz; if (iev->proc == PROC_QUEUE) { switch (imsg->hdr.type) { case IMSG_BATCH_CREATE: - if (imsg->fd < 0) - fatalx("mta: fd pass fail"); + b = imsg->data; s = calloc(1, sizeof *s); if (s == NULL) fatal(NULL); - TAILQ_INIT(&s->recipients); - TAILQ_INIT(&s->relays); - s->id = imsg->hdr.peerid; + s->id = b->id; s->state = MTA_INIT; s->env = env; - s->datafd = imsg->fd; - memcpy(&s->content_id, imsg->data, - sizeof s->content_id); + s->datafd = -1; + + /* establish host name */ + if (b->rule.r_action == A_RELAYVIA) + s->host = strdup(b->rule.r_value.relayhost.hostname); + else + s->host = strdup(b->hostname); + if (s->host == NULL) + fatal(NULL); + + /* establish port */ + s->port = ntohs(b->rule.r_value.relayhost.port); /* XXX */ + + /* have cert? */ + s->cert = strdup(b->rule.r_value.relayhost.cert); + if (s->cert == NULL) + fatal(NULL); + else if (s->cert[0] == '\0') { + free(s->cert); + s->cert = NULL; + } + + /* use auth? */ + if ((b->rule.r_value.relayhost.flags & F_SSL) && + (b->rule.r_value.relayhost.flags & F_AUTH)) + s->flags |= MTA_USE_AUTH; + + /* force a particular SSL mode? */ + switch (b->rule.r_value.relayhost.flags & F_SSL) { + case F_SSL: + s->flags |= MTA_FORCE_ANYSSL; + break; + + case F_SMTPS: + s->flags |= MTA_FORCE_SMTPS; + + case F_STARTTLS: + /* client_* API by default requires STARTTLS */ + break; + + default: + s->flags |= MTA_ALLOW_PLAIN; + } + + TAILQ_INIT(&s->recipients); + TAILQ_INIT(&s->relays); SPLAY_INSERT(mtatree, &env->mta_sessions, s); return; case IMSG_BATCH_APPEND: - action = imsg->data; - s = mta_lookup(env, imsg->hdr.peerid); - if (s->auxraw == NULL) { - /* - * XXX: queue can batch together actions with - * different relay params. - */ - s->auxraw = strdup(action->arg); - if (s->auxraw == NULL) - fatal(NULL); - auxsplit(&s->aux, s->auxraw); - } - auxsplit(&aux, action->arg); - rcpt_sz = sizeof *rcpt + strlen(aux.rcpt) + 1; - rcpt = malloc(rcpt_sz); - if (rcpt == NULL) + m = imsg->data; + s = mta_lookup(env, m->batch_id); + m = malloc(sizeof *m); + if (m == NULL) fatal(NULL); - rcpt->action_id = action->id; - strlcpy(rcpt->address, aux.rcpt, rcpt_sz - sizeof *rcpt); - strlcpy(rcpt->status, "000 init", sizeof rcpt->status); - TAILQ_INSERT_TAIL(&s->recipients, rcpt, entry); + *m = *(struct message *)imsg->data; + strlcpy(m->session_errorline, "000 init", + sizeof(m->session_errorline)); + TAILQ_INSERT_TAIL(&s->recipients, m, entry); return; case IMSG_BATCH_CLOSE: - s = mta_lookup(env, imsg->hdr.peerid); - memcpy(&s->birth, imsg->data, sizeof s->birth); - mta_pickup(s, NULL); + b = imsg->data; + mta_pickup(mta_lookup(env, b->id), NULL); + return; + + case IMSG_QUEUE_MESSAGE_FD: + b = imsg->data; + mta_pickup(mta_lookup(env, b->id), &imsg->fd); return; } } @@ -124,7 +155,8 @@ mta_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_LKA) { switch (imsg->hdr.type) { case IMSG_LKA_SECRET: - mta_pickup(mta_lookup(env, imsg->hdr.peerid), imsg->data); + secret = imsg->data; + mta_pickup(mta_lookup(env, secret->id), secret->secret); return; case IMSG_DNS_A: @@ -290,25 +322,24 @@ mta_session_cmp(struct mta_session *a, struct mta_session *b) } struct mta_session * -mta_lookup(struct smtpd *env, u_int32_t id) +mta_lookup(struct smtpd *env, u_int64_t id) { struct mta_session key, *res; key.id = id; - res = SPLAY_FIND(mtatree, &env->mta_sessions, &key); - if (res == NULL) + if ((res = SPLAY_FIND(mtatree, &env->mta_sessions, &key)) == NULL) fatalx("mta_lookup: session not found"); - return res; + return (res); } void mta_enter_state(struct mta_session *s, int newstate, void *p) { + struct secret secret; struct mta_relay *relay; struct sockaddr *sa; - struct recipient *rcpt; + struct message *m; struct smtp_client *pcb; - char *host; int max_reuse; s->state = newstate; @@ -318,34 +349,33 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) /* * Lookup AUTH secret. */ - if (s->aux.relay_via[0]) - host = s->aux.relay_via; - else { - rcpt = TAILQ_FIRST(&s->recipients); - host = strchr(rcpt->address, '@') + 1; - } + bzero(&secret, sizeof(secret)); + secret.id = s->id; + strlcpy(secret.host, s->host, sizeof(secret.host)); imsg_compose_event(s->env->sc_ievs[PROC_LKA], IMSG_LKA_SECRET, - s->id, 0, -1, host, strlen(host) + 1); + 0, 0, -1, &secret, sizeof(secret)); break; case MTA_MX: /* * Lookup MX record. */ - if (s->aux.relay_via[0]) - host = s->aux.relay_via; - else { - rcpt = TAILQ_FIRST(&s->recipients); - host = strchr(rcpt->address, '@') + 1; - } - dns_query_mx(s->env, host, 0, s->id); + dns_query_mx(s->env, s->host, 0, s->id); + break; + + case MTA_DATA: + /* + * Obtain message body fd. + */ + log_debug("mta: getting datafd"); + mta_request_datafd(s); break; case MTA_CONNECT: /* * Connect to the MX. */ - if (strcmp(s->aux.ssl, "ssl") == 0) + if (s->flags & MTA_FORCE_ANYSSL) max_reuse = 2; else max_reuse = 1; @@ -362,25 +392,24 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) log_debug("mta: connect %s", ss_to_text(&relay->sa)); sa = (struct sockaddr *)&relay->sa; - if (s->aux.port[0]) - sa_set_port(sa, s->aux.port); - else if (strcmp(s->aux.ssl, "ssl") == 0 && - relay->used == 1) - sa_set_port(sa, "465"); - else if (strcmp(s->aux.ssl, "smtps") == 0) - sa_set_port(sa, "465"); + if (s->port) + sa_set_port(sa, s->port); + else if ((s->flags & MTA_FORCE_ANYSSL) && relay->used == 1) + sa_set_port(sa, 465); + else if (s->flags & MTA_FORCE_SMTPS) + sa_set_port(sa, 465); else - sa_set_port(sa, "25"); + sa_set_port(sa, 25); s->fd = socket(sa->sa_family, SOCK_STREAM, 0); if (s->fd == -1) - fatal("socket"); + fatal("mta cannot create socket"); session_socket_blockmode(s->fd, BM_NONBLOCK); session_socket_no_linger(s->fd); if (connect(s->fd, sa, sa->sa_len) == -1) { if (errno != EINPROGRESS) { - mta_status(s, "110 connect: %s", + mta_status(s, "110 connect error: %s", strerror(errno)); close(s->fd); continue; @@ -412,10 +441,10 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) pcb = client_init(s->fd, s->datafd, s->env->sc_hostname, 1); /* lookup SSL certificate */ - if (s->aux.cert[0]) { - struct ssl key, *res; + if (s->cert) { + struct ssl key, *res; - strlcpy(key.ssl_name, s->aux.cert, sizeof key.ssl_name); + strlcpy(key.ssl_name, s->cert, sizeof(key.ssl_name)); res = SPLAY_FIND(ssltree, s->env->sc_ssl, &key); if (res == NULL) { client_close(pcb); @@ -430,11 +459,11 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) /* choose SMTPS vs. STARTTLS */ relay = TAILQ_FIRST(&s->relays); - if (strcmp(s->aux.ssl, "ssl") == 0 && relay->used == 1) + if ((s->flags & MTA_FORCE_ANYSSL) && relay->used == 1) client_ssl_smtps(pcb); - else if (strcmp(s->aux.ssl, "smtps") == 0) + else if (s->flags & MTA_FORCE_SMTPS) client_ssl_smtps(pcb); - else if (s->aux.ssl[0] == '\0') + else if (s->flags & MTA_ALLOW_PLAIN) client_ssl_optional(pcb); /* enable AUTH */ @@ -442,14 +471,17 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) client_auth(pcb, s->secret); /* set envelope sender */ - if (s->aux.mail_from[0]) - client_sender(pcb, "%s", s->aux.mail_from); + m = TAILQ_FIRST(&s->recipients); + if (m->sender.user[0] && m->sender.domain[0]) + client_sender(pcb, "%s@%s", m->sender.user, + m->sender.domain); else client_sender(pcb, ""); /* set envelope recipients */ - TAILQ_FOREACH(rcpt, &s->recipients, entry) - client_rcpt(pcb, rcpt, "%s", rcpt->address); + TAILQ_FOREACH(m, &s->recipients, entry) + client_rcpt(pcb, m, "%s@%s", m->recipient.user, + m->recipient.domain); s->pcb = pcb; event_set(&s->ev, s->fd, EV_READ|EV_WRITE, mta_event, s); @@ -462,11 +494,11 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) */ /* update queue status */ - while ((rcpt = TAILQ_FIRST(&s->recipients))) - mta_rcpt_done(s, rcpt); + while ((m = TAILQ_FIRST(&s->recipients))) + mta_message_done(s, m); imsg_compose_event(s->env->sc_ievs[PROC_QUEUE], - IMSG_BATCH_DONE, s->id, 0, -1, NULL, 0); + IMSG_BATCH_DONE, 0, 0, -1, NULL, 0); /* deallocate resources */ SPLAY_REMOVE(mtatree, &s->env->mta_sessions, s); @@ -475,8 +507,9 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) free(relay); } close(s->datafd); - free(s->auxraw); free(s->secret); + free(s->host); + free(s->cert); free(s); break; @@ -492,7 +525,7 @@ mta_pickup(struct mta_session *s, void *p) switch (s->state) { case MTA_INIT: - if (s->aux.auth[0]) + if (s->flags & MTA_USE_AUTH) mta_enter_state(s, MTA_SECRET, NULL); else mta_enter_state(s, MTA_MX, NULL); @@ -523,6 +556,15 @@ mta_pickup(struct mta_session *s, void *p) mta_status(s, "600 Unable to resolve DNS for domain"); mta_enter_state(s, MTA_DONE, NULL); } else + mta_enter_state(s, MTA_DATA, NULL); + break; + + case MTA_DATA: + /* QUEUE replied to body fd request. */ + s->datafd = *(int *)p; + if (s->datafd == -1) + fatalx("mta cannot obtain msgfd"); + else mta_enter_state(s, MTA_CONNECT, NULL); break; @@ -530,7 +572,7 @@ mta_pickup(struct mta_session *s, void *p) /* Remote accepted/rejected connection. */ error = session_socket_error(s->fd); if (error) { - mta_status(s, "110 connect: %s", strerror(error)); + mta_status(s, "110 connect error: %s", strerror(error)); close(s->fd); mta_enter_state(s, MTA_CONNECT, NULL); } else @@ -551,7 +593,6 @@ mta_event(int fd, short event, void *p) { struct mta_session *s = p; struct smtp_client *pcb = s->pcb; - struct recipient *rcpt; if (event & EV_TIMEOUT) { mta_status(s, "150 timeout"); @@ -564,10 +605,9 @@ mta_event(int fd, short event, void *p) case CLIENT_STOP_WRITE: goto ro; case CLIENT_RCPT_FAIL: - rcpt = pcb->rcptfail; - mta_rcpt_status(pcb->rcptfail, pcb->reply); - mta_rcpt_log(s, pcb->rcptfail); - mta_rcpt_done(s, pcb->rcptfail); + mta_message_status(pcb->rcptfail, pcb->reply); + mta_message_log(s, pcb->rcptfail); + mta_message_done(s, pcb->rcptfail); goto rw; case CLIENT_DONE: mta_status(s, "%s", pcb->status); @@ -580,13 +620,10 @@ out: client_close(pcb); s->pcb = NULL; - if (TAILQ_EMPTY(&s->recipients)) { - log_debug("%s: leaving", __func__); + if (TAILQ_EMPTY(&s->recipients)) mta_enter_state(s, MTA_DONE, NULL); - } else { - log_debug("%s: connecting to next", __func__); + else mta_enter_state(s, MTA_CONNECT, NULL); - } return; rw: @@ -603,7 +640,7 @@ void mta_status(struct mta_session *s, const char *fmt, ...) { char *status; - struct recipient *rcpt, *next; + struct message *m, *next; va_list ap; va_start(ap, fmt); @@ -611,16 +648,16 @@ mta_status(struct mta_session *s, const char *fmt, ...) fatal("vasprintf"); va_end(ap); - for (rcpt = TAILQ_FIRST(&s->recipients); rcpt; rcpt = next) { - next = TAILQ_NEXT(rcpt, entry); + for (m = TAILQ_FIRST(&s->recipients); m; m = next) { + next = TAILQ_NEXT(m, entry); /* save new status */ - mta_rcpt_status(rcpt, status); + mta_message_status(m, status); /* remove queue entry */ if (*status == '2' || *status == '5' || *status == '6') { - mta_rcpt_log(s, rcpt); - mta_rcpt_done(s, rcpt); + mta_message_log(s, m); + mta_message_done(s, m); } } @@ -628,56 +665,58 @@ mta_status(struct mta_session *s, const char *fmt, ...) } void -mta_rcpt_status(struct recipient *rcpt, char *status) +mta_message_status(struct message *m, char *status) { /* * Previous delivery attempts might have assigned an errorline of * higher status (eg. 5yz is of higher status than 4yz), so check * this before deciding to overwrite existing status with a new one. */ - if (*status != '2' && strncmp(rcpt->status, status, 3) > 0) + if (*status != '2' && strncmp(m->session_errorline, status, 3) > 0) return; /* change status */ - log_debug("mta: new status for %s: %s", rcpt->address, status); - strlcpy(rcpt->status, status, sizeof rcpt->status); + log_debug("mta: new status for %s@%s: %s", m->recipient.user, + m->recipient.domain, status); + strlcpy(m->session_errorline, status, sizeof(m->session_errorline)); } void -mta_rcpt_log(struct mta_session *s, struct recipient *rcpt) +mta_message_log(struct mta_session *s, struct message *m) { - struct mta_relay *relay; - - relay = TAILQ_FIRST(&s->relays); + struct mta_relay *relay = TAILQ_FIRST(&s->relays); + char *status = m->session_errorline; - log_info("%s: to=%s, delay=%d, relay=%s [%s], stat=%s (%s)", - queue_be_decode(s->content_id), rcpt->address, - time(NULL) - s->birth, + log_info("%s: to=<%s@%s>, delay=%d, relay=%s [%s], stat=%s (%s)", + m->message_id, m->recipient.user, + m->recipient.domain, time(NULL) - m->creation, relay ? relay->fqdn : "(none)", relay ? ss_to_text(&relay->sa) : "", - rcpt->status[0] == '2' ? "Sent" : - rcpt->status[0] == '5' ? "RemoteError" : - rcpt->status[0] == '4' ? "RemoteError" : "LocalError", - rcpt->status + 4); + *status == '2' ? "Sent" : + *status == '5' ? "RemoteError" : + *status == '4' ? "RemoteError" : "LocalError", + status + 4); } void -mta_rcpt_done(struct mta_session *s, struct recipient *rcpt) +mta_message_done(struct mta_session *s, struct message *m) { - struct action *action; - int action_sz; - - action_sz = sizeof *action + strlen(rcpt->status) + 1; - action = malloc(action_sz); - if (action == NULL) - fatal(NULL); - action->id = rcpt->action_id; - strlcpy(action->arg, rcpt->status, action_sz - sizeof *action); - imsg_compose_event(s->env->sc_ievs[PROC_QUEUE], IMSG_BATCH_UPDATE, - s->id, 0, -1, action, action_sz); - TAILQ_REMOVE(&s->recipients, rcpt, entry); - free(action); - free(rcpt); + switch (m->session_errorline[0]) { + case '6': + case '5': + m->status = S_MESSAGE_PERMFAILURE; + break; + case '2': + m->status = S_MESSAGE_ACCEPTED; + break; + default: + m->status = S_MESSAGE_TEMPFAILURE; + break; + } + imsg_compose_event(s->env->sc_ievs[PROC_QUEUE], + IMSG_QUEUE_MESSAGE_UPDATE, 0, 0, -1, m, sizeof(*m)); + TAILQ_REMOVE(&s->recipients, m, entry); + free(m); } void @@ -686,4 +725,17 @@ mta_connect_done(int fd, short event, void *p) mta_pickup(p, NULL); } +void +mta_request_datafd(struct mta_session *s) +{ + struct batch b; + struct message *m; + + b.id = s->id; + m = TAILQ_FIRST(&s->recipients); + strlcpy(b.message_id, m->message_id, sizeof(b.message_id)); + imsg_compose_event(s->env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_MESSAGE_FD, + 0, 0, -1, &b, sizeof(b)); +} + SPLAY_GENERATE(mtatree, mta_session, entry, mta_session_cmp); diff --git a/usr.sbin/smtpd/parse.y b/usr.sbin/smtpd/parse.y index 5a580029c38..4f113d5973a 100644 --- a/usr.sbin/smtpd/parse.y +++ b/usr.sbin/smtpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.60 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: parse.y,v 1.61 2010/06/01 19:47:09 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -116,7 +116,7 @@ typedef struct { %} -%token SIZE LISTEN ON ALL PORT +%token QUEUE INTERVAL SIZE LISTEN ON ALL PORT %token MAP TYPE HASH LIST SINGLE SSL SMTPS CERTIFICATE %token DNS DB PLAIN EXTERNAL DOMAIN CONFIG SOURCE %token RELAY VIA DELIVER TO MAILDIR MBOX HOSTNAME @@ -266,7 +266,10 @@ tag : TAG STRING { | /* empty */ { $$ = NULL; } ; -main : SIZE size { +main : QUEUE INTERVAL interval { + conf->sc_qintval = $3; + } + | SIZE size { conf->sc_maxsize = $2; } | LISTEN ON STRING port ssl certname auth tag { @@ -1056,6 +1059,7 @@ lookup(char *s) { "hash", HASH }, { "hostname", HOSTNAME }, { "include", INCLUDE }, + { "interval", INTERVAL }, { "list", LIST }, { "listen", LISTEN }, { "local", LOCAL }, @@ -1067,6 +1071,7 @@ lookup(char *s) { "on", ON }, { "plain", PLAIN }, { "port", PORT }, + { "queue", QUEUE }, { "reject", REJECT }, { "relay", RELAY }, { "single", SINGLE }, @@ -1458,6 +1463,8 @@ parse_config(struct smtpd *x_conf, const char *filename, int opts) SPLAY_INIT(conf->sc_ssl); SPLAY_INIT(&conf->sc_sessions); + conf->sc_qintval.tv_sec = SMTPD_QUEUE_INTERVAL; + conf->sc_qintval.tv_usec = 0; conf->sc_opts = opts; if ((file = pushfile(filename, 0)) == NULL) { diff --git a/usr.sbin/smtpd/parser.c b/usr.sbin/smtpd/parser.c index 5f7d09cf24b..1826e230b53 100644 --- a/usr.sbin/smtpd/parser.c +++ b/usr.sbin/smtpd/parser.c @@ -1,4 +1,4 @@ -/* $OpenBSD: parser.c,v 1.12 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: parser.c,v 1.13 2010/06/01 19:47:09 jacekm Exp $ */ /* * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -58,7 +58,6 @@ struct token { static const struct token t_main[]; static const struct token t_show[]; -static const struct token t_show_queue[]; static const struct token t_pause[]; static const struct token t_resume[]; static const struct token t_schedule[]; @@ -79,18 +78,12 @@ static const struct token t_main[] = { }; static const struct token t_show[] = { - {KEYWORD, "queue", SHOW_QUEUE, t_show_queue}, + {KEYWORD, "queue", SHOW_QUEUE, NULL}, {KEYWORD, "runqueue", SHOW_RUNQUEUE, NULL}, {KEYWORD, "stats", SHOW_STATS, NULL}, {ENDTOKEN, "", NONE, NULL} }; -static const struct token t_show_queue[] = { - {NOTOKEN, "", NONE, NULL}, - {KEYWORD, "raw", SHOW_QUEUE_RAW, NULL}, - {ENDTOKEN, "", NONE, NULL} -}; - static const struct token t_pause[] = { {KEYWORD, "local", PAUSE_MDA, NULL}, {KEYWORD, "outgoing", PAUSE_MTA, NULL}, @@ -106,12 +99,12 @@ static const struct token t_resume[] = { }; static const struct token t_schedule[] = { - {VARIABLE, "message", SCHEDULE, NULL}, + {VARIABLE, "message id/uid", SCHEDULE, NULL}, {ENDTOKEN, "", NONE, NULL} }; static const struct token t_remove[] = { - {VARIABLE, "message", REMOVE, NULL}, + {VARIABLE, "message id/uid", REMOVE, NULL}, {ENDTOKEN, "", NONE, NULL} }; diff --git a/usr.sbin/smtpd/parser.h b/usr.sbin/smtpd/parser.h index 88142c943e9..d481e19a44c 100644 --- a/usr.sbin/smtpd/parser.h +++ b/usr.sbin/smtpd/parser.h @@ -1,4 +1,4 @@ -/* $OpenBSD: parser.h,v 1.11 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: parser.h,v 1.12 2010/06/01 19:47:09 jacekm Exp $ */ /* * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -26,7 +26,6 @@ enum actions { LOG_VERBOSE, LOG_BRIEF, SHOW_QUEUE, - SHOW_QUEUE_RAW, SHOW_RUNQUEUE, SHOW_STATS, PAUSE_MDA, diff --git a/usr.sbin/smtpd/queue.c b/usr.sbin/smtpd/queue.c index 6e159950f18..e0ea10c1de7 100644 --- a/usr.sbin/smtpd/queue.c +++ b/usr.sbin/smtpd/queue.c @@ -1,7 +1,6 @@ -/* $OpenBSD: queue.c,v 1.84 2010/06/01 14:21:52 jacekm Exp $ */ +/* $OpenBSD: queue.c,v 1.85 2010/06/01 19:47:09 jacekm Exp $ */ /* - * Copyright (c) 2008-2010 Jacek Masiulaniec <jacekm@dobremiasto.net> * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> * @@ -24,14 +23,11 @@ #include <sys/param.h> #include <sys/socket.h> #include <sys/stat.h> -#include <sys/uio.h> -#include <ctype.h> #include <errno.h> #include <event.h> #include <fcntl.h> #include <libgen.h> -#include <math.h> #include <pwd.h> #include <signal.h> #include <stdio.h> @@ -40,308 +36,156 @@ #include <unistd.h> #include "smtpd.h" -#include "queue_backend.h" -#include "client.h" -void queue_imsg(struct smtpd *, struct imsgev *, struct imsg *); -int queue_append(struct incoming *, char *); -void queue_destroy(struct incoming *); -int queue_control(u_int64_t, int); -__dead void queue_shutdown(void); -void queue_sig_handler(int, short, void *); - -void queue_mem_init(struct smtpd *); -void queue_mem_content_unref(struct content *); - -void queue_send(int, short, void *); -void queue_expire(struct batch *); -void queue_update(int, int, u_int64_t, char *); -void queue_done(int, int); -void queue_schedule(int, struct batch *); -void queue_sleep(int); -time_t queue_retry(int, time_t, time_t); - -void queue_bounce_wait(struct content *); -void queue_bounce_schedule(int, short, void *); -void queue_bounce_init(int, int); -void queue_bounce_event(int, short, void *); - -int queue_detect_loop(struct incoming *); - -struct batch *batch_it(struct incoming *, char *); -int batchsort(const void *, const void *); -int action_grow(struct action **, char *); -char *rcpt_pretty(struct aux *); - -/* table of batches in larval state */ -void **incoming; -int incoming_sz; - -struct queue runqs[3]; +void queue_imsg(struct smtpd *, struct imsgev *, struct imsg *); +void queue_pass_to_runner(struct smtpd *, struct imsgev *, struct imsg *); +__dead void queue_shutdown(void); +void queue_sig_handler(int, short, void *); +void queue_setup_events(struct smtpd *); +void queue_disable_events(struct smtpd *); +void queue_purge(char *); + +int queue_create_layout_message(char *, char *); +void queue_delete_layout_message(char *, char *); +int queue_record_layout_envelope(char *, struct message *); +int queue_remove_layout_envelope(char *, struct message *); +int queue_commit_layout_message(char *, struct message *); +int queue_open_layout_messagefile(char *, struct message *); void queue_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { - struct action *update; - struct incoming *s; - struct batch *batch; + struct submit_status ss; struct message *m; - u_int64_t content_id; - u_int rq; - int i, fd, error; - time_t now; - struct iovec iov[2]; - char aux[2048]; /* XXX */ + struct batch *b; + int fd, ret; if (iev->proc == PROC_SMTP) { + m = imsg->data; + switch (imsg->hdr.type) { - case IMSG_QUEUE_CREATE: - /* - * Create file that will hold mail content. Its name - * uniquely identifies entire mail transaction. Actions - * will refer to the this file as source of mail content. - */ - s = calloc(1, sizeof *s); - if (s == NULL) - fatal(NULL); - for (rq = 0; rq < nitems(s->batches); rq++) - SLIST_INIT(&s->batches[rq]); - s->content = calloc(1, sizeof *s->content); - if (s->content == NULL) - fatal(NULL); - if (queue_be_content_create(&s->content->id) < 0) - s->content->id = INVALID_ID; - i = table_alloc(&incoming, &incoming_sz); - incoming[i] = s; - iov[0].iov_base = &s->content->id; - iov[0].iov_len = sizeof s->content->id; - iov[1].iov_base = &i; - iov[1].iov_len = sizeof i; - imsg_composev(&iev->ibuf, IMSG_QUEUE_CREATE, - imsg->hdr.peerid, 0, -1, iov, 2); - imsg_event_add(iev); + case IMSG_QUEUE_CREATE_MESSAGE: + ss.id = m->session_id; + ss.code = 250; + bzero(ss.u.msgid, sizeof ss.u.msgid); + if (m->flags & F_MESSAGE_ENQUEUED) + ret = enqueue_create_layout(ss.u.msgid); + else + ret = queue_create_incoming_layout(ss.u.msgid); + if (ret == 0) + ss.code = 421; + imsg_compose_event(iev, IMSG_QUEUE_CREATE_MESSAGE, 0, 0, -1, + &ss, sizeof ss); return; - case IMSG_QUEUE_DELETE: - /* - * Delete failed transaction's content and actions. - */ - memcpy(&i, imsg->data, sizeof i); - s = table_lookup(incoming, incoming_sz, i); - if (s == NULL) - fatalx("queue: bogus delete req"); - incoming[i] = NULL; - queue_destroy(s); + case IMSG_QUEUE_REMOVE_MESSAGE: + if (m->flags & F_MESSAGE_ENQUEUED) + enqueue_delete_message(m->message_id); + else + queue_delete_incoming_message(m->message_id); return; - case IMSG_QUEUE_OPEN: - /* - * Open the file that will hold mail content. - */ - memcpy(&i, imsg->data, sizeof i); - s = table_lookup(incoming, incoming_sz, i); - if (s == NULL) - fatalx("queue: bogus open req"); - fd = queue_be_content_open(s->content->id, 1); - if (fd < 0) - fatal("queue: content open error"); - imsg_compose_event(iev, IMSG_QUEUE_OPEN, - imsg->hdr.peerid, 0, fd, NULL, 0); + case IMSG_QUEUE_COMMIT_MESSAGE: + ss.id = m->session_id; + if (m->flags & F_MESSAGE_ENQUEUED) { + if (enqueue_commit_message(m)) + env->stats->queue.inserts_local++; + else + ss.code = 421; + } else { + if (queue_commit_incoming_message(m)) + env->stats->queue.inserts_remote++; + else + ss.code = 421; + } + imsg_compose_event(iev, IMSG_QUEUE_COMMIT_MESSAGE, 0, 0, -1, + &ss, sizeof ss); return; - case IMSG_QUEUE_CLOSE: - /* - * Commit mail to queue: we take on responsibility for - * performing all requested actions on this content. - */ - memcpy(&i, imsg->data, sizeof i); - s = table_lookup(incoming, incoming_sz, i); - if (s == NULL) - fatalx("queue: bogus commit req"); - incoming[i] = NULL; - if (queue_detect_loop(s) < 0) { - error = S_MESSAGE_PERMFAILURE; - imsg_compose_event(iev, IMSG_QUEUE_CLOSE, - imsg->hdr.peerid, 0, -1, &error, sizeof error); - return; - } - if (queue_be_commit(s->content->id) < 0) { - error = S_MESSAGE_TEMPFAILURE; - imsg_compose_event(iev, IMSG_QUEUE_CLOSE, - imsg->hdr.peerid, 0, -1, &error, sizeof error); - return; - } - env->stats->queue.inserts++; - env->stats->queue.length++; - time(&now); - for (rq = 0; rq < nitems(s->batches); rq++) { - while ((batch = SLIST_FIRST(&s->batches[rq]))) { - SLIST_REMOVE_HEAD(&s->batches[rq], entry); - batch = realloc(batch, sizeof *batch); - if (batch == NULL) - fatal(NULL); - batch->retry = RETRY_NOW; - queue_schedule(rq, batch); - } - } - for (i = 0; i < s->nlocal; i++) - free(s->local[i]); - free(s->local); - free(s); - queue_sleep(Q_LOCAL); - queue_sleep(Q_RELAY); - error = 0; - imsg_compose_event(iev, IMSG_QUEUE_CLOSE, - imsg->hdr.peerid, 0, -1, &error, sizeof error); + case IMSG_QUEUE_MESSAGE_FILE: + ss.id = m->session_id; + if (m->flags & F_MESSAGE_ENQUEUED) + fd = enqueue_open_messagefile(m); + else + fd = queue_open_incoming_message_file(m); + if (fd == -1) + ss.code = 421; + imsg_compose_event(iev, IMSG_QUEUE_MESSAGE_FILE, 0, 0, fd, + &ss, sizeof ss); return; case IMSG_SMTP_ENQUEUE: - queue_bounce_init(imsg->hdr.peerid, imsg->fd); + queue_pass_to_runner(env, iev, imsg); return; } } if (iev->proc == PROC_LKA) { - switch (imsg->hdr.type) { - case IMSG_QUEUE_APPEND: - m = imsg->data; - s = table_lookup(incoming, incoming_sz, m->queue_id); - if (s == NULL) - fatalx("queue: bogus append"); + m = imsg->data; - switch (m->recipient.rule.r_action) { - case A_MBOX: - case A_MAILDIR: - case A_EXT: - /* ?|from|to|user1|user2|path */ - if (m->recipient.rule.r_action == A_MBOX) - strlcpy(aux, "M|", sizeof aux); - else if (m->recipient.rule.r_action == A_MAILDIR) - strlcpy(aux, "D|", sizeof aux); - else - strlcpy(aux, "P|", sizeof aux); - if (m->sender.user[0] && m->sender.domain[0]) { - strlcat(aux, m->sender.user, sizeof aux); - strlcat(aux, "@", sizeof aux); - strlcat(aux, m->sender.domain, sizeof aux); - } - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->session_rcpt.user, sizeof aux); - strlcat(aux, "@", sizeof aux); - strlcat(aux, m->session_rcpt.domain, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->sender.pw_name, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->recipient.pw_name, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->recipient.rule.r_value.path, sizeof aux); - break; + switch (imsg->hdr.type) { + case IMSG_QUEUE_SUBMIT_ENVELOPE: + m->id = generate_uid(); + ss.id = m->session_id; - case A_FILENAME: - /* F|from|to|user1|user2|path */ - strlcpy(aux, "F|", sizeof aux); - if (m->sender.user[0] && m->sender.domain[0]) { - strlcat(aux, m->sender.user, sizeof aux); - strlcat(aux, "@", sizeof aux); - strlcat(aux, m->sender.domain, sizeof aux); - } - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->session_rcpt.user, sizeof aux); - strlcat(aux, "@", sizeof aux); - strlcat(aux, m->session_rcpt.domain, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->sender.pw_name, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, SMTPD_USER, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->recipient.u.filename, sizeof aux); - break; + if (IS_MAILBOX(m->recipient) || IS_EXT(m->recipient)) + m->type = T_MDA_MESSAGE; + else + m->type = T_MTA_MESSAGE; - case A_RELAY: - case A_RELAYVIA: - /* R|from|to|user|rcpt|via|port|ssl|cert|auth */ - strlcpy(aux, "R|", sizeof aux); - if (m->sender.user[0] && m->sender.domain[0]) { - strlcat(aux, m->sender.user, sizeof aux); - strlcat(aux, "@", sizeof aux); - strlcat(aux, m->sender.domain, sizeof aux); - } - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->session_rcpt.user, sizeof aux); - strlcat(aux, "@", sizeof aux); - strlcat(aux, m->session_rcpt.domain, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->sender.pw_name, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->recipient.user, sizeof aux); - strlcat(aux, "@", sizeof aux); - strlcat(aux, m->recipient.domain, sizeof aux); - strlcat(aux, "|", sizeof aux); - if (m->recipient.rule.r_action == A_RELAYVIA) - strlcat(aux, m->recipient.rule.r_value.relayhost.hostname, sizeof aux); - strlcat(aux, "|", sizeof aux); - if (m->recipient.rule.r_value.relayhost.port) { - char port[10]; - snprintf(port, sizeof port, "%d", ntohs(m->recipient.rule.r_value.relayhost.port)); - strlcat(aux, port, sizeof aux); - } - strlcat(aux, "|", sizeof aux); - switch (m->recipient.rule.r_value.relayhost.flags & F_SSL) { - case F_SSL: - strlcat(aux, "ssl", sizeof aux); - break; - case F_SMTPS: - strlcat(aux, "smtps", sizeof aux); - break; - case F_STARTTLS: - strlcat(aux, "starttls", sizeof aux); - break; - } - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->recipient.rule.r_value.relayhost.cert, sizeof aux); - strlcat(aux, "|", sizeof aux); - if (m->recipient.rule.r_value.relayhost.flags & F_AUTH) - strlcat(aux, "secrets", sizeof aux); - break; + /* Write to disk */ + if (m->flags & F_MESSAGE_ENQUEUED) + ret = enqueue_record_envelope(m); + else + ret = queue_record_incoming_envelope(m); - default: - fatalx("queue: bad r_action"); + if (ret == 0) { + ss.code = 421; + imsg_compose_event(env->sc_ievs[PROC_SMTP], + IMSG_QUEUE_TEMPFAIL, 0, 0, -1, &ss, + sizeof ss); } - if (queue_append(s, aux) < 0) - error = S_MESSAGE_TEMPFAILURE; - else - error = 0; - imsg_compose_event(iev, IMSG_QUEUE_APPEND, - imsg->hdr.peerid, 0, -1, &error, sizeof error); + return; + + case IMSG_QUEUE_COMMIT_ENVELOPES: + ss.id = m->session_id; + ss.code = 250; + imsg_compose_event(env->sc_ievs[PROC_SMTP], + IMSG_QUEUE_COMMIT_ENVELOPES, 0, 0, -1, &ss, + sizeof ss); return; } } - if (iev->proc == PROC_MDA) { + if (iev->proc == PROC_RUNNER) { + /* forward imsgs from runner on its behalf */ + imsg_compose_event(env->sc_ievs[imsg->hdr.peerid], imsg->hdr.type, + 0, imsg->hdr.pid, imsg->fd, (char *)imsg->data, + imsg->hdr.len - sizeof imsg->hdr); + return; + } + + if (iev->proc == PROC_MTA) { switch (imsg->hdr.type) { - case IMSG_BATCH_UPDATE: - update = imsg->data; - queue_update(Q_LOCAL, imsg->hdr.peerid, update->id, - update->arg); + case IMSG_QUEUE_MESSAGE_FD: + b = imsg->data; + fd = queue_open_message_file(b->message_id); + imsg_compose_event(iev, IMSG_QUEUE_MESSAGE_FD, 0, 0, + fd, b, sizeof *b); return; + case IMSG_QUEUE_MESSAGE_UPDATE: case IMSG_BATCH_DONE: - queue_done(Q_LOCAL, imsg->hdr.peerid); + queue_pass_to_runner(env, iev, imsg); return; - } } - if (iev->proc == PROC_MTA) { + if (iev->proc == PROC_MDA) { switch (imsg->hdr.type) { - case IMSG_BATCH_UPDATE: - update = imsg->data; - queue_update(Q_RELAY, imsg->hdr.peerid, update->id, - update->arg); - return; - - case IMSG_BATCH_DONE: - queue_done(Q_RELAY, imsg->hdr.peerid); + case IMSG_QUEUE_MESSAGE_UPDATE: + case IMSG_MDA_SESS_NEW: + queue_pass_to_runner(env, iev, imsg); return; } } @@ -349,59 +193,25 @@ queue_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_CONTROL) { switch (imsg->hdr.type) { case IMSG_QUEUE_PAUSE_LOCAL: - runqs[Q_LOCAL].max = 0; - queue_sleep(Q_LOCAL); - return; - - case IMSG_QUEUE_PAUSE_RELAY: - runqs[Q_RELAY].max = 0; - queue_sleep(Q_RELAY); - return; - + case IMSG_QUEUE_PAUSE_OUTGOING: case IMSG_QUEUE_RESUME_LOCAL: - runqs[Q_LOCAL].max = env->sc_maxconn; - queue_sleep(Q_LOCAL); - return; - - case IMSG_QUEUE_RESUME_RELAY: - runqs[Q_RELAY].max = env->sc_maxconn; - queue_sleep(Q_RELAY); - return; - + case IMSG_QUEUE_RESUME_OUTGOING: case IMSG_QUEUE_SCHEDULE: - memcpy(&content_id, imsg->data, sizeof content_id); - error = queue_control(content_id, 1); - if (error) - log_warnx("schedule request failed"); - else { - queue_sleep(Q_LOCAL); - queue_sleep(Q_RELAY); - queue_sleep(Q_BOUNCE); - } - imsg_compose_event(iev, IMSG_QUEUE_SCHEDULE, - imsg->hdr.peerid, 0, -1, &error, sizeof error); - return; - case IMSG_QUEUE_REMOVE: - memcpy(&content_id, imsg->data, sizeof content_id); - error = queue_control(content_id, 0); - if (error) - log_warnx("remove request failed"); - else { - queue_sleep(Q_LOCAL); - queue_sleep(Q_RELAY); - queue_sleep(Q_BOUNCE); - } - imsg_compose_event(iev, IMSG_QUEUE_REMOVE, - imsg->hdr.peerid, 0, -1, &error, sizeof error); + queue_pass_to_runner(env, iev, imsg); return; } } if (iev->proc == PROC_PARENT) { switch (imsg->hdr.type) { + case IMSG_PARENT_ENQUEUE_OFFLINE: + queue_pass_to_runner(env, iev, imsg); + return; + case IMSG_CTL_VERBOSE: log_verbose(*(int *)imsg->data); + queue_pass_to_runner(env, iev, imsg); return; } } @@ -409,168 +219,12 @@ queue_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) fatalx("queue_imsg: unexpected imsg"); } -int -queue_append(struct incoming *s, char *auxraw) -{ - struct batch *batch; - struct action *action; - char *copy; - struct aux aux; - - log_debug("aux %s", auxraw); - - copy = strdup(auxraw); - if (copy == NULL) - fatal(NULL); - auxsplit(&aux, copy); - - /* remember local recipients for delivered-to: loop detection */ - if (aux.mode[0] != 'R') { - if (s->nlocal == s->local_sz) { - s->local_sz *= 2; - s->local = realloc(s->local, ++s->local_sz * - sizeof s->local[0]); - if (s->local == NULL) - fatal(NULL); - } - /* - * XXX: using rcpt_to is wrong because it's unexpanded address - * as seen in RCPT TO; must use expanded address in the form - * <user>@<domain>, but since lka expands local addresses to - * just <user> this is currently undoable. - */ - s->local[s->nlocal] = strdup(aux.rcpt_to); - if (s->local[s->nlocal] == NULL) - fatal(NULL); - s->nlocal++; - } - - /* assign batch */ - if (aux.mode[0] != 'R') - batch = batch_it(s, ""); - else if (aux.relay_via[0]) - batch = batch_it(s, aux.relay_via); - else - batch = batch_it(s, strchr(aux.rcpt, '@')); - if (batch == NULL) - fatal(NULL); - free(copy); - - action = malloc(sizeof *action); - if (action == NULL) - fatal(NULL); - SLIST_INSERT_HEAD(&batch->actions, action, entry); - if (queue_be_action_new(s->content->id, &action->id, auxraw) < 0) - return -1; - - s->content->ref++; - - return 0; -} - -struct batch * -batch_it(struct incoming *s, char *sortkey) -{ - struct batch *batch; - size_t batch_sz; - u_int rq; - - if (*sortkey) { - rq = Q_RELAY; - SLIST_FOREACH(batch, &s->batches[rq], entry) - if (strcmp(batch->sortkey, sortkey) == 0) - break; - } else { - rq = Q_LOCAL; - batch = NULL; - } - - if (batch == NULL) { - batch_sz = sizeof *batch + strlen(sortkey) + 1; - batch = malloc(batch_sz); - if (batch == NULL) - return NULL; - SLIST_INIT(&batch->actions); - batch->content = s->content; - strlcpy(batch->sortkey, sortkey, batch_sz - sizeof *batch); - SLIST_INSERT_HEAD(&s->batches[rq], batch, entry); - } - - return batch; -} - void -queue_destroy(struct incoming *s) +queue_pass_to_runner(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { - struct batch *batch; - struct action *action; - u_int rq; - int i; - - for (rq = 0; rq < nitems(s->batches); rq++) { - while ((batch = SLIST_FIRST(&s->batches[rq]))) { - SLIST_REMOVE_HEAD(&s->batches[rq], entry); - while ((action = SLIST_FIRST(&batch->actions))) { - SLIST_REMOVE_HEAD(&batch->actions, entry); - queue_be_action_delete(s->content->id, - action->id); - free(action); - } - free(batch); - } - } - queue_be_content_delete(s->content->id); - free(s->content); - for (i = 0; i < s->nlocal; i++) - free(s->local[i]); - free(s->local); - free(s); -} - -/* - * Walk all runqueues to schedule or remove requested content. - */ -int -queue_control(u_int64_t content_id, int schedule) -{ - struct batch *b, *next; - struct action *action; - struct action_be a; - struct aux aux; - u_int rq, n; - - n = 0; - for (rq = 0; rq < nitems(runqs); rq++) { - for (b = SLIST_FIRST(&runqs[rq].head); b; b = next) { - next = SLIST_NEXT(b, entry); - if (content_id && b->content->id != content_id) - continue; - n++; - SLIST_REMOVE(&runqs[rq].head, b, batch, entry); - if (schedule) { - time(&b->retry); - queue_schedule(rq, b); - continue; - } - while ((action = SLIST_FIRST(&b->actions))) { - SLIST_REMOVE_HEAD(&b->actions, entry); - if (queue_be_action_read(&a, b->content->id, - action->id) < 0) - fatal("queue: action read error"); - auxsplit(&aux, a.aux); - log_info("%s: to=%s, delay=%d, stat=Removed", - queue_be_decode(b->content->id), - rcpt_pretty(&aux), time(NULL) - a.birth); - queue_be_action_delete(b->content->id, - action->id); - queue_mem_content_unref(b->content); - free(action); - } - free(b); - } - } - - return (n > 0 ? 0 : -1); + imsg_compose_event(env->sc_ievs[PROC_RUNNER], imsg->hdr.type, + iev->proc, imsg->hdr.pid, imsg->fd, imsg->data, + imsg->hdr.len - sizeof imsg->hdr); } void @@ -589,16 +243,26 @@ queue_sig_handler(int sig, short event, void *p) void queue_shutdown(void) { - log_info("queue exiting"); + log_info("queue handler exiting"); _exit(0); } +void +queue_setup_events(struct smtpd *env) +{ +} + +void +queue_disable_events(struct smtpd *env) +{ +} + pid_t queue(struct smtpd *env) { pid_t pid; struct passwd *pw; - u_int rq; + struct event ev_sigint; struct event ev_sigterm; @@ -608,7 +272,8 @@ queue(struct smtpd *env) { PROC_SMTP, imsg_dispatch }, { PROC_MDA, imsg_dispatch }, { PROC_MTA, imsg_dispatch }, - { PROC_LKA, imsg_dispatch } + { PROC_LKA, imsg_dispatch }, + { PROC_RUNNER, imsg_dispatch } }; switch (pid = fork()) { @@ -637,38 +302,9 @@ queue(struct smtpd *env) setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) fatal("queue: cannot drop privileges"); - /* - * Queue opens fds for four purposes: smtp, mta, mda, and bounces. - * Therefore, use all available fd space and set the maxconn (=max - * session count for each of these tasks) to a quarter of this value. - */ - fdlimit(1.0); - if ((env->sc_maxconn = availdesc() / 4) < 1) - fatalx("queue: fd starvation"); - imsg_callback = queue_imsg; event_init(); - config_pipes(env, peers, nitems(peers)); - config_peers(env, peers, nitems(peers)); - - for (rq = 0; rq < nitems(runqs); rq++) { - SLIST_INIT(&runqs[rq].head); - runqs[rq].env = env; - runqs[rq].max = env->sc_maxconn; - } - runqs[Q_LOCAL].name = "Q_LOCAL"; - runqs[Q_RELAY].name = "Q_RELAY"; - runqs[Q_BOUNCE].name = "Q_BOUNCE"; - - /* bouncing costs 2 fds: file and socket */ - runqs[Q_BOUNCE].max /= 2; - - queue_mem_init(env); - queue_sleep(Q_LOCAL); - queue_sleep(Q_RELAY); - queue_sleep(Q_BOUNCE); - signal_set(&ev_sigint, SIGINT, queue_sig_handler, env); signal_set(&ev_sigterm, SIGTERM, queue_sig_handler, env); signal_add(&ev_sigint, NULL); @@ -676,759 +312,64 @@ queue(struct smtpd *env) signal(SIGPIPE, SIG_IGN); signal(SIGHUP, SIG_IGN); - event_dispatch(); - queue_shutdown(); - - return (0); -} - -void -queue_mem_init(struct smtpd *env) -{ - SLIST_HEAD(,batch) lookup[4096]; - void **batch; - struct content *content; - struct action *action; - struct batch *b; - char *sortkey; - struct action_be a; - struct aux aux; - int batch_sz, batches, rq, sz, i; - - for (i = 0; i < 4096; i++) - SLIST_INIT(&lookup[i]); - batch = NULL; - batch_sz = 0; - batches = 0; - /* - * Sort actions into batches. - */ - for (;;) { - if (queue_be_getnext(&a) < 0) - fatal("queue: backend error"); - if (a.action_id == 0) - break; - auxsplit(&aux, a.aux); - - /* - * Assignment to batch is based on the sortkey: - * B=<content_id> for bounced mail - * R=<domain> for relayed mail - * L=<action_id> for local mail - */ - if (a.status[0] == '5' || a.status[0] == '6') - asprintf(&sortkey, "B=%s", queue_be_decode(a.content_id)); - else if (aux.mode[0] == 'R') { - if (aux.relay_via[0]) - asprintf(&sortkey, "R=%s", aux.relay_via); - else - asprintf(&sortkey, "R=%s", strchr(aux.rcpt, '@')); - } else - asprintf(&sortkey, "L=%s", queue_be_decode(a.action_id)); - - content = NULL; - SLIST_FOREACH(b, &lookup[a.content_id & 4095], entry) { - if (b->content->id == a.content_id) { - content = b->content; - if (strcmp(b->sortkey, sortkey) == 0) - break; - } - } - - if (b == NULL) { - sz = sizeof *b + strlen(sortkey) + 1; - b = malloc(sz); - if (b == NULL) - fatal("queue_mem_init"); - SLIST_INIT(&b->actions); - strlcpy(b->sortkey, sortkey, sz - sizeof *b); - - if (*sortkey == 'B') - rq = Q_BOUNCE; - else if (*sortkey == 'R') - rq = Q_RELAY; - else - rq = Q_LOCAL; - - b->retry = queue_retry(rq, a.birth, a.birth); - while (b->retry < time(NULL)) - b->retry = queue_retry(rq, a.birth, b->retry); - - if (b->retry > a.birth + SMTPD_EXPIRE) - b->retry = NO_RETRY_EXPIRED; - - if (content) - b->content = content; - else { - b->content = calloc(1, sizeof *b->content); - if (b->content == NULL) - fatal("queue_mem_init"); - b->content->id = a.content_id; - b->content->ref = 0; - env->stats->queue.length++; - } - - SLIST_INSERT_HEAD(&lookup[a.content_id & 4095], b, entry); - if (batches == batch_sz) { - batch_sz *= 2; - batch = realloc(batch, ++batch_sz * sizeof *batch); - if (batch == NULL) - fatal("queue_mem_init"); - } - batch[batches] = b; - batches++; - } - - action = malloc(sizeof *action); - if (action == NULL) - fatal("queue_mem_init"); - action->id = a.action_id; - SLIST_INSERT_HEAD(&b->actions, action, entry); - b->content->ref++; - free(sortkey); - } - - /* - * Add batches to schedule. + * queue opens fds for four purposes: smtp, mta, mda, and bounces. + * Therefore, use all available fd space and set the maxconn (=max + * session count for mta and mda) to a quarter of this value. */ - qsort(batch, batches, sizeof *batch, batchsort); - for (i = 0; i < batches; i++) { - b = batch[i]; - if (b->sortkey[0] == 'B') - rq = Q_BOUNCE; - else if (b->sortkey[0] == 'R') - rq = Q_RELAY; - else - rq = Q_LOCAL; - b = realloc(b, sizeof *b); - if (b == NULL) - fatal("queue_mem_init"); - queue_schedule(rq, b); - } - free(batch); -} - -int -batchsort(const void *x, const void *y) -{ - const struct batch *b1 = x, *b2 = y; - return (b1->retry < b2->retry ? -1 : b1->retry > b2->retry); -} - -void -queue_mem_content_unref(struct content *content) -{ - content->ref--; - if (content->ref < 0) - fatalx("queue: bad refcount"); - else if (content->ref == 0) { - queue_be_content_delete(content->id); - runqs[Q_LOCAL].env->stats->queue.length--; - } -} - -void -queue_send(int fd, short event, void *p) -{ - struct smtpd *env; - struct batch *batch; - struct action *action; - struct action_be a; - int rq, i, to, size; - time_t now; - - rq = (struct queue *)p - runqs; - env = runqs[rq].env; - time(&now); - i = -1; - - while ((batch = SLIST_FIRST(&runqs[rq].head))) { - if (batch->retry > now || runqs[rq].sessions >= runqs[rq].max) - break; - - SLIST_REMOVE_HEAD(&runqs[rq].head, entry); - i = table_alloc(&runqs[rq].session, &runqs[rq].session_sz); - runqs[rq].session[i] = batch; - runqs[rq].sessions++; - - log_debug("%s: %d: start %s", runqs[rq].name, i, - queue_be_decode(batch->content->id)); - - if (batch->retry == NO_RETRY_EXPIRED) { - log_debug("%s: %d: expire", runqs[rq].name, i); - queue_expire(batch); - queue_done(rq, i); - continue; - } - - if (rq == Q_BOUNCE) { - log_debug("%s: %d: socket request", runqs[rq].name, i); - imsg_compose_event(env->sc_ievs[PROC_SMTP], - IMSG_SMTP_ENQUEUE, i, 0, -1, NULL, 0); - continue; - } - - log_debug("%s: %d: send", runqs[rq].name, i); - - fd = queue_be_content_open(batch->content->id, 0); - if (fd < 0) - fatal("queue: content open error"); - - if (rq == Q_LOCAL) - to = PROC_MDA; - else - to = PROC_MTA; - - imsg_compose_event(env->sc_ievs[to], IMSG_BATCH_CREATE, i, 0, - fd, &batch->content->id, sizeof batch->content->id); - - while ((action = SLIST_FIRST(&batch->actions))) { - SLIST_REMOVE_HEAD(&batch->actions, entry); - if (queue_be_action_read(&a, batch->content->id, - action->id) < 0) - fatal("queue: action read error"); - size = action_grow(&action, a.aux); - imsg_compose_event(env->sc_ievs[to], IMSG_BATCH_APPEND, - i, 0, -1, action, size); - free(action); - } - - imsg_compose_event(env->sc_ievs[to], IMSG_BATCH_CLOSE, i, 0, -1, - &a.birth, sizeof a.birth); - } - - /* Sanity check: were we called for no good reason? */ - if (i == -1) - fatalx("queue_send: empty run"); - - queue_sleep(rq); -} - -void -queue_expire(struct batch *batch) -{ - struct action *action, *fail; - struct action_be a; - struct aux aux; - time_t birth; - int error; - - action = SLIST_FIRST(&batch->actions); - if (queue_be_action_read(&a, batch->content->id, action->id) < 0) - fatal("queue: action read error"); - - auxsplit(&aux, a.aux); - birth = a.birth; - - if (a.status[0] == '5' || a.status[0] == '6') { - log_warnx("%s: to=%s, delay=%d, stat=Expired (no bounce due " - "to: larval bounce)", - queue_be_decode(batch->content->id), aux.mail_from, - time(NULL) - birth); - while ((action = SLIST_FIRST(&batch->actions))) { - SLIST_REMOVE_HEAD(&batch->actions, entry); - queue_be_action_delete(batch->content->id, action->id); - queue_mem_content_unref(batch->content); - free(action); - } - return; - } - - if (aux.mail_from[0] == '\0') { - while ((action = SLIST_FIRST(&batch->actions))) { - SLIST_REMOVE_HEAD(&batch->actions, entry); - - if (queue_be_action_read(&a, batch->content->id, - action->id) < 0) - fatal("queue: action read error"); - auxsplit(&aux, a.aux); - log_warnx("%s: to=%s, delay=%d, stat=Expired (no bounce " - "due to: double bounce)", - queue_be_decode(batch->content->id), - rcpt_pretty(&aux), time(NULL) - birth); - - queue_be_action_delete(batch->content->id, action->id); - queue_mem_content_unref(batch->content); - free(action); - } - return; - } - - SLIST_FOREACH(action, &batch->actions, entry) - if (queue_be_action_status(batch->content->id, action->id, - "600 Message expired after too many delivery attempts") < 0) - break; - - if (action) { - fail = action; - error = errno; - } else { - fail = NULL; - error = 0; - } - - while ((action = SLIST_FIRST(&batch->actions))) { - if (action == fail) - break; - SLIST_REMOVE_HEAD(&batch->actions, entry); - - if (queue_be_action_read(&a, batch->content->id, - action->id) < 0) - fatal("queue: action read error"); - auxsplit(&aux, a.aux); - log_info("%s: to=%s, delay=%d, stat=Expired", - queue_be_decode(batch->content->id), rcpt_pretty(&aux), - time(NULL) - birth); - - SLIST_INSERT_HEAD(&batch->content->actions, action, entry); - - queue_bounce_wait(batch->content); - } - - while ((action = SLIST_FIRST(&batch->actions))) { - SLIST_REMOVE_HEAD(&batch->actions, entry); - - if (queue_be_action_read(&a, batch->content->id, - action->id) < 0) - fatal("queue: action read error"); - auxsplit(&aux, a.aux); - log_warnx("%s: to=%s, delay=%d, stat=Expired (no bounce due " - "to: %s)", - queue_be_decode(batch->content->id), rcpt_pretty(&aux), - time(NULL) - birth, strerror(error)); - - queue_be_action_delete(batch->content->id, action->id); - queue_mem_content_unref(batch->content); - free(action); - } -} - -/* - * Grow action to append auxillary info needed by mta and mda. To conserve - * memory, queue calls this routine only for active delivery sessions so that - * pending actions, potentially many, remain tiny. - */ -int -action_grow(struct action **action, char *aux) -{ - struct action *p; - int size; - - size = sizeof *p + strlen(aux) + 1; - p = realloc(*action, size); - if (p == NULL) - fatal(NULL); - strlcpy(p->arg, aux, size - sizeof *p); - *action = p; - - return size; -} - -void -queue_update(int rq, int i, u_int64_t action_id, char *new_status) -{ - struct batch *batch; - struct action *action; - struct action_be a; - struct aux aux; - - batch = table_lookup(runqs[rq].session, runqs[rq].session_sz, i); - if (batch == NULL) - fatalx("queue: bogus update"); - - if (*new_status == '2') { - queue_be_action_delete(batch->content->id, action_id); - queue_mem_content_unref(batch->content); - return; - } - - action = malloc(sizeof *action); - if (action == NULL) - fatal(NULL); - action->id = action_id; - - if (*new_status == '5' || *new_status == '6') { - if (queue_be_action_read(&a, batch->content->id, action_id) < 0) - fatal("queue: queue read error"); - - auxsplit(&aux, a.aux); - - if (aux.mail_from[0] == '\0') { - log_warnx("%s: bounce recipient %s not contactable, " - "bounce dropped", - queue_be_decode(batch->content->id), aux.rcpt_to); - queue_be_action_delete(batch->content->id, action_id); - queue_mem_content_unref(batch->content); - free(action); - return; - } - - if (queue_be_action_status(batch->content->id, action_id, - new_status) < 0) { - log_warn("%s: recipient %s not contactable, bounce not " - "created due to queue error", - queue_be_decode(batch->content->id), aux.rcpt_to); - queue_be_action_delete(batch->content->id, action_id); - queue_mem_content_unref(batch->content); - free(action); - return; - } - - SLIST_INSERT_HEAD(&batch->content->actions, action, entry); - - queue_bounce_wait(batch->content); - } else { - queue_be_action_status(batch->content->id, action_id, new_status); - SLIST_INSERT_HEAD(&batch->actions, action, entry); - } -} - -void -queue_done(int rq, int i) -{ - struct action_be a; - struct batch *batch; - struct action *action; - - /* Take batch off the session table. */ - batch = table_lookup(runqs[rq].session, runqs[rq].session_sz, i); - if (batch == NULL) - fatalx("queue: bogus batch"); - runqs[rq].session[i] = NULL; - runqs[rq].sessions--; - - log_debug("%s: %d: done", runqs[rq].name, i); - - /* All actions sent? */ - if (SLIST_EMPTY(&batch->actions)) { - if (batch->content->ref == 0) { - free(batch->content->ev); - free(batch->content); - } - free(batch); - } else { - /* Batch has actions with temporary errors. */ - action = SLIST_FIRST(&batch->actions); - if (queue_be_action_read(&a, batch->content->id, - action->id) < 0) - fatal("queue: action read error"); - batch->retry = queue_retry(rq, a.birth, batch->retry); - if (batch->retry > a.birth + SMTPD_EXPIRE) - batch->retry = NO_RETRY_EXPIRED; - queue_schedule(rq, batch); - } - - queue_sleep(rq); -} + fdlimit(1.0); + if ((env->sc_maxconn = availdesc() / 4) < 1) + fatalx("runner: fd starvation"); -/* - * Insert batch into runqueue in retry time order. - */ -void -queue_schedule(int rq, struct batch *batch) -{ - struct batch *b, *prev; + config_pipes(env, peers, nitems(peers)); + config_peers(env, peers, nitems(peers)); - prev = NULL; + queue_purge(PATH_INCOMING); + queue_purge(PATH_ENQUEUE); - SLIST_FOREACH(b, &runqs[rq].head, entry) { - if (b->retry >= batch->retry) { - if (prev) - SLIST_INSERT_AFTER(prev, batch, entry); - else - SLIST_INSERT_HEAD(&runqs[rq].head, batch, - entry); - break; - } - prev = b; - } + queue_setup_events(env); + event_dispatch(); + queue_shutdown(); - if (b == NULL) { - if (prev) - SLIST_INSERT_AFTER(prev, batch, entry); - else - SLIST_INSERT_HEAD(&runqs[rq].head, batch, entry); - } + return (0); } -void -queue_sleep(int rq) +struct batch * +batch_by_id(struct smtpd *env, u_int64_t id) { - struct timeval tv; - struct batch *next; - time_t now; - - evtimer_del(&runqs[rq].ev); + struct batch lookup; - if (runqs[rq].sessions >= runqs[rq].max) - return; - - next = SLIST_FIRST(&runqs[rq].head); - if (next == NULL) - return; - - time(&now); - if (next->retry < now) - tv.tv_sec = 0; - else - tv.tv_sec = next->retry - now; - tv.tv_usec = 0; - - log_debug("%s: sleep %lus", runqs[rq].name, tv.tv_sec); - - evtimer_set(&runqs[rq].ev, queue_send, &runqs[rq]); - evtimer_add(&runqs[rq].ev, &tv); + lookup.id = id; + return SPLAY_FIND(batchtree, &env->batch_queue, &lookup); } -/* - * Qmail-like retry schedule. - * - * Local deliveries are tried more often than remote. - */ -time_t -queue_retry(int rq, time_t birth, time_t last) -{ - int n; - - if (last - birth < 0) - n = 0; - else if (rq == Q_RELAY) - n = sqrt(last - birth) + 20; - else - n = sqrt(last - birth) + 10; - return birth + n * n; -} - -/* - * Wait for permanent failures against this content for few more seconds. - * If none arrive, combine them into single batch and put on Q_BOUNCE - * runqueue. If one does arrive, append it, and restart the timer. - */ void -queue_bounce_wait(struct content *content) +queue_purge(char *queuepath) { - struct timeval tv; + char path[MAXPATHLEN]; + struct qwalk *q; - if (content->ev == NULL) { - content->ev = calloc(1, sizeof *content->ev); - if (content->ev == NULL) - fatal(NULL); - } - tv.tv_sec = 3; - tv.tv_usec = 0; - evtimer_del(content->ev); - evtimer_set(content->ev, queue_bounce_schedule, content); - evtimer_add(content->ev, &tv); -} - -void -queue_bounce_schedule(int fd, short event, void *p) -{ - struct content *content = p; - struct batch *batch; - struct action *action; + q = qwalk_new(queuepath); - free(content->ev); - content->ev = NULL; + while (qwalk(q, path)) + queue_delete_layout_message(queuepath, basename(path)); - batch = malloc(sizeof *batch); - if (batch == NULL) - fatal(NULL); - SLIST_INIT(&batch->actions); - batch->content = content; - while ((action = SLIST_FIRST(&content->actions))) { - SLIST_REMOVE_HEAD(&content->actions, entry); - SLIST_INSERT_HEAD(&batch->actions, action, entry); - } - time(&batch->retry); - queue_schedule(Q_BOUNCE, batch); - queue_sleep(Q_BOUNCE); + qwalk_close(q); } void -queue_bounce_init(int i, int sock) +queue_submit_envelope(struct smtpd *env, struct message *message) { - struct smtpd *env = runqs[Q_BOUNCE].env; - struct batch *batch; - struct bounce *s; - struct action *action; - struct action_be a; - struct aux aux; - int fd, header; - - log_debug("%s: %d: init", runqs[Q_BOUNCE].name, i); - - batch = table_lookup(runqs[Q_BOUNCE].session, - runqs[Q_BOUNCE].session_sz, i); - if (batch == NULL) - fatalx("queue: bogus bounce batch"); - - if (sock < 0) { - queue_done(Q_BOUNCE, i); - return; - } - - fd = queue_be_content_open(batch->content->id, 0); - if (fd < 0) - fatal("queue: content open error"); - - s = calloc(1, sizeof *s); - if (s == NULL) - fatal(NULL); - s->batch = batch; - s->pcb = client_init(sock, fd, env->sc_hostname, 1); - s->id = i; - client_sender(s->pcb, ""); - client_ssl_optional(s->pcb); - - header = 0; - SLIST_FOREACH(action, &batch->actions, entry) { - if (queue_be_action_read(&a, batch->content->id, - action->id) < 0) - fatal("queue: backend read error"); - auxsplit(&aux, a.aux); - if (header == 0) { - client_rcpt(s->pcb, "%s", aux.mail_from); - client_printf(s->pcb, - "From: Mailer Daemon <MAILER-DAEMON@%s>\n" - "To: %s\n" - "Subject: Delivery status notification\n" - "Date: %s\n" - "\n" - "This is automated mail delivery notification, please DO NOT REPLY.\n" - "An error has occurred while attempting to deliver your mail to the\n" - "following recipients:\n" - "\n", - env->sc_hostname, aux.mail_from, - time_to_text(time(NULL))); - header = 1; - } - if (strlen(a.status) > 4 && (a.status[0] == '1' || a.status[0] == '6')) - a.status += 4; - client_printf(s->pcb, "%s: %s\n\n", aux.rcpt_to, a.status); - } - client_printf(s->pcb, "Below is a copy of your mail:\n\n"); - - session_socket_blockmode(sock, BM_NONBLOCK); - event_set(&s->ev, sock, EV_READ|EV_WRITE, queue_bounce_event, s); - event_add(&s->ev, &s->pcb->timeout); + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_QUEUE_SUBMIT_ENVELOPE, 0, 0, -1, + message, sizeof(struct message)); } void -queue_bounce_event(int fd, short event, void *p) +queue_commit_envelopes(struct smtpd *env, struct message *message) { - struct action *action; - struct bounce *s = p; - char *status = NULL; - - if (event & EV_TIMEOUT) { - status = "100 timeout"; - goto out; - } - - switch (client_talk(s->pcb, event & EV_WRITE)) { - case CLIENT_STOP_WRITE: - goto ro; - case CLIENT_WANT_WRITE: - goto rw; - case CLIENT_RCPT_FAIL: - status = s->pcb->reply; - break; - case CLIENT_DONE: - status = s->pcb->status; - break; - default: - fatalx("queue: bad client_talk"); - } - -out: - log_debug("%s: %d: last event", runqs[Q_BOUNCE].name, s->id); - - if (*status == '5' || *status == '6') - fatalx("queue: smtp refused bounce"); - if (*status == '2') { - while ((action = SLIST_FIRST(&s->batch->actions))) { - SLIST_REMOVE_HEAD(&s->batch->actions, entry); - queue_be_action_delete(s->batch->content->id, - action->id); - queue_mem_content_unref(s->batch->content); - free(action); - } - } - queue_done(Q_BOUNCE, s->id); - client_close(s->pcb); - free(s); - return; - -ro: - event_set(&s->ev, fd, EV_READ, queue_bounce_event, s); - event_add(&s->ev, &s->pcb->timeout); - return; - -rw: - event_set(&s->ev, fd, EV_READ|EV_WRITE, queue_bounce_event, s); - event_add(&s->ev, &s->pcb->timeout); -} - -int -queue_detect_loop(struct incoming *s) -{ - FILE *fp; - char *buf, *lbuf; - size_t len, received; - int fd, i; - - fd = queue_be_content_open(s->content->id, 0); - if (fd < 0) - fatal("queue_detect_loop: content open error"); - fp = fdopen(fd, "r"); - if (fp == NULL) - fatal("queue_detect_loop: fdopen"); - - received = 0; - lbuf = NULL; - - while ((buf = fgetln(fp, &len))) { - free(lbuf); - lbuf = NULL; - - if (buf[len - 1] == '\n') { - buf[len - 1] = '\0'; - len--; - } else { - /* EOF without EOL, copy and add the NUL */ - if ((lbuf = malloc(len + 1)) == NULL) - fatal(NULL); - memcpy(lbuf, buf, len); - lbuf[len] = '\0'; - buf = lbuf; - } - - if (*buf == '\0') { - buf = NULL; - break; - } - - if (strncasecmp(buf, "Received:", 9) == 0) { - received++; - if (received >= MAX_HOPS_COUNT) - break; - } else if (strncasecmp(buf, "Delivered-To:", 13) == 0) { - buf += 13; - while (isspace(*buf)) - buf++; - buf[strcspn(buf, " \t")] = '\0'; - for (i = 0; i < s->nlocal; i++) - if (strcmp(s->local[i], buf) == 0) - break; - if (i < s->nlocal) - break; - } - } - free(lbuf); - fclose(fp); - - return (buf == NULL ? 0 : -1); + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_QUEUE_COMMIT_ENVELOPES, 0, 0, -1, + message, sizeof(struct message)); } diff --git a/usr.sbin/smtpd/queue_backend.c b/usr.sbin/smtpd/queue_backend.c deleted file mode 100644 index f07481e0ec2..00000000000 --- a/usr.sbin/smtpd/queue_backend.c +++ /dev/null @@ -1,330 +0,0 @@ -/* $OpenBSD: queue_backend.c,v 1.1 2010/05/31 23:38:56 jacekm Exp $ */ - -/* - * Copyright (c) 2010 Jacek Masiulaniec <jacekm@dobremiasto.net> - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <sys/types.h> -#include <sys/stat.h> - -#include <ctype.h> -#include <errno.h> -#include <fcntl.h> -#include <fts.h> -#include <libgen.h> -#include <limits.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "queue_backend.h" - -static char path[PATH_MAX]; - -static char *idchars = "ABCDEFGHIJKLMNOPQRSTUVWYZabcdefghijklmnopqrstuvwxyz0123456789"; - -int -queue_be_content_create(u_int64_t *content_id) -{ - int c, fd; - - c = idchars[arc4random_uniform(61)]; - snprintf(path, sizeof path, "content/%c/%cXXXXXXX", c, c); - fd = mkstemp(path); - if (fd < 0) { - if (errno != ENOENT) - return -1; - if (mkdir(dirname(path), 0700) < 0) - return -1; - fd = mkstemp(path); - if (fd < 0) - return -1; - } - close(fd); - *content_id = queue_be_encode(path + 10); - return 0; -} - -int -queue_be_content_open(u_int64_t content_id, int wr) -{ - char *id; - - id = queue_be_decode(content_id); - snprintf(path, sizeof path, "content/%c/%s", id[0], id); - return open(path, wr ? O_RDWR|O_APPEND|O_EXLOCK : O_RDONLY|O_SHLOCK); -} - -void -queue_be_content_delete(u_int64_t content_id) -{ - char *id; - - id = queue_be_decode(content_id); - snprintf(path, sizeof path, "content/%c/%s", id[0], id); - unlink(path); -} - -int -queue_be_action_new(u_int64_t content_id, u_int64_t *action_id, char *aux) -{ - FILE *fp; - char *id; - int fd; - - id = queue_be_decode(content_id); - snprintf(path, sizeof path, "action/%c/%s,XXXXXXXX", id[0], id); - fd = mkstemp(path); - if (fd < 0) { - if (errno != ENOENT) - return -1; - if (mkdir(dirname(path), 0700) < 0) - return -1; - fd = mkstemp(path); - if (fd < 0) - return -1; - } - fp = fdopen(fd, "w+"); - if (fp == NULL) { - unlink(path); - return -1; - } - fprintf(fp, "%s\n", aux); - if (fclose(fp) == EOF) { - unlink(path); - return -1; - } - *action_id = queue_be_encode(path + 18); - return 0; -} - -int -queue_be_action_read(struct action_be *a, u_int64_t content_id, u_int64_t action_id) -{ - static char status[2048]; - static char aux[2048]; - struct stat sb_status, sb_content; - char *id; - FILE *fp; - - bzero(a, sizeof *a); - a->content_id = content_id; - a->action_id = action_id; - - /* - * Auxillary params for mta and mda. - */ - id = queue_be_decode(content_id); - snprintf(path, sizeof path, "action/%c/%s,", id[0], id); - strlcat(path, queue_be_decode(action_id), sizeof path); - fp = fopen(path, "r"); - if (fp == NULL) - return -1; - if (fgets(aux, sizeof aux, fp) == NULL) { - fclose(fp); - return -1; - } - fclose(fp); - aux[strcspn(aux, "\n")] = '\0'; - a->aux = aux; - - /* - * Error status message. - */ - id = queue_be_decode(content_id); - snprintf(path, sizeof path, "status/%c/%s,", id[0], id); - strlcat(path, queue_be_decode(action_id), sizeof path); - fp = fopen(path, "r"); - if (fp) { - if (fgets(status, sizeof status, fp) != NULL) - status[strcspn(status, "\n")] = '\0'; - else - status[0] = '\0'; - if (fstat(fileno(fp), &sb_status) < 0) { - fclose(fp); - return -1; - } - fclose(fp); - } else - status[0] = '\0'; - a->status = status; - - /* - * Message birth time. - * - * For bounces, use mtime of the status file. - * For non-bounces, use mtime of the content file. - */ - id = queue_be_decode(content_id); - snprintf(path, sizeof path, "content/%c/%s", id[0], id); - if (stat(path, &sb_content) < 0) - return -1; - if (sb_content.st_mode & S_IWUSR) - a->birth = 0; - else if (status[0] == '5' || status[0] == '6') - a->birth = sb_status.st_mtime; - else - a->birth = sb_content.st_mtime; - - return 0; -} - -int -queue_be_action_status(u_int64_t content_id, u_int64_t action_id, char *status) -{ - FILE *fp; - char *id; - - id = queue_be_decode(content_id); - snprintf(path, sizeof path, "status/%c/%s,", id[0], id); - strlcat(path, queue_be_decode(action_id), sizeof path); - fp = fopen(path, "w+"); - if (fp == NULL) { - if (errno != ENOENT) - return -1; - mkdir(dirname(path), 0700); - fp = fopen(path, "w+"); - if (fp == NULL) - return -1; - } - if (fprintf(fp, "%s\n", status) == -1) { - fclose(fp); - return -1; - } - if (fclose(fp) == EOF) - return -1; - return 0; -} - -void -queue_be_action_delete(u_int64_t content_id, u_int64_t action_id) -{ - char *id, *dir[] = { "action", "status" }; - u_int i; - - for (i = 0; i < 2; i++) { - id = queue_be_decode(content_id); - snprintf(path, sizeof path, "%s/%c/%s,", dir[i], id[0], id); - id = queue_be_decode(action_id); - strlcat(path, id, sizeof path); - unlink(path); - } -} - -int -queue_be_commit(u_int64_t content_id) -{ - char *id; - - id = queue_be_decode(content_id); - snprintf(path, sizeof path, "content/%c/%s", id[0], id); - if (utimes(path, NULL) < 0 || chmod(path, 0400) < 0) - return -1; - return 0; -} - -int -queue_be_getnext(struct action_be *a) -{ - static FTS *fts; - static FTSENT *fe; - char *dir[] = { "action", NULL }; - - if (fts == NULL) { - fts = fts_open(dir, FTS_PHYSICAL|FTS_NOCHDIR, NULL); - if (fts == NULL) - return -1; - } - - for (;;) { - fe = fts_read(fts); - if (fe == NULL) { - if (errno) { - fts_close(fts); - return -1; - } else { - if (fts_close(fts) < 0) - return -1; - a->content_id = 0; - a->action_id = 0; - return 0; - } - } - switch (fe->fts_info) { - case FTS_F: - break; - case FTS_D: - case FTS_DP: - continue; - default: - fts_close(fts); - return -1; - } - break; - } - - if (fe->fts_namelen != 17 || fe->fts_name[8] != ',') { - fts_close(fts); - return -1; - } - a->content_id = queue_be_encode(fe->fts_name); - a->action_id = queue_be_encode(fe->fts_name + 9); - if (queue_be_action_read(a, a->content_id, a->action_id) < 0) - return -2; - - return 0; -} - -char * -queue_be_decode(u_int64_t id) -{ - static char txt[9]; - - memcpy(txt, &id, sizeof id); - txt[8] = '\0'; - return txt; -} - -u_int64_t -queue_be_encode(const char *txt) -{ - u_int64_t id; - - if (strlen(txt) < sizeof id) - id = INVALID_ID; - else - memcpy(&id, txt, sizeof id); - - return id; -} - -int -queue_be_init(char *prefix, uid_t uid, gid_t gid) -{ - char *dir[] = { "action", "content", "status" }; - int i; - - for (i = 0; i < 3; i++) { - snprintf(path, sizeof path, "%s/%s", prefix, dir[i]); - if (mkdir(path, 0700) < 0 && errno != EEXIST) - return -1; - if (chmod(path, 0700) < 0) - return -1; - if (chown(path, uid, gid) < 0) - return -1; - } - return 0; -} diff --git a/usr.sbin/smtpd/queue_backend.h b/usr.sbin/smtpd/queue_backend.h deleted file mode 100644 index 6a74e2e9119..00000000000 --- a/usr.sbin/smtpd/queue_backend.h +++ /dev/null @@ -1,44 +0,0 @@ -/* $OpenBSD: queue_backend.h,v 1.1 2010/05/31 23:38:56 jacekm Exp $ */ - -/* - * Copyright (c) 2010 Jacek Masiulaniec <jacekm@dobremiasto.net> - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#define INVALID_ID 1 - -struct action_be { - u_int64_t content_id; - u_int64_t action_id; - time_t birth; - char *aux; - char *status; -}; - -int queue_be_content_create(u_int64_t *); -int queue_be_content_open(u_int64_t, int); -void queue_be_content_delete(u_int64_t); - -int queue_be_action_new(u_int64_t, u_int64_t *, char *); -int queue_be_action_read(struct action_be *, u_int64_t, u_int64_t); -int queue_be_action_status(u_int64_t, u_int64_t, char *); -void queue_be_action_delete(u_int64_t, u_int64_t); - -int queue_be_commit(u_int64_t); - -u_int64_t queue_be_encode(const char *); -char *queue_be_decode(u_int64_t); - -int queue_be_init(char *, uid_t, gid_t); -int queue_be_getnext(struct action_be *); diff --git a/usr.sbin/smtpd/queue_shared.c b/usr.sbin/smtpd/queue_shared.c new file mode 100644 index 00000000000..a63dc9dfc32 --- /dev/null +++ b/usr.sbin/smtpd/queue_shared.c @@ -0,0 +1,817 @@ +/* $OpenBSD: queue_shared.c,v 1.29 2010/06/01 19:47:09 jacekm Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2008-2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "smtpd.h" + +#define QWALK_AGAIN 0x1 +#define QWALK_RECURSE 0x2 +#define QWALK_RETURN 0x3 + +struct qwalk { + char path[MAXPATHLEN]; + DIR *dirs[3]; + int (*filefn)(struct qwalk *, char *); + int bucket; + int level; + int strict; +}; + +int walk_simple(struct qwalk *, char *); +int walk_queue(struct qwalk *, char *); + +void display_envelope(struct message *, int); +void getflag(u_int *, int, char *, char *, size_t); + +int +queue_create_layout_message(char *queuepath, char *message_id) +{ + char rootdir[MAXPATHLEN]; + char evpdir[MAXPATHLEN]; + + if (! bsnprintf(rootdir, sizeof(rootdir), "%s/%d.XXXXXXXXXXXXXXXX", + queuepath, time(NULL))) + fatalx("queue_create_layout_message: snprintf"); + + if (mkdtemp(rootdir) == NULL) { + if (errno == ENOSPC) { + log_debug("FAILED WITH ENOSPC"); + bzero(message_id, MAX_ID_SIZE); + return 0; + } + fatal("queue_create_layout_message: mkdtemp"); + } + + if (strlcpy(message_id, rootdir + strlen(queuepath) + 1, MAX_ID_SIZE) + >= MAX_ID_SIZE) + fatalx("queue_create_layout_message: truncation"); + + if (! bsnprintf(evpdir, sizeof(evpdir), "%s%s", rootdir, + PATH_ENVELOPES)) + fatalx("queue_create_layout_message: snprintf"); + + if (mkdir(evpdir, 0700) == -1) { + if (errno == ENOSPC) { + log_debug("FAILED WITH ENOSPC"); + rmdir(rootdir); + bzero(message_id, MAX_ID_SIZE); + return 0; + } + fatal("queue_create_layout_message: mkdir"); + } + return 1; +} + +void +queue_delete_layout_message(char *queuepath, char *msgid) +{ + char rootdir[MAXPATHLEN]; + char purgedir[MAXPATHLEN]; + + if (! bsnprintf(rootdir, sizeof(rootdir), "%s/%s", queuepath, msgid)) + fatalx("snprintf"); + + if (! bsnprintf(purgedir, sizeof(purgedir), "%s/%s", PATH_PURGE, msgid)) + fatalx("snprintf"); + + if (rename(rootdir, purgedir) == -1) { + log_debug("ID: %s", msgid); + log_debug("PATH: %s", rootdir); + log_debug("PURGE: %s", purgedir); + fatal("queue_delete_layout_message: rename"); + } +} + +int +queue_record_layout_envelope(char *queuepath, struct message *message) +{ + char evpname[MAXPATHLEN]; + FILE *fp; + int fd; + +again: + if (! bsnprintf(evpname, sizeof(evpname), "%s/%s%s/%s.%qu", queuepath, + message->message_id, PATH_ENVELOPES, message->message_id, + (u_int64_t)arc4random())) + fatalx("queue_record_incoming_envelope: snprintf"); + + fd = open(evpname, O_WRONLY|O_CREAT|O_EXCL, 0600); + if (fd == -1) { + if (errno == EEXIST) + goto again; + if (errno == ENOSPC || errno == ENFILE) + goto tempfail; + fatal("queue_record_incoming_envelope: open"); + } + + fp = fdopen(fd, "w"); + if (fp == NULL) + fatal("queue_record_incoming_envelope: fdopen"); + + message->creation = time(NULL); + if (strlcpy(message->message_uid, strrchr(evpname, '/') + 1, + sizeof(message->message_uid)) >= sizeof(message->message_uid)) + fatalx("queue_record_incoming_envelope: truncation"); + + if (fwrite(message, sizeof (struct message), 1, fp) != 1) { + if (errno == ENOSPC) + goto tempfail; + fatal("queue_record_incoming_envelope: write"); + } + + if (! safe_fclose(fp)) + goto tempfail; + + return 1; + +tempfail: + unlink(evpname); + close(fd); + message->creation = 0; + message->message_uid[0] = '\0'; + + return 0; +} + +int +queue_remove_layout_envelope(char *queuepath, struct message *message) +{ + char pathname[MAXPATHLEN]; + + if (! bsnprintf(pathname, sizeof(pathname), "%s/%s%s/%s", queuepath, + message->message_id, PATH_ENVELOPES, message->message_uid)) + fatal("queue_remove_incoming_envelope: snprintf"); + + if (unlink(pathname) == -1) + fatal("queue_remove_incoming_envelope: unlink"); + + return 1; +} + +int +queue_commit_layout_message(char *queuepath, struct message *messagep) +{ + char rootdir[MAXPATHLEN]; + char queuedir[MAXPATHLEN]; + + if (! bsnprintf(rootdir, sizeof(rootdir), "%s/%s", queuepath, + messagep->message_id)) + fatal("queue_commit_message_incoming: snprintf"); + + if (! bsnprintf(queuedir, sizeof(queuedir), "%s/%d", PATH_QUEUE, + queue_hash(messagep->message_id))) + fatal("queue_commit_message_incoming: snprintf"); + + if (mkdir(queuedir, 0700) == -1) { + if (errno == ENOSPC) + return 0; + if (errno != EEXIST) + fatal("queue_commit_message_incoming: mkdir"); + } + + if (strlcat(queuedir, "/", sizeof(queuedir)) >= sizeof(queuedir) || + strlcat(queuedir, messagep->message_id, sizeof(queuedir)) >= + sizeof(queuedir)) + fatalx("queue_commit_incoming_message: truncation"); + + if (rename(rootdir, queuedir) == -1) { + if (errno == ENOSPC) + return 0; + fatal("queue_commit_message_incoming: rename"); + } + + return 1; +} + +int +queue_open_layout_messagefile(char *queuepath, struct message *messagep) +{ + char pathname[MAXPATHLEN]; + + if (! bsnprintf(pathname, sizeof(pathname), "%s/%s/message", queuepath, + messagep->message_id)) + fatal("queue_open_incoming_message_file: snprintf"); + + return open(pathname, O_CREAT|O_EXCL|O_RDWR, 0600); +} + +int +enqueue_create_layout(char *msgid) +{ + return queue_create_layout_message(PATH_ENQUEUE, msgid); +} + +void +enqueue_delete_message(char *msgid) +{ + queue_delete_layout_message(PATH_ENQUEUE, msgid); +} + +int +enqueue_record_envelope(struct message *message) +{ + return queue_record_layout_envelope(PATH_ENQUEUE, message); +} + +int +enqueue_remove_envelope(struct message *message) +{ + return queue_remove_layout_envelope(PATH_ENQUEUE, message); +} + +int +enqueue_commit_message(struct message *message) +{ + return queue_commit_layout_message(PATH_ENQUEUE, message); +} + +int +enqueue_open_messagefile(struct message *message) +{ + return queue_open_layout_messagefile(PATH_ENQUEUE, message); +} + +int +bounce_create_layout(char *msgid, struct message *message) +{ + char msgpath[MAXPATHLEN]; + char lnkpath[MAXPATHLEN]; + + if (! queue_create_layout_message(PATH_BOUNCE, msgid)) + return 0; + + if (! bsnprintf(msgpath, sizeof(msgpath), "%s/%d/%s/message", + PATH_QUEUE, queue_hash(message->message_id), + message->message_id)) + return 0; + + if (! bsnprintf(lnkpath, sizeof(lnkpath), "%s/%s/message", + PATH_BOUNCE, msgid)) + return 0; + + if (link(msgpath, lnkpath) == -1) + fatal("link"); + + return 1; +} + +void +bounce_delete_message(char *msgid) +{ + queue_delete_layout_message(PATH_BOUNCE, msgid); +} + +int +bounce_record_envelope(struct message *message) +{ + message->lasttry = 0; + message->retry = 0; + return queue_record_layout_envelope(PATH_BOUNCE, message); +} + +int +bounce_remove_envelope(struct message *message) +{ + return queue_remove_layout_envelope(PATH_BOUNCE, message); +} + +int +bounce_commit_message(struct message *message) +{ + return queue_commit_layout_message(PATH_BOUNCE, message); +} + +int +bounce_record_message(struct message *messagep) +{ + char msgid[MAX_ID_SIZE]; + struct message mbounce; + + if (messagep->type == T_BOUNCE_MESSAGE) { + log_debug("mailer daemons loop detected !"); + return 0; + } + + mbounce = *messagep; + mbounce.type = T_BOUNCE_MESSAGE; + mbounce.status &= ~S_MESSAGE_PERMFAILURE; + + if (! bounce_create_layout(msgid, messagep)) + return 0; + + strlcpy(mbounce.message_id, msgid, sizeof(mbounce.message_id)); + if (! bounce_record_envelope(&mbounce)) + return 0; + + return bounce_commit_message(&mbounce); +} + +int +queue_create_incoming_layout(char *msgid) +{ + return queue_create_layout_message(PATH_INCOMING, msgid); +} + +void +queue_delete_incoming_message(char *msgid) +{ + queue_delete_layout_message(PATH_INCOMING, msgid); +} + +int +queue_record_incoming_envelope(struct message *message) +{ + return queue_record_layout_envelope(PATH_INCOMING, message); +} + +int +queue_remove_incoming_envelope(struct message *message) +{ + return queue_remove_layout_envelope(PATH_INCOMING, message); +} + +int +queue_commit_incoming_message(struct message *message) +{ + return queue_commit_layout_message(PATH_INCOMING, message); +} + +int +queue_open_incoming_message_file(struct message *message) +{ + return queue_open_layout_messagefile(PATH_INCOMING, message); +} + +int +queue_open_message_file(char *msgid) +{ + int fd; + char pathname[MAXPATHLEN]; + u_int16_t hval; + + hval = queue_hash(msgid); + + if (! bsnprintf(pathname, sizeof(pathname), "%s/%d/%s/message", + PATH_QUEUE, hval, msgid)) + fatal("queue_open_message_file: snprintf"); + + if ((fd = open(pathname, O_RDONLY)) == -1) + fatal("queue_open_message_file: open"); + + return fd; +} + +void +queue_delete_message(char *msgid) +{ + char rootdir[MAXPATHLEN]; + char evpdir[MAXPATHLEN]; + char msgpath[MAXPATHLEN]; + u_int16_t hval; + + hval = queue_hash(msgid); + if (! bsnprintf(rootdir, sizeof(rootdir), "%s/%d/%s", PATH_QUEUE, + hval, msgid)) + fatal("queue_delete_message: snprintf"); + + if (! bsnprintf(evpdir, sizeof(evpdir), "%s%s", rootdir, + PATH_ENVELOPES)) + fatal("queue_delete_message: snprintf"); + + if (! bsnprintf(msgpath, sizeof(msgpath), "%s/message", rootdir)) + fatal("queue_delete_message: snprintf"); + + if (unlink(msgpath) == -1) + fatal("queue_delete_message: unlink"); + + if (rmdir(evpdir) == -1) { + /* It is ok to fail rmdir with ENOENT here + * because upon successful delivery of the + * last envelope, we remove the directory. + */ + if (errno != ENOENT) + fatal("queue_delete_message: rmdir"); + } + + if (rmdir(rootdir) == -1) + fatal("#2 queue_delete_message: rmdir"); + + if (! bsnprintf(rootdir, sizeof(rootdir), "%s/%d", PATH_QUEUE, hval)) + fatal("queue_delete_message: snprintf"); + + rmdir(rootdir); + + return; +} + +void +queue_message_update(struct message *messagep) +{ + messagep->flags &= ~F_MESSAGE_PROCESSING; + messagep->status &= ~(S_MESSAGE_ACCEPTED|S_MESSAGE_REJECTED); + messagep->batch_id = 0; + messagep->retry++; + + if (messagep->status & S_MESSAGE_PERMFAILURE) { + if (messagep->type != T_BOUNCE_MESSAGE && + messagep->sender.user[0] != '\0') + bounce_record_message(messagep); + queue_remove_envelope(messagep); + return; + } + + if (messagep->status & S_MESSAGE_TEMPFAILURE) { + messagep->status &= ~S_MESSAGE_TEMPFAILURE; + queue_update_envelope(messagep); + return; + } + + /* no error, remove envelope */ + queue_remove_envelope(messagep); +} + +int +queue_remove_envelope(struct message *messagep) +{ + char pathname[MAXPATHLEN]; + u_int16_t hval; + + hval = queue_hash(messagep->message_id); + + if (! bsnprintf(pathname, sizeof(pathname), "%s/%d/%s%s/%s", + PATH_QUEUE, hval, messagep->message_id, PATH_ENVELOPES, + messagep->message_uid)) + fatal("queue_remove_envelope: snprintf"); + + if (unlink(pathname) == -1) + fatal("queue_remove_envelope: unlink"); + + if (! bsnprintf(pathname, sizeof(pathname), "%s/%d/%s%s", PATH_QUEUE, + hval, messagep->message_id, PATH_ENVELOPES)) + fatal("queue_remove_envelope: snprintf"); + + if (rmdir(pathname) != -1) + queue_delete_message(messagep->message_id); + + return 1; +} + +int +queue_update_envelope(struct message *messagep) +{ + char temp[MAXPATHLEN]; + char dest[MAXPATHLEN]; + FILE *fp; + u_int64_t batch_id; + + batch_id = messagep->batch_id; + messagep->batch_id = 0; + + if (! bsnprintf(temp, sizeof(temp), "%s/envelope.tmp", PATH_QUEUE)) + fatalx("queue_update_envelope"); + + if (! bsnprintf(dest, sizeof(dest), "%s/%d/%s%s/%s", PATH_QUEUE, + queue_hash(messagep->message_id), messagep->message_id, + PATH_ENVELOPES, messagep->message_uid)) + fatal("queue_update_envelope: snprintf"); + + fp = fopen(temp, "w"); + if (fp == NULL) { + if (errno == ENOSPC || errno == ENFILE) + goto tempfail; + fatal("queue_update_envelope: open"); + } + if (fwrite(messagep, sizeof(struct message), 1, fp) != 1) { + if (errno == ENOSPC) + goto tempfail; + fatal("queue_update_envelope: fwrite"); + } + if (! safe_fclose(fp)) + goto tempfail; + + if (rename(temp, dest) == -1) { + if (errno == ENOSPC) + goto tempfail; + fatal("queue_update_envelope: rename"); + } + + messagep->batch_id = batch_id; + return 1; + +tempfail: + if (unlink(temp) == -1) + fatal("queue_update_envelope: unlink"); + if (fp) + fclose(fp); + + messagep->batch_id = batch_id; + return 0; +} + +int +queue_load_envelope(struct message *messagep, char *evpid) +{ + char pathname[MAXPATHLEN]; + char msgid[MAX_ID_SIZE]; + FILE *fp; + + if (strlcpy(msgid, evpid, sizeof(msgid)) >= sizeof(msgid)) + fatalx("queue_load_envelope: truncation"); + *strrchr(msgid, '.') = '\0'; + + if (! bsnprintf(pathname, sizeof(pathname), "%s/%d/%s%s/%s", PATH_QUEUE, + queue_hash(msgid), msgid, PATH_ENVELOPES, evpid)) + fatalx("queue_load_envelope: snprintf"); + + fp = fopen(pathname, "r"); + if (fp == NULL) { + if (errno == ENOSPC || errno == ENFILE) + return 0; + fatal("queue_load_envelope: fopen"); + } + if (fread(messagep, sizeof(struct message), 1, fp) != 1) + fatal("queue_load_envelope: fread"); + fclose(fp); + + return 1; +} + +u_int16_t +queue_hash(char *msgid) +{ + u_int16_t h; + + for (h = 5381; *msgid; msgid++) + h = ((h << 5) + h) + *msgid; + + return (h % DIRHASH_BUCKETS); +} + +struct qwalk * +qwalk_new(char *path) +{ + struct qwalk *q; + + q = calloc(1, sizeof(struct qwalk)); + if (q == NULL) + fatal("qwalk_new: calloc"); + + strlcpy(q->path, path, sizeof(q->path)); + + q->level = 0; + q->strict = 0; + q->filefn = walk_simple; + + if (smtpd_process == PROC_QUEUE || smtpd_process == PROC_RUNNER) + q->strict = 1; + + if (strcmp(path, PATH_QUEUE) == 0) + q->filefn = walk_queue; + + q->dirs[0] = opendir(q->path); + if (q->dirs[0] == NULL) + fatal("qwalk_new: opendir"); + + return (q); +} + +int +qwalk(struct qwalk *q, char *filepath) +{ + struct dirent *dp; + +again: + errno = 0; + dp = readdir(q->dirs[q->level]); + if (errno) + fatal("qwalk: readdir"); + if (dp == NULL) { + closedir(q->dirs[q->level]); + q->dirs[q->level] = NULL; + if (q->level == 0) + return (0); + q->level--; + goto again; + } + + if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) + goto again; + + switch (q->filefn(q, dp->d_name)) { + case QWALK_AGAIN: + goto again; + case QWALK_RECURSE: + goto recurse; + case QWALK_RETURN: + if (! bsnprintf(filepath, MAXPATHLEN, "%s/%s", q->path, + dp->d_name)) + fatalx("qwalk: snprintf"); + return (1); + default: + fatalx("qwalk: callback failed"); + } + +recurse: + q->level++; + q->dirs[q->level] = opendir(q->path); + if (q->dirs[q->level] == NULL) { + if (errno == ENOENT && !q->strict) { + q->level--; + goto again; + } + fatal("qwalk: opendir"); + } + goto again; +} + +void +qwalk_close(struct qwalk *q) +{ + int i; + + for (i = 0; i <= q->level; i++) + if (q->dirs[i]) + closedir(q->dirs[i]); + + bzero(q, sizeof(struct qwalk)); + free(q); +} + +int +walk_simple(struct qwalk *q, char *fname) +{ + return (QWALK_RETURN); +} + +int +walk_queue(struct qwalk *q, char *fname) +{ + const char *errstr; + + switch (q->level) { + case 0: + if (strcmp(fname, "envelope.tmp") == 0) + return (QWALK_AGAIN); + q->bucket = strtonum(fname, 0, DIRHASH_BUCKETS - 1, &errstr); + if (errstr) { + log_warnx("walk_queue: invalid bucket: %s", fname); + return (QWALK_AGAIN); + } + if (! bsnprintf(q->path, sizeof(q->path), "%s/%d", PATH_QUEUE, + q->bucket)) + fatalx("walk_queue: snprintf"); + return (QWALK_RECURSE); + case 1: + if (! bsnprintf(q->path, sizeof(q->path), "%s/%d/%s%s", + PATH_QUEUE, q->bucket, fname, PATH_ENVELOPES)) + fatalx("walk_queue: snprintf"); + return (QWALK_RECURSE); + case 2: + return (QWALK_RETURN); + } + + return (-1); +} + +void +show_queue(char *queuepath, int flags) +{ + char path[MAXPATHLEN]; + struct message message; + struct qwalk *q; + FILE *fp; + + log_init(1); + + if (chroot(PATH_SPOOL) == -1 || chdir(".") == -1) + err(1, "%s", PATH_SPOOL); + + q = qwalk_new(queuepath); + + while (qwalk(q, path)) { + fp = fopen(path, "r"); + if (fp == NULL) { + if (errno == ENOENT) + continue; + err(1, "%s", path); + } + + errno = 0; + if (fread(&message, sizeof(struct message), 1, fp) != 1) + err(1, "%s", path); + fclose(fp); + + display_envelope(&message, flags); + } + + qwalk_close(q); +} + +void +display_envelope(struct message *envelope, int flags) +{ + char status[128]; + + status[0] = '\0'; + + getflag(&envelope->status, S_MESSAGE_TEMPFAILURE, "TEMPFAIL", + status, sizeof(status)); + + if (envelope->status) + errx(1, "%s: unexpected status 0x%04x", envelope->message_uid, + envelope->status); + + getflag(&envelope->flags, F_MESSAGE_BOUNCE, "BOUNCE", + status, sizeof(status)); + getflag(&envelope->flags, F_MESSAGE_AUTHENTICATED, "AUTH", + status, sizeof(status)); + getflag(&envelope->flags, F_MESSAGE_PROCESSING, "PROCESSING", + status, sizeof(status)); + getflag(&envelope->flags, F_MESSAGE_SCHEDULED, "SCHEDULED", + status, sizeof(status)); + getflag(&envelope->flags, F_MESSAGE_ENQUEUED, "ENQUEUED", + status, sizeof(status)); + getflag(&envelope->flags, F_MESSAGE_FORCESCHEDULE, "SCHEDULED_MANUAL", + status, sizeof(status)); + + if (envelope->flags) + errx(1, "%s: unexpected flags 0x%04x", envelope->message_uid, + envelope->flags); + + if (status[0]) + status[strlen(status) - 1] = '\0'; + else + strlcpy(status, "-", sizeof(status)); + + switch (envelope->type) { + case T_MDA_MESSAGE: + printf("MDA"); + break; + case T_MTA_MESSAGE: + printf("MTA"); + break; + case T_BOUNCE_MESSAGE: + printf("BOUNCE"); + break; + default: + printf("UNKNOWN"); + } + + printf("|%s|%s|%s@%s|%s@%s|%d|%u", + envelope->message_uid, + status, + envelope->sender.user, envelope->sender.domain, + envelope->recipient.user, envelope->recipient.domain, + envelope->lasttry, + envelope->retry); + + if (envelope->session_errorline[0] != '\0') + printf("|%s", envelope->session_errorline); + + printf("\n"); +} + +void +getflag(u_int *bitmap, int bit, char *bitstr, char *buf, size_t len) +{ + if (*bitmap & bit) { + *bitmap &= ~bit; + strlcat(buf, bitstr, len); + strlcat(buf, ",", len); + } +} diff --git a/usr.sbin/smtpd/runner.c b/usr.sbin/smtpd/runner.c new file mode 100644 index 00000000000..9c2c4579c32 --- /dev/null +++ b/usr.sbin/smtpd/runner.c @@ -0,0 +1,889 @@ +/* $OpenBSD: runner.c,v 1.89 2010/06/01 19:47:09 jacekm Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2008-2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <libgen.h> +#include <netdb.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "smtpd.h" + +void runner_imsg(struct smtpd *, struct imsgev *, struct imsg *); + +__dead void runner_shutdown(void); +void runner_sig_handler(int, short, void *); +void runner_setup_events(struct smtpd *); +void runner_disable_events(struct smtpd *); + +void runner_reset_flags(void); +void runner_process_offline(struct smtpd *); + +void runner_timeout(int, short, void *); + +void runner_process_queue(struct smtpd *); +void runner_process_runqueue(struct smtpd *); +void runner_process_batchqueue(struct smtpd *); + +int runner_message_schedule(struct message *, time_t); + +void runner_purge_run(void); +void runner_purge_message(char *); + +int runner_check_loop(struct message *); + +struct batch *batch_record(struct smtpd *, struct message *); +struct batch *batch_lookup(struct smtpd *, struct message *); + +int runner_force_envelope_schedule(char *); +int runner_force_message_schedule(char *); + +int runner_force_envelope_remove(char *); +int runner_force_message_remove(char *); + +void +runner_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) +{ + struct message *m; + struct remove *rem; + struct sched *sched; + + switch (imsg->hdr.type) { + case IMSG_QUEUE_MESSAGE_UPDATE: + env->stats->runner.active--; + queue_message_update(imsg->data); + return; + + case IMSG_MDA_SESS_NEW: + env->stats->mda.sessions_active--; + return; + + case IMSG_BATCH_DONE: + env->stats->mta.sessions_active--; + return; + + case IMSG_QUEUE_SCHEDULE: + sched = imsg->data; + sched->ret = 0; + if (valid_message_uid(sched->mid)) + sched->ret = runner_force_envelope_schedule(sched->mid); + else if (valid_message_id(sched->mid)) + sched->ret = runner_force_message_schedule(sched->mid); + imsg_compose_event(iev, IMSG_QUEUE_SCHEDULE, imsg->hdr.peerid, + 0, -1, sched, sizeof *sched); + return; + + case IMSG_QUEUE_REMOVE: + rem = imsg->data; + rem->ret = 0; + if (valid_message_uid(rem->mid)) + rem->ret = runner_force_envelope_remove(rem->mid); + else if (valid_message_id(rem->mid)) + rem->ret = runner_force_message_remove(rem->mid); + imsg_compose_event(iev, IMSG_QUEUE_REMOVE, imsg->hdr.peerid, 0, + -1, rem, sizeof *rem); + return; + + case IMSG_PARENT_ENQUEUE_OFFLINE: + runner_process_offline(env); + return; + + case IMSG_SMTP_ENQUEUE: + m = imsg->data; + if (imsg->fd < 0 || !bounce_session(env, imsg->fd, m)) { + m->status = S_MESSAGE_TEMPFAILURE; + queue_message_update(m); + } + return; + + case IMSG_QUEUE_PAUSE_LOCAL: + env->sc_opts |= SMTPD_MDA_PAUSED; + return; + + case IMSG_QUEUE_RESUME_LOCAL: + env->sc_opts &= ~SMTPD_MDA_PAUSED; + return; + + case IMSG_QUEUE_PAUSE_OUTGOING: + env->sc_opts |= SMTPD_MTA_PAUSED; + return; + + case IMSG_QUEUE_RESUME_OUTGOING: + env->sc_opts &= ~SMTPD_MTA_PAUSED; + return; + + case IMSG_CTL_VERBOSE: + log_verbose(*(int *)imsg->data); + return; + } + + fatalx("runner_imsg: unexpected imsg"); +} + +void +runner_sig_handler(int sig, short event, void *p) +{ + switch (sig) { + case SIGINT: + case SIGTERM: + runner_shutdown(); + break; + default: + fatalx("runner_sig_handler: unexpected signal"); + } +} + +void +runner_shutdown(void) +{ + log_info("runner handler exiting"); + _exit(0); +} + +void +runner_setup_events(struct smtpd *env) +{ + struct timeval tv; + + evtimer_set(&env->sc_ev, runner_timeout, env); + tv.tv_sec = 0; + tv.tv_usec = 10; + evtimer_add(&env->sc_ev, &tv); +} + +void +runner_disable_events(struct smtpd *env) +{ + evtimer_del(&env->sc_ev); +} + +pid_t +runner(struct smtpd *env) +{ + pid_t pid; + struct passwd *pw; + + struct event ev_sigint; + struct event ev_sigterm; + + struct peer peers[] = { + { PROC_QUEUE, imsg_dispatch } + }; + + switch (pid = fork()) { + case -1: + fatal("runner: cannot fork"); + case 0: + break; + default: + return (pid); + } + + purge_config(env, PURGE_EVERYTHING); + + pw = env->sc_pw; + + if (chroot(PATH_SPOOL) == -1) + fatal("runner: chroot"); + if (chdir("/") == -1) + fatal("runner: chdir(\"/\")"); + + smtpd_process = PROC_RUNNER; + setproctitle("%s", env->sc_title[smtpd_process]); + + 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("runner: cannot drop privileges"); + + SPLAY_INIT(&env->batch_queue); + + imsg_callback = runner_imsg; + event_init(); + + signal_set(&ev_sigint, SIGINT, runner_sig_handler, env); + signal_set(&ev_sigterm, SIGTERM, runner_sig_handler, env); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + /* see fdlimit()-related comment in queue.c */ + fdlimit(1.0); + if ((env->sc_maxconn = availdesc() / 4) < 1) + fatalx("runner: fd starvation"); + + config_pipes(env, peers, nitems(peers)); + config_peers(env, peers, nitems(peers)); + + unlink(PATH_QUEUE "/envelope.tmp"); + runner_reset_flags(); + runner_process_offline(env); + + runner_setup_events(env); + event_dispatch(); + runner_shutdown(); + + return (0); +} + +void +runner_process_offline(struct smtpd *env) +{ + char path[MAXPATHLEN]; + struct qwalk *q; + + q = qwalk_new(PATH_OFFLINE); + + if (qwalk(q, path)) + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_PARENT_ENQUEUE_OFFLINE, PROC_PARENT, 0, -1, path, + strlen(path) + 1); + + qwalk_close(q); +} + +void +runner_reset_flags(void) +{ + char path[MAXPATHLEN]; + struct message message; + struct qwalk *q; + + q = qwalk_new(PATH_QUEUE); + + while (qwalk(q, path)) { + while (! queue_load_envelope(&message, basename(path))) + sleep(1); + message_reset_flags(&message); + } + + qwalk_close(q); +} + +void +runner_timeout(int fd, short event, void *p) +{ + struct smtpd *env = p; + struct timeval tv; + + runner_purge_run(); + + runner_process_queue(env); + runner_process_runqueue(env); + runner_process_batchqueue(env); + + tv.tv_sec = 1; + tv.tv_usec = 0; + evtimer_add(&env->sc_ev, &tv); +} + +void +runner_process_queue(struct smtpd *env) +{ + char path[MAXPATHLEN]; + char rqpath[MAXPATHLEN]; + struct message message; + time_t now; + size_t mta_av, mda_av, bnc_av; + struct qwalk *q; + + mta_av = env->sc_maxconn - env->stats->mta.sessions_active; + mda_av = env->sc_maxconn - env->stats->mda.sessions_active; + bnc_av = env->sc_maxconn - env->stats->runner.bounces_active; + + now = time(NULL); + q = qwalk_new(PATH_QUEUE); + + while (qwalk(q, path)) { + if (! queue_load_envelope(&message, basename(path))) + continue; + + if (message.type & T_MDA_MESSAGE) { + if (env->sc_opts & SMTPD_MDA_PAUSED) + continue; + if (mda_av == 0) + continue; + } + + if (message.type & T_MTA_MESSAGE) { + if (env->sc_opts & SMTPD_MTA_PAUSED) + continue; + if (mta_av == 0) + continue; + } + + if (message.type & T_BOUNCE_MESSAGE) { + if (env->sc_opts & (SMTPD_MDA_PAUSED|SMTPD_MTA_PAUSED)) + continue; + if (bnc_av == 0) + continue; + } + + if (! runner_message_schedule(&message, now)) + continue; + + if (runner_check_loop(&message)) { + message_set_errormsg(&message, "loop has been detected"); + bounce_record_message(&message); + queue_remove_envelope(&message); + continue; + } + + message.flags |= F_MESSAGE_SCHEDULED; + message.flags &= ~F_MESSAGE_FORCESCHEDULE; + queue_update_envelope(&message); + + if (! bsnprintf(rqpath, sizeof(rqpath), "%s/%s", PATH_RUNQUEUE, + basename(path))) + fatalx("runner_process_queue: snprintf"); + + if (symlink(path, rqpath) == -1) { + if (errno == EEXIST) + continue; + if (errno == ENOSPC) + break; + fatal("runner_process_queue: symlink"); + } + + if (message.type & T_MDA_MESSAGE) + mda_av--; + if (message.type & T_MTA_MESSAGE) + mta_av--; + if (message.type & T_BOUNCE_MESSAGE) + bnc_av--; + } + + qwalk_close(q); +} + +void +runner_process_runqueue(struct smtpd *env) +{ + char path[MAXPATHLEN]; + struct message message; + time_t tm; + struct batch *batchp; + struct message *messagep; + struct qwalk *q; + + tm = time(NULL); + + q = qwalk_new(PATH_RUNQUEUE); + + while (qwalk(q, path)) { + unlink(path); + + if (! queue_load_envelope(&message, basename(path))) + continue; + + if (message.flags & F_MESSAGE_PROCESSING) + continue; + + message.lasttry = tm; + message.flags &= ~F_MESSAGE_SCHEDULED; + message.flags |= F_MESSAGE_PROCESSING; + + if (! queue_update_envelope(&message)) + continue; + + messagep = calloc(1, sizeof (struct message)); + if (messagep == NULL) + fatal("runner_process_runqueue: calloc"); + *messagep = message; + + messagep->batch_id = 0; + batchp = batch_lookup(env, messagep); + if (batchp != NULL) + messagep->batch_id = batchp->id; + + batchp = batch_record(env, messagep); + if (messagep->batch_id == 0) + messagep->batch_id = batchp->id; + } + + qwalk_close(q); +} + +void +runner_process_batchqueue(struct smtpd *env) +{ + 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))) { + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_SMTP_ENQUEUE, PROC_SMTP, 0, -1, m, + sizeof *m); + TAILQ_REMOVE(&batchp->messages, m, entry); + free(m); + } + env->stats->runner.bounces_active++; + env->stats->runner.bounces++; + break; + + case T_MDA_BATCH: + m = TAILQ_FIRST(&batchp->messages); + fd = queue_open_message_file(m->message_id); + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_MDA_SESS_NEW, PROC_MDA, 0, fd, m, + sizeof *m); + TAILQ_REMOVE(&batchp->messages, m, entry); + free(m); + env->stats->mda.sessions_active++; + env->stats->mda.sessions++; + break; + + case T_MTA_BATCH: + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_BATCH_CREATE, PROC_MTA, 0, -1, batchp, + sizeof *batchp); + while ((m = TAILQ_FIRST(&batchp->messages))) { + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_BATCH_APPEND, PROC_MTA, 0, -1, m, + sizeof *m); + TAILQ_REMOVE(&batchp->messages, m, entry); + free(m); + } + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_BATCH_CLOSE, PROC_MTA, 0, -1, batchp, + sizeof *batchp); + env->stats->mta.sessions_active++; + env->stats->mta.sessions++; + break; + + default: + fatalx("runner_process_batchqueue: unknown type"); + } + + SPLAY_REMOVE(batchtree, &env->batch_queue, batchp); + free(batchp); + } +} + +int +runner_message_schedule(struct message *messagep, time_t tm) +{ + time_t delay; + + if (messagep->flags & (F_MESSAGE_SCHEDULED|F_MESSAGE_PROCESSING)) + return 0; + + if (messagep->flags & F_MESSAGE_FORCESCHEDULE) + return 1; + + /* Batch has been in the queue for too long and expired */ + if (tm - messagep->creation >= SMTPD_QUEUE_EXPIRY) { + message_set_errormsg(messagep, "message expired after sitting in queue for %d days", + SMTPD_QUEUE_EXPIRY / 60 / 60 / 24); + bounce_record_message(messagep); + queue_remove_envelope(messagep); + return 0; + } + + if (messagep->lasttry == 0) + return 1; + + delay = SMTPD_QUEUE_MAXINTERVAL; + + // recompute path + + if (messagep->type == T_MDA_MESSAGE || + messagep->type == T_BOUNCE_MESSAGE) { + if (messagep->retry < 5) + return 1; + + if (messagep->retry < 15) + delay = (messagep->retry * 60) + arc4random_uniform(60); + } + + if (messagep->type == T_MTA_MESSAGE) { + if (messagep->retry < 3) + delay = SMTPD_QUEUE_INTERVAL; + else if (messagep->retry <= 7) { + delay = SMTPD_QUEUE_INTERVAL * (1 << (messagep->retry - 3)); + if (delay > SMTPD_QUEUE_MAXINTERVAL) + delay = SMTPD_QUEUE_MAXINTERVAL; + } + } + + if (tm >= messagep->lasttry + delay) + return 1; + + return 0; +} + +int +runner_force_envelope_schedule(char *mid) +{ + struct message message; + + if (! queue_load_envelope(&message, mid)) + return 0; + + if (message.flags & (F_MESSAGE_PROCESSING|F_MESSAGE_SCHEDULED)) + return 1; + + message.flags |= F_MESSAGE_FORCESCHEDULE; + + if (! queue_update_envelope(&message)) + return 0; + + return 1; +} + +int +runner_force_message_schedule(char *mid) +{ + char path[MAXPATHLEN]; + DIR *dirp; + struct dirent *dp; + + if (! bsnprintf(path, MAXPATHLEN, "%s/%d/%s/envelopes", + PATH_QUEUE, queue_hash(mid), mid)) + return 0; + + dirp = opendir(path); + if (dirp == NULL) + return 0; + + while ((dp = readdir(dirp)) != NULL) { + if (valid_message_uid(dp->d_name)) + runner_force_envelope_schedule(dp->d_name); + } + closedir(dirp); + + return 1; +} + + +int +runner_force_envelope_remove(char *mid) +{ + struct message message; + + if (! queue_load_envelope(&message, mid)) + return 0; + + if (message.flags & (F_MESSAGE_PROCESSING|F_MESSAGE_SCHEDULED)) + return 0; + + if (! queue_remove_envelope(&message)) + return 0; + + return 1; +} + +int +runner_force_message_remove(char *mid) +{ + char path[MAXPATHLEN]; + DIR *dirp; + struct dirent *dp; + + if (! bsnprintf(path, MAXPATHLEN, "%s/%d/%s/envelopes", + PATH_QUEUE, queue_hash(mid), mid)) + return 0; + + dirp = opendir(path); + if (dirp == NULL) + return 0; + + while ((dp = readdir(dirp)) != NULL) { + if (valid_message_uid(dp->d_name)) + runner_force_envelope_remove(dp->d_name); + } + closedir(dirp); + + return 1; +} + +void +runner_purge_run(void) +{ + char path[MAXPATHLEN]; + struct qwalk *q; + + q = qwalk_new(PATH_PURGE); + + while (qwalk(q, path)) + runner_purge_message(basename(path)); + + qwalk_close(q); +} + +void +runner_purge_message(char *msgid) +{ + char rootdir[MAXPATHLEN]; + char evpdir[MAXPATHLEN]; + char evppath[MAXPATHLEN]; + char msgpath[MAXPATHLEN]; + DIR *dirp; + struct dirent *dp; + + if (! bsnprintf(rootdir, sizeof(rootdir), "%s/%s", PATH_PURGE, msgid)) + fatal("runner_purge_message: snprintf"); + + if (! bsnprintf(evpdir, sizeof(evpdir), "%s%s", rootdir, + PATH_ENVELOPES)) + fatal("runner_purge_message: snprintf"); + + if (! bsnprintf(msgpath, sizeof(msgpath), "%s/message", rootdir)) + fatal("runner_purge_message: snprintf"); + + if (unlink(msgpath) == -1) + if (errno != ENOENT) + fatal("runner_purge_message: unlink"); + + dirp = opendir(evpdir); + if (dirp == NULL) { + if (errno == ENOENT) + goto delroot; + fatal("runner_purge_message: opendir"); + } + while ((dp = readdir(dirp)) != NULL) { + if (strcmp(dp->d_name, ".") == 0 || + strcmp(dp->d_name, "..") == 0) + continue; + if (! bsnprintf(evppath, sizeof(evppath), "%s/%s", evpdir, + dp->d_name)) + fatal("runner_purge_message: snprintf"); + + if (unlink(evppath) == -1) + if (errno != ENOENT) + fatal("runner_purge_message: unlink"); + } + closedir(dirp); + + if (rmdir(evpdir) == -1) + if (errno != ENOENT) + fatal("runner_purge_message: rmdir"); + +delroot: + if (rmdir(rootdir) == -1) + if (errno != ENOENT) + fatal("runner_purge_message: rmdir"); +} + +struct batch * +batch_record(struct smtpd *env, struct message *messagep) +{ + struct batch *batchp; + struct path *path; + + batchp = NULL; + if (messagep->batch_id != 0) { + batchp = batch_by_id(env, messagep->batch_id); + if (batchp == NULL) + fatalx("batch_record: internal inconsistency."); + } + if (batchp == NULL) { + batchp = calloc(1, sizeof(struct batch)); + if (batchp == NULL) + fatal("batch_record: calloc"); + + batchp->id = generate_uid(); + + (void)strlcpy(batchp->message_id, messagep->message_id, + sizeof(batchp->message_id)); + TAILQ_INIT(&batchp->messages); + SPLAY_INSERT(batchtree, &env->batch_queue, batchp); + + if (messagep->type & T_BOUNCE_MESSAGE) { + batchp->type = T_BOUNCE_BATCH; + path = &messagep->sender; + } + else { + path = &messagep->recipient; + } + batchp->rule = path->rule; + + (void)strlcpy(batchp->hostname, path->domain, + sizeof(batchp->hostname)); + + if (batchp->type != T_BOUNCE_BATCH) { + if (IS_MAILBOX(*path) || IS_EXT(*path)) { + batchp->type = T_MDA_BATCH; + } + else { + batchp->type = T_MTA_BATCH; + } + } + } + + TAILQ_INSERT_TAIL(&batchp->messages, messagep, entry); + env->stats->runner.active++; + return batchp; +} + +struct batch * +batch_lookup(struct smtpd *env, struct message *message) +{ + struct batch *batchp; + struct batch lookup; + + /* We only support delivery of one message at a time, in MDA + * and bounces messages. + */ + if (message->type == T_BOUNCE_MESSAGE || message->type == T_MDA_MESSAGE) + return NULL; + + /* If message->batch_id != 0, we can retrieve batch by id */ + if (message->batch_id != 0) { + lookup.id = message->batch_id; + return SPLAY_FIND(batchtree, &env->batch_queue, &lookup); + } + + /* We do not know the batch_id yet, maybe it was created but we could not + * be notified, or it just does not exist. Let's scan to see if we can do + * a match based on our message_id and flags. + */ + SPLAY_FOREACH(batchp, batchtree, &env->batch_queue) { + + if (batchp->type != message->type) + continue; + + if (strcasecmp(batchp->message_id, message->message_id) != 0) + continue; + + if (batchp->type & T_MTA_BATCH) + if (strcasecmp(batchp->hostname, message->recipient.domain) != 0) + continue; + + break; + } + + return batchp; +} + +int +batch_cmp(struct batch *s1, struct batch *s2) +{ + /* + * do not return u_int64_t's + */ + if (s1->id < s2->id) + return (-1); + + if (s1->id > s2->id) + return (1); + + return (0); +} + +int +runner_check_loop(struct message *messagep) +{ + int fd; + FILE *fp; + char *buf, *lbuf; + size_t len; + struct path chkpath; + int ret = 0; + int rcvcount = 0; + + fd = queue_open_message_file(messagep->message_id); + if ((fp = fdopen(fd, "r")) == NULL) + fatal("fdopen"); + + lbuf = NULL; + while ((buf = fgetln(fp, &len))) { + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + else { + /* EOF without EOL, copy and add the NUL */ + if ((lbuf = malloc(len + 1)) == NULL) + err(1, NULL); + memcpy(lbuf, buf, len); + lbuf[len] = '\0'; + buf = lbuf; + } + + if (strchr(buf, ':') == NULL && !isspace((int)*buf)) + break; + + if (strncasecmp("Received: ", buf, 10) == 0) { + rcvcount++; + if (rcvcount == MAX_HOPS_COUNT) { + ret = 1; + break; + } + } + + else if (strncasecmp("Delivered-To: ", buf, 14) == 0) { + struct path rcpt; + + bzero(&chkpath, sizeof (struct path)); + if (! recipient_to_path(&chkpath, buf + 14)) + continue; + + rcpt = messagep->recipient; + if (messagep->type == T_BOUNCE_MESSAGE) + rcpt = messagep->sender; + + if (strcasecmp(chkpath.user, rcpt.user) == 0 && + strcasecmp(chkpath.domain, rcpt.domain) == 0) { + ret = 1; + break; + } + } + } + free(lbuf); + + fclose(fp); + return ret; +} + +void +message_reset_flags(struct message *m) +{ + m->flags &= ~F_MESSAGE_SCHEDULED; + m->flags &= ~F_MESSAGE_PROCESSING; + + while (! queue_update_envelope(m)) + sleep(1); +} + +SPLAY_GENERATE(batchtree, batch, b_nodes, batch_cmp); diff --git a/usr.sbin/smtpd/smtp.c b/usr.sbin/smtpd/smtp.c index 4269ccdc6e9..d000f1303f2 100644 --- a/usr.sbin/smtpd/smtp.c +++ b/usr.sbin/smtpd/smtp.c @@ -1,9 +1,9 @@ -/* $OpenBSD: smtp.c,v 1.72 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: smtp.c,v 1.73 2010/06/01 19:47:09 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> - * Copyright (c) 2009-2010 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -39,7 +39,6 @@ #include <unistd.h> #include "smtpd.h" -#include "queue_backend.h" void smtp_imsg(struct smtpd *, struct imsgev *, struct imsg *); __dead void smtp_shutdown(void); @@ -47,22 +46,21 @@ void smtp_sig_handler(int, short, void *); void smtp_setup_events(struct smtpd *); void smtp_disable_events(struct smtpd *); void smtp_pause(struct smtpd *); -int smtp_enqueue(struct smtpd *, uid_t); +int smtp_enqueue(struct smtpd *, uid_t *); void smtp_accept(int, short, void *); struct session *smtp_new(struct listener *); -struct session *session_lookup(struct smtpd *, u_int32_t); - -u_int32_t smtp_id; +struct session *session_lookup(struct smtpd *, u_int64_t); void smtp_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { + struct session skey; + struct submit_status *ss; struct listener *l; struct session *s; struct auth *auth; struct ssl *ssl; struct dns *dns; - int status, fd; if (iev->proc == PROC_LKA) { switch (imsg->hdr.type) { @@ -83,56 +81,82 @@ smtp_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_MFA) { switch (imsg->hdr.type) { - case IMSG_MFA_MAIL: case IMSG_MFA_RCPT: - s = session_lookup(env, imsg->hdr.peerid); + case IMSG_MFA_MAIL: + log_debug("smtp: got imsg_mfa_mail/rcpt"); + ss = imsg->data; + s = session_lookup(env, ss->id); if (s == NULL) return; - memcpy(&status, imsg->data, sizeof status); - s->s_msg.status |= status; - session_pickup(s); + session_pickup(s, ss); return; } } if (iev->proc == PROC_QUEUE) { + ss = imsg->data; + switch (imsg->hdr.type) { - case IMSG_QUEUE_CREATE: - s = session_lookup(env, imsg->hdr.peerid); + case IMSG_QUEUE_CREATE_MESSAGE: + log_debug("smtp: imsg_queue_create_message returned"); + s = session_lookup(env, ss->id); if (s == NULL) return; - memcpy(&s->content_id, imsg->data, sizeof s->content_id); - memcpy(&s->queue_id, (u_int64_t *)imsg->data + 1, - sizeof s->queue_id); - if (s->content_id == INVALID_ID) - s->s_msg.status |= S_MESSAGE_TEMPFAILURE; - session_pickup(s); + strlcpy(s->s_msg.message_id, ss->u.msgid, + sizeof s->s_msg.message_id); + session_pickup(s, ss); return; - case IMSG_QUEUE_OPEN: - s = session_lookup(env, imsg->hdr.peerid); + case IMSG_QUEUE_MESSAGE_FILE: + log_debug("smtp: imsg_queue_message_file returned"); + s = session_lookup(env, ss->id); if (s == NULL) { close(imsg->fd); return; } - s->datafp = fdopen(imsg->fd, "a"); - if (s->datafp == NULL) - fatal("smtp: fdopen"); - session_pickup(s); + s->datafp = fdopen(imsg->fd, "w"); + if (s->datafp == NULL) { + /* queue may have experienced tempfail. */ + if (ss->code != 421) + fatalx("smtp: fdopen"); + close(imsg->fd); + } + session_pickup(s, ss); + return; + + case IMSG_QUEUE_TEMPFAIL: + log_debug("smtp: got imsg_queue_tempfail"); + skey.s_id = ss->id; + s = SPLAY_FIND(sessiontree, &env->sc_sessions, &skey); + if (s == NULL) + fatalx("smtp: session is gone"); + if (s->s_flags & F_WRITEONLY) + /* session is write-only, must not destroy it. */ + s->s_msg.status |= S_MESSAGE_TEMPFAILURE; + else + fatalx("smtp: corrupt session"); return; - case IMSG_QUEUE_CLOSE: - s = session_lookup(env, imsg->hdr.peerid); + case IMSG_QUEUE_COMMIT_ENVELOPES: + log_debug("smtp: got imsg_queue_commit_envelopes"); + s = session_lookup(env, ss->id); if (s == NULL) return; - memcpy(&status, imsg->data, sizeof status); - s->s_msg.status |= status; - session_pickup(s); + session_pickup(s, ss); + return; + + case IMSG_QUEUE_COMMIT_MESSAGE: + log_debug("smtp: got imsg_queue_commit_message"); + s = session_lookup(env, ss->id); + if (s == NULL) + return; + session_pickup(s, ss); return; case IMSG_SMTP_ENQUEUE: - fd = smtp_enqueue(env, getuid()); - imsg_compose_event(iev, IMSG_SMTP_ENQUEUE, imsg->hdr.peerid, 0, fd, NULL, 0); + imsg_compose_event(iev, IMSG_SMTP_ENQUEUE, 0, 0, + smtp_enqueue(env, NULL), imsg->data, + sizeof(struct message)); return; } } @@ -225,7 +249,7 @@ smtp_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) s->s_flags &= ~F_AUTHENTICATED; s->s_msg.flags &= ~F_MESSAGE_AUTHENTICATED; } - session_pickup(s); + session_pickup(s, NULL); return; case IMSG_CTL_VERBOSE: @@ -237,9 +261,9 @@ smtp_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_CONTROL) { switch (imsg->hdr.type) { case IMSG_SMTP_ENQUEUE: - fd = smtp_enqueue(env, *(uid_t *)imsg->data); imsg_compose_event(iev, IMSG_SMTP_ENQUEUE, - imsg->hdr.peerid, 0, fd, NULL, 0); + imsg->hdr.peerid, 0, smtp_enqueue(env, imsg->data), + NULL, 0); return; case IMSG_SMTP_PAUSE: @@ -412,7 +436,7 @@ smtp_resume(struct smtpd *env) } int -smtp_enqueue(struct smtpd *env, uid_t uid) +smtp_enqueue(struct smtpd *env, uid_t *euid) { static struct listener local, *l; static struct sockaddr_storage sa; @@ -452,8 +476,16 @@ smtp_enqueue(struct smtpd *env, uid_t uid) s->s_fd = fd[0]; s->s_ss = sa; + s->s_msg.flags |= F_MESSAGE_ENQUEUED; + + if (euid) + bsnprintf(s->s_hostname, sizeof(s->s_hostname), "%d@localhost", + *euid); + else { + strlcpy(s->s_hostname, "localhost", sizeof(s->s_hostname)); + s->s_msg.flags |= F_MESSAGE_BOUNCE; + } - bsnprintf(s->s_hostname, sizeof(s->s_hostname), "%d@localhost", uid); strlcpy(s->s_msg.session_hostname, s->s_hostname, sizeof(s->s_msg.session_hostname)); @@ -483,6 +515,7 @@ smtp_accept(int fd, short event, void *p) dns_query_ptr(l->env, &s->s_ss, s->s_id); } + struct session * smtp_new(struct listener *l) { @@ -502,7 +535,7 @@ smtp_new(struct listener *l) if ((s = calloc(1, sizeof(*s))) == NULL) fatal(NULL); - s->s_id = smtp_id++; + s->s_id = generate_uid(); s->s_env = env; s->s_l = l; strlcpy(s->s_msg.tag, l->tag, sizeof(s->s_msg.tag)); @@ -518,7 +551,7 @@ smtp_new(struct listener *l) * Helper function for handling IMSG replies. */ struct session * -session_lookup(struct smtpd *env, u_int32_t id) +session_lookup(struct smtpd *env, u_int64_t id) { struct session key; struct session *s; diff --git a/usr.sbin/smtpd/smtp_session.c b/usr.sbin/smtpd/smtp_session.c index ea2b386e319..eaaaf964b30 100644 --- a/usr.sbin/smtpd/smtp_session.c +++ b/usr.sbin/smtpd/smtp_session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtp_session.c,v 1.133 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: smtp_session.c,v 1.134 2010/06/01 19:47:09 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -41,7 +41,6 @@ #include <unistd.h> #include "smtpd.h" -#include "queue_backend.h" int session_rfc5321_helo_handler(struct session *, char *); int session_rfc5321_ehlo_handler(struct session *, char *); @@ -425,6 +424,8 @@ session_rfc5321_mail_handler(struct session *s, char *args) s->s_msg.session_id = s->s_id; s->s_msg.session_ss = s->s_ss; + log_debug("session_rfc5321_mail_handler: sending notification to mfa"); + session_imsg(s, PROC_MFA, IMSG_MFA_MAIL, 0, 0, -1, &s->s_msg, sizeof(s->s_msg)); return 1; @@ -449,8 +450,6 @@ session_rfc5321_rcpt_handler(struct session *s, char *args) return 1; } - s->s_msg.queue_id = s->queue_id; - s->s_state = S_RCPT_MFA; session_imsg(s, PROC_MFA, IMSG_MFA_RCPT, 0, 0, -1, &s->s_msg, @@ -488,8 +487,8 @@ session_rfc5321_data_handler(struct session *s, char *args) s->s_state = S_DATA_QUEUE; - session_imsg(s, PROC_QUEUE, IMSG_QUEUE_OPEN, s->s_id, 0, -1, - &s->queue_id, sizeof s->queue_id); + session_imsg(s, PROC_QUEUE, IMSG_QUEUE_MESSAGE_FILE, 0, 0, -1, + &s->s_msg, sizeof(s->s_msg)); return 1; } @@ -600,12 +599,13 @@ rfc5321: } void -session_pickup(struct session *s) +session_pickup(struct session *s, struct submit_status *ss) { if (s == NULL) fatal("session_pickup: desynchronized"); - if (s->s_msg.status & S_MESSAGE_TEMPFAILURE) { + if ((ss != NULL && ss->code == 421) || + (s->s_msg.status & S_MESSAGE_TEMPFAILURE)) { session_respond(s, "421 Service temporarily unavailable"); s->s_env->stats->smtp.tempfail++; s->s_flags |= F_QUIT; @@ -635,36 +635,48 @@ session_pickup(struct session *s) break; case S_MAIL_MFA: - if (s->s_msg.status & S_MESSAGE_PERMFAILURE) { + if (ss == NULL) + fatalx("bad ss at S_MAIL_MFA"); + if (ss->code != 250) { s->s_state = S_HELO; - session_respond(s, "530 Sender rejected"); + session_respond(s, "%d Sender rejected", ss->code); return; } + s->s_state = S_MAIL_QUEUE; - session_imsg(s, PROC_QUEUE, IMSG_QUEUE_CREATE, s->s_id, 0, -1, - NULL, 0); + s->s_msg.sender = ss->u.path; + + session_imsg(s, PROC_QUEUE, IMSG_QUEUE_CREATE_MESSAGE, 0, 0, -1, + &s->s_msg, sizeof(s->s_msg)); break; case S_MAIL_QUEUE: + if (ss == NULL) + fatalx("bad ss at S_MAIL_QUEUE"); s->s_state = S_MAIL; - session_respond(s, "250 2.1.0 Sender ok"); + session_respond(s, "%d 2.1.0 Sender ok", ss->code); break; case S_RCPT_MFA: + if (ss == NULL) + fatalx("bad ss at S_RCPT_MFA"); /* recipient was not accepted */ - if (s->s_msg.status & S_MESSAGE_PERMFAILURE) { + if (ss->code != 250) { /* We do not have a valid recipient, downgrade state */ if (s->rcptcount == 0) s->s_state = S_MAIL; else s->s_state = S_RCPT; - session_respond(s, "530 5.0.0 Recipient rejected: %s@%s", + session_respond(s, "%d 5.0.0 Recipient rejected: %s@%s", ss->code, s->s_msg.session_rcpt.user, s->s_msg.session_rcpt.domain); return; } - s->rcptcount++; + s->s_state = S_RCPT; - session_respond(s, "250 2.0.0 Recipient ok"); + s->rcptcount++; + s->s_msg.recipient = ss->u.path; + + session_respond(s, "%d 2.0.0 Recipient ok", ss->code); break; case S_DATA_QUEUE: @@ -676,7 +688,7 @@ session_pickup(struct session *s) s->s_msg.session_helo, s->s_hostname, ss_to_text(&s->s_ss)); fprintf(s->datafp, "\tby %s (OpenSMTPD) with %sSMTP id %s", s->s_env->sc_hostname, s->s_flags & F_EHLO ? "E" : "", - queue_be_decode(s->content_id)); + s->s_msg.message_id); if (s->s_flags & F_SECURE) { fprintf(s->datafp, "\n\t(version=%s cipher=%s bits=%d)", @@ -695,24 +707,23 @@ session_pickup(struct session *s) break; case S_DONE: - if (s->s_msg.status & S_MESSAGE_PERMFAILURE) - session_respond(s, "554 5.4.6 Routing loop detected"); - else { - session_respond(s, "250 2.0.0 %s Message accepted for delivery", - queue_be_decode(s->content_id)); - log_info("%s: from=%s%s%s, size=%ld, nrcpts=%zd, " - "relay=%s [%s]", - queue_be_decode(s->content_id), - s->s_msg.sender.user, - s->s_msg.sender.user[0] == '\0' ? "" : "@", - s->s_msg.sender.domain, - s->s_datalen, - s->rcptcount, - s->s_hostname, - ss_to_text(&s->s_ss)); - } + session_respond(s, "250 2.0.0 %s Message accepted for delivery", + s->s_msg.message_id); + log_info("%s: from=<%s%s%s>, size=%ld, nrcpts=%zd, proto=%s, " + "relay=%s [%s]", + s->s_msg.message_id, + s->s_msg.sender.user, + s->s_msg.sender.user[0] == '\0' ? "" : "@", + s->s_msg.sender.domain, + s->s_datalen, + s->rcptcount, + s->s_flags & F_EHLO ? "ESMTP" : "SMTP", + s->s_hostname, + ss_to_text(&s->s_ss)); + s->s_state = S_HELO; - s->content_id = 0; + s->s_msg.message_id[0] = '\0'; + s->s_msg.message_uid[0] = '\0'; bzero(&s->s_nresp, sizeof(s->s_nresp)); break; @@ -732,7 +743,7 @@ session_init(struct listener *l, struct session *s) } session_bufferevent_new(s); - session_pickup(s); + session_pickup(s, NULL); } void @@ -816,7 +827,7 @@ session_read_data(struct session *s, char *line) if (strcmp(line, ".") == 0) { s->s_datalen = ftell(s->datafp); - if (fclose(s->datafp) == EOF) + if (! safe_fclose(s->datafp)) s->s_msg.status |= S_MESSAGE_TEMPFAILURE; s->datafp = NULL; @@ -828,8 +839,8 @@ session_read_data(struct session *s, char *line) s->s_flags |= F_QUIT; s->s_env->stats->smtp.tempfail++; } else { - session_imsg(s, PROC_QUEUE, IMSG_QUEUE_CLOSE, - s->s_id, 0, -1, &s->queue_id, sizeof s->queue_id); + session_imsg(s, PROC_QUEUE, IMSG_QUEUE_COMMIT_MESSAGE, + 0, 0, -1, &s->s_msg, sizeof(s->s_msg)); s->s_state = S_DONE; } @@ -967,12 +978,10 @@ session_destroy(struct session *s) if (s->datafp != NULL) fclose(s->datafp); - if (s->content_id && s->s_state != S_DONE) { - log_debug("%s: deleting queue session", __func__); + if (s->s_msg.message_id[0] != '\0' && s->s_state != S_DONE) imsg_compose_event(s->s_env->sc_ievs[PROC_QUEUE], - IMSG_QUEUE_DELETE, 0, 0, -1, &s->queue_id, - sizeof s->queue_id); - } + IMSG_QUEUE_REMOVE_MESSAGE, 0, 0, -1, &s->s_msg, + sizeof(s->s_msg)); ssl_session_destroy(s); @@ -992,6 +1001,7 @@ session_destroy(struct session *s) } SPLAY_REMOVE(sessiontree, &s->s_env->sc_sessions, s); + bzero(s, sizeof(*s)); free(s); } @@ -1089,10 +1099,9 @@ session_respond(struct session *s, char *fmt, ...) switch (EVBUFFER_DATA(EVBUFFER_OUTPUT(s->s_bev))[n]) { case '5': case '4': - log_info("(none): from=<%s%s%s>, relay=%s [%s], stat=LocalError (%.*s)", - s->s_msg.sender.user, - s->s_msg.sender.user[0] == '\0' ? "" : "@", - s->s_msg.sender.domain, + log_info("%s: from=<%s@%s>, relay=%s [%s], stat=LocalError (%.*s)", + s->s_msg.message_id[0] ? s->s_msg.message_id : "(none)", + s->s_msg.sender.user, s->s_msg.sender.domain, s->s_hostname, ss_to_text(&s->s_ss), (int)EVBUFFER_LENGTH(EVBUFFER_OUTPUT(s->s_bev)) - n - 2, EVBUFFER_DATA(EVBUFFER_OUTPUT(s->s_bev))); diff --git a/usr.sbin/smtpd/smtpctl.8 b/usr.sbin/smtpd/smtpctl.8 index 3243b7d83b5..a39d03418a3 100644 --- a/usr.sbin/smtpd/smtpctl.8 +++ b/usr.sbin/smtpd/smtpctl.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: smtpctl.8,v 1.14 2010/05/31 23:38:56 jacekm Exp $ +.\" $OpenBSD: smtpctl.8,v 1.15 2010/06/01 19:47:09 jacekm Exp $ .\" .\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: May 31 2010 $ +.Dd $Mdocdate: June 1 2010 $ .Dt SMTPCTL 8 .Os .Sh NAME @@ -43,19 +43,20 @@ Temporarily stop deliveries to local users. .It Cm pause outgoing Temporarily stop relaying and deliveries to remote users. -.It Cm remove Ar message-id | Cm all -Removes messages with the same message ID, or all messages. +.It Cm remove Ar message-uid | message-id +Removes a single message, or all messages with the same message ID. .It Cm resume incoming Resume accepting incoming sessions. .It Cm resume local Resume deliveries to local users. .It Cm resume outgoing Resume relaying and deliveries to remote users. -.It Cm schedule Ar message-id | Cm all -Marks messages with the same message ID, or all messages, +.It Cm schedule Ar message-uid | message-id +Marks a single message, or all messages with the same message ID, as ready for immediate delivery. -.It Cm show queue Op Ic raw -Displays undelivered messages. +.It Cm show queue +Displays information concerning envelopes +that are currently in a queue. .It Cm show runqueue Displays information concerning envelopes that are scheduled for delivery. diff --git a/usr.sbin/smtpd/smtpctl.c b/usr.sbin/smtpd/smtpctl.c index 35f1d519221..04dc4a6e404 100644 --- a/usr.sbin/smtpd/smtpctl.c +++ b/usr.sbin/smtpd/smtpctl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpctl.c,v 1.49 2010/06/01 11:37:26 jacekm Exp $ */ +/* $OpenBSD: smtpctl.c,v 1.50 2010/06/01 19:47:09 jacekm Exp $ */ /* * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -43,11 +43,9 @@ #include "smtpd.h" #include "parser.h" -#include "queue_backend.h" __dead void usage(void); int show_command_output(struct imsg*); -void show_queue(int); int show_stats_output(struct imsg *); int proctype; @@ -75,7 +73,6 @@ main(int argc, char *argv[]) struct sockaddr_un sun; struct parse_result *res = NULL; struct imsg imsg; - u_int64_t content_id; int ctl_sock; int done = 0; int n, verbose = 0; @@ -86,7 +83,7 @@ main(int argc, char *argv[]) else if (strcmp(__progname, "mailq") == 0) { if (geteuid()) errx(1, "need root privileges"); - show_queue(0); + show_queue(PATH_QUEUE, 0); return 0; } else if (strcmp(__progname, "smtpctl") == 0) { /* check for root privileges */ @@ -99,12 +96,10 @@ main(int argc, char *argv[]) /* handle "disconnected" commands */ switch (res->action) { case SHOW_QUEUE: - show_queue(0); - break; - case SHOW_QUEUE_RAW: - show_queue(1); + show_queue(PATH_QUEUE, 0); break; case SHOW_RUNQUEUE: + show_queue(PATH_RUNQUEUE, 0); break; default: goto connected; @@ -151,7 +146,7 @@ connected: imsg_compose(ibuf, IMSG_QUEUE_PAUSE_LOCAL, 0, 0, -1, NULL, 0); break; case PAUSE_MTA: - imsg_compose(ibuf, IMSG_QUEUE_PAUSE_RELAY, 0, 0, -1, NULL, 0); + imsg_compose(ibuf, IMSG_QUEUE_PAUSE_OUTGOING, 0, 0, -1, NULL, 0); break; case PAUSE_SMTP: imsg_compose(ibuf, IMSG_SMTP_PAUSE, 0, 0, -1, NULL, 0); @@ -160,7 +155,7 @@ connected: imsg_compose(ibuf, IMSG_QUEUE_RESUME_LOCAL, 0, 0, -1, NULL, 0); break; case RESUME_MTA: - imsg_compose(ibuf, IMSG_QUEUE_RESUME_RELAY, 0, 0, -1, NULL, 0); + imsg_compose(ibuf, IMSG_QUEUE_RESUME_OUTGOING, 0, 0, -1, NULL, 0); break; case RESUME_SMTP: imsg_compose(ibuf, IMSG_SMTP_RESUME, 0, 0, -1, NULL, 0); @@ -168,26 +163,24 @@ connected: case SHOW_STATS: imsg_compose(ibuf, IMSG_STATS, 0, 0, -1, NULL, 0); break; - case SCHEDULE: - if (strcmp(res->data, "all") == 0) - content_id = 0; - else - content_id = queue_be_encode(res->data); - if (content_id == INVALID_ID) - errx(1, "invalid id: %s", res->data); - imsg_compose(ibuf, IMSG_QUEUE_SCHEDULE, 0, 0, -1, &content_id, - sizeof content_id); + case SCHEDULE: { + struct sched s; + + s.fd = -1; + bzero(s.mid, sizeof (s.mid)); + strlcpy(s.mid, res->data, sizeof (s.mid)); + imsg_compose(ibuf, IMSG_QUEUE_SCHEDULE, 0, 0, -1, &s, sizeof (s)); break; - case REMOVE: - if (strcmp(res->data, "all") == 0) - content_id = 0; - else - content_id = queue_be_encode(res->data); - if (content_id == INVALID_ID) - errx(1, "invalid id: %s", res->data); - imsg_compose(ibuf, IMSG_QUEUE_REMOVE, 0, 0, -1, &content_id, - sizeof content_id); + } + case REMOVE: { + struct remove s; + + s.fd = -1; + bzero(s.mid, sizeof (s.mid)); + strlcpy(s.mid, res->data, sizeof (s.mid)); + imsg_compose(ibuf, IMSG_QUEUE_REMOVE, 0, 0, -1, &s, sizeof (s)); break; + } case MONITOR: /* XXX */ break; @@ -271,76 +264,6 @@ show_command_output(struct imsg *imsg) return (1); } -void -show_queue(int raw) -{ - struct action_be a; - struct aux aux; - int ret, title; - - if (chdir(PATH_SPOOL) < 0) - err(1, "chdir"); - - title = 0; - for (;;) { - ret = queue_be_getnext(&a); - if (ret == -1) - err(1, "getnext error"); - if (ret == -2) - continue; /* unlinked during scan */ - if (a.content_id == 0) - break; /* end of queue */ - if (a.birth == 0) - continue; /* uncommitted */ - auxsplit(&aux, a.aux); - - if (raw) { - printf("%s|", queue_be_decode(a.content_id)); - printf("%d|", a.birth); - printf("%s|%s|", aux.mode, aux.mail_from); - if (aux.mode[0] == 'R') - printf("%s", aux.rcpt); - else - printf("%s", aux.rcpt_to); - printf("\n"); - continue; - } - - if (title == 0) { - printf("Message Envelope\n"); - title = 1; - } - printf("%s ", queue_be_decode(a.content_id)); - if (aux.user_from[0]) - printf("%s (", aux.user_from); - if (aux.mail_from[0]) - printf("%s", aux.mail_from); - else - printf("BOUNCE"); - if (aux.user_from[0]) - printf(")"); - printf(" -> "); - if (aux.mode[0] == 'R') { - printf("%s", aux.rcpt); - if (strcmp(aux.rcpt, aux.rcpt_to) != 0) - printf(" (%s)", aux.rcpt_to); - } else { - if (aux.user_to[0]) - printf("%s (", aux.user_to); - printf("%s", aux.rcpt_to); - if (aux.user_to[0]) - printf(")"); - } - if (a.status[0]) { - if (a.status[0] == '1' || a.status[0] == '6') - printf(" [%s]", a.status + 4); - else - printf(" [\"%s\"]", a.status + 4); - } - printf("\n"); - } -} - int show_stats_output(struct imsg *imsg) { @@ -365,8 +288,12 @@ show_stats_output(struct imsg *imsg) printf("parent.uptime=%d\n", time(NULL) - stats->parent.start); - printf("queue.inserts=%zd\n", stats->queue.inserts); - printf("queue.length=%zd\n", stats->queue.length); + printf("queue.inserts.local=%zd\n", stats->queue.inserts_local); + printf("queue.inserts.remote=%zd\n", stats->queue.inserts_remote); + + printf("runner.active=%zd\n", stats->runner.active); + printf("runner.bounces=%zd\n", stats->runner.bounces); + printf("runner.bounces.active=%zd\n", stats->runner.bounces_active); printf("smtp.errors.delays=%zd\n", stats->smtp.delays); printf("smtp.errors.linetoolong=%zd\n", stats->smtp.linetoolong); diff --git a/usr.sbin/smtpd/smtpctl/Makefile b/usr.sbin/smtpd/smtpctl/Makefile index 51d0af9c31f..893f2ec381c 100644 --- a/usr.sbin/smtpd/smtpctl/Makefile +++ b/usr.sbin/smtpd/smtpctl/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.13 2010/05/31 23:38:56 jacekm Exp $ +# $OpenBSD: Makefile,v 1.14 2010/06/01 19:47:09 jacekm Exp $ .PATH: ${.CURDIR}/.. @@ -17,7 +17,7 @@ CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual CFLAGS+= -Wsign-compare -Wbounded CFLAGS+= -DCLIENT_NO_SSL -SRCS= smtpctl.c parser.c log.c enqueue.c queue_backend.c util.c client.c +SRCS= smtpctl.c parser.c log.c enqueue.c queue_shared.c util.c client.c LDADD+= -lutil -levent DPADD+= ${LIBUTIL} ${LIBEVENT} .include <bsd.prog.mk> diff --git a/usr.sbin/smtpd/smtpd.c b/usr.sbin/smtpd/smtpd.c index 1119dabc9d4..b02298d2f82 100644 --- a/usr.sbin/smtpd/smtpd.c +++ b/usr.sbin/smtpd/smtpd.c @@ -1,9 +1,9 @@ -/* $OpenBSD: smtpd.c,v 1.109 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: smtpd.c,v 1.110 2010/06/01 19:47:09 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> - * Copyright (c) 2009-2010 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -28,7 +28,6 @@ #include <sys/uio.h> #include <sys/mman.h> -#include <dirent.h> #include <err.h> #include <errno.h> #include <event.h> @@ -46,7 +45,6 @@ #include <unistd.h> #include "smtpd.h" -#include "queue_backend.h" void parent_imsg(struct smtpd *, struct imsgev *, struct imsg *); __dead void usage(void); @@ -59,8 +57,10 @@ void parent_sig_handler(int, short, void *); void forkmda(struct smtpd *, struct imsgev *, u_int32_t, struct deliver *); -void parent_enqueue_offline(struct smtpd *); +int parent_enqueue_offline(struct smtpd *, char *); int parent_forward_open(char *); +int setup_spool(uid_t, gid_t); +int path_starts_with(char *, char *); void fork_peers(struct smtpd *); @@ -68,11 +68,11 @@ 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 setup_spool(struct passwd *); - extern char **environ; void (*imsg_callback)(struct smtpd *, struct imsgev *, struct imsg *); +int __b64_pton(char const *, unsigned char *, size_t); + void parent_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { @@ -116,6 +116,17 @@ parent_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) } } + if (iev->proc == PROC_QUEUE) { + switch (imsg->hdr.type) { + case IMSG_PARENT_ENQUEUE_OFFLINE: + if (! parent_enqueue_offline(env, imsg->data)) + imsg_compose_event(iev, + IMSG_PARENT_ENQUEUE_OFFLINE, 0, 0, -1, + NULL, 0); + return; + } + } + if (iev->proc == PROC_MDA) { switch (imsg->hdr.type) { case IMSG_PARENT_FORK_MDA: @@ -169,7 +180,6 @@ parent_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) } } - log_warnx("parent got imsg %d from %d", imsg->hdr.type, iev->proc); fatalx("parent_imsg: unexpected imsg"); } @@ -374,10 +384,12 @@ parent_sig_handler(int sig, short event, void *p) case CHILD_ENQUEUE_OFFLINE: if (fail) log_warnx("couldn't enqueue offline " - "message; child %s", cause); + "message; smtpctl %s", cause); else log_debug("offline message enqueued"); - parent_enqueue_offline(env); + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_PARENT_ENQUEUE_OFFLINE, 0, 0, -1, + NULL, 0); break; default: @@ -471,11 +483,12 @@ main(int argc, char *argv[]) if (geteuid()) errx(1, "need root privileges"); - if ((env.sc_pw = getpwnam(SMTPD_USER)) == NULL) + if ((env.sc_pw = getpwnam(SMTPD_USER)) == NULL) errx(1, "unknown user %s", SMTPD_USER); - setup_spool(env.sc_pw); - + if (!setup_spool(env.sc_pw->pw_uid, 0)) + errx(1, "invalid directory permissions"); + log_init(debug); log_verbose(verbose); @@ -518,8 +531,6 @@ main(int argc, char *argv[]) bzero(&tv, sizeof(tv)); evtimer_add(&env.sc_ev, &tv); - parent_enqueue_offline(&env); - event_dispatch(); return (0); @@ -552,6 +563,7 @@ fork_peers(struct smtpd *env) env->sc_instances[PROC_MTA] = 1; env->sc_instances[PROC_PARENT] = 1; env->sc_instances[PROC_QUEUE] = 1; + env->sc_instances[PROC_RUNNER] = 1; env->sc_instances[PROC_SMTP] = 1; init_pipes(env); @@ -562,6 +574,7 @@ fork_peers(struct smtpd *env) env->sc_title[PROC_MFA] = "mail filter agent"; env->sc_title[PROC_MTA] = "mail transfer agent"; env->sc_title[PROC_QUEUE] = "queue"; + env->sc_title[PROC_RUNNER] = "runner"; env->sc_title[PROC_SMTP] = "smtp server"; child_add(env, control(env), CHILD_DAEMON, PROC_CONTROL); @@ -570,6 +583,7 @@ fork_peers(struct smtpd *env) child_add(env, mfa(env), CHILD_DAEMON, PROC_MFA); child_add(env, mta(env), CHILD_DAEMON, PROC_MTA); child_add(env, queue(env), CHILD_DAEMON, PROC_QUEUE); + child_add(env, runner(env), CHILD_DAEMON, PROC_RUNNER); child_add(env, smtp(env), CHILD_DAEMON, PROC_SMTP); setproctitle("[priv]"); @@ -616,6 +630,136 @@ child_lookup(struct smtpd *env, pid_t pid) return SPLAY_FIND(childtree, &env->children, &key); } +int +setup_spool(uid_t uid, gid_t gid) +{ + unsigned int n; + char *paths[] = { PATH_INCOMING, PATH_ENQUEUE, PATH_QUEUE, + PATH_RUNQUEUE, PATH_PURGE, + PATH_OFFLINE, PATH_BOUNCE }; + char pathname[MAXPATHLEN]; + struct stat sb; + int ret; + + if (! bsnprintf(pathname, sizeof(pathname), "%s", PATH_SPOOL)) + fatal("snprintf"); + + if (stat(pathname, &sb) == -1) { + if (errno != ENOENT) { + warn("stat: %s", pathname); + return 0; + } + + if (mkdir(pathname, 0711) == -1) { + warn("mkdir: %s", pathname); + return 0; + } + + if (chown(pathname, 0, 0) == -1) { + warn("chown: %s", pathname); + return 0; + } + + if (stat(pathname, &sb) == -1) + err(1, "stat: %s", pathname); + } + + /* check if it's a directory */ + if (!S_ISDIR(sb.st_mode)) { + warnx("%s is not a directory", pathname); + return 0; + } + + /* check that it is owned by uid/gid */ + if (sb.st_uid != 0 || sb.st_gid != 0) { + warnx("%s must be owned by root:wheel", pathname); + return 0; + } + + /* check permission */ + if ((sb.st_mode & (S_IRUSR|S_IWUSR|S_IXUSR)) != (S_IRUSR|S_IWUSR|S_IXUSR) || + (sb.st_mode & (S_IRGRP|S_IWGRP|S_IXGRP)) != S_IXGRP || + (sb.st_mode & (S_IROTH|S_IWOTH|S_IXOTH)) != S_IXOTH) { + warnx("%s must be rwx--x--x (0711)", pathname); + return 0; + } + + ret = 1; + for (n = 0; n < nitems(paths); n++) { + mode_t mode; + uid_t owner; + gid_t group; + + if (!strcmp(paths[n], PATH_OFFLINE)) { + mode = 01777; + owner = 0; + group = 0; + } else { + mode = 0700; + owner = uid; + group = gid; + } + + if (! bsnprintf(pathname, sizeof(pathname), "%s%s", PATH_SPOOL, + paths[n])) + fatal("snprintf"); + + if (stat(pathname, &sb) == -1) { + if (errno != ENOENT) { + warn("stat: %s", pathname); + ret = 0; + continue; + } + + /* chmod is deffered to avoid umask effect */ + if (mkdir(pathname, 0) == -1) { + ret = 0; + warn("mkdir: %s", pathname); + } + + if (chown(pathname, owner, group) == -1) { + ret = 0; + warn("chown: %s", pathname); + } + + if (chmod(pathname, mode) == -1) { + ret = 0; + warn("chmod: %s", pathname); + } + + if (stat(pathname, &sb) == -1) + err(1, "stat: %s", pathname); + } + + /* check if it's a directory */ + if (!S_ISDIR(sb.st_mode)) { + ret = 0; + warnx("%s is not a directory", pathname); + } + + /* check that it is owned by owner/group */ + if (sb.st_uid != owner) { + ret = 0; + warnx("%s is not owned by uid %d", pathname, owner); + } + if (sb.st_gid != group) { + ret = 0; + warnx("%s is not owned by gid %d", pathname, group); + } + + /* check permission */ + if ((sb.st_mode & 07777) != mode) { + char mode_str[12]; + + ret = 0; + strmode(mode, mode_str); + mode_str[10] = '\0'; + warnx("%s must be %s (%o)", pathname, mode_str + 1, mode); + } + } + return ret; +} + void imsg_event_add(struct imsgev *iev) { @@ -658,11 +802,8 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, errno = 0; pw = getpwnam(deliver->user); if (pw == NULL) { - if (errno) - n = snprintf(ebuf, sizeof ebuf, "getpwnam: %s", - strerror(errno)); - else - n = snprintf(ebuf, sizeof ebuf, "user not found"); + 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; } @@ -745,8 +886,7 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, /* avoid hangs by setting 5m timeout */ alarm(300); - /* external mda */ - if (deliver->mode == 'P') { + if (deliver->mode == A_EXT) { char *environ_new[2]; environ_new[0] = "PATH=" _PATH_DEFPATH; @@ -757,8 +897,7 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, error("execle"); } - /* internal mda: maildir */ - if (deliver->mode == 'D') { + if (deliver->mode == A_MAILDIR) { char tmp[PATH_MAX], new[PATH_MAX]; int ch, fd; FILE *fp; @@ -788,6 +927,10 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, 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); @@ -797,8 +940,7 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, } #undef error2 - /* internal mda: file */ - if (deliver->mode == 'F') { + if (deliver->mode == A_FILENAME) { struct stat sb; time_t now; size_t len; @@ -833,6 +975,10 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, 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); @@ -843,103 +989,114 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, #undef error #undef error2 -void -parent_enqueue_offline(struct smtpd *env) +int +parent_enqueue_offline(struct smtpd *env, char *runner_path) { - char path[MAXPATHLEN], *line, charstr[2], *envp[2], *tmp; - struct stat sb; - struct dirent *de; + char path[MAXPATHLEN]; struct passwd *pw; - DIR *dir; + struct stat sb; pid_t pid; - arglist args; - int fd, line_sz; - fd = -1; - pw = NULL; + log_debug("parent_enqueue_offline: path %s", runner_path); - dir = opendir(PATH_SPOOL PATH_OFFLINE); - if (dir == NULL) - fatal("opendir"); + if (! bsnprintf(path, sizeof(path), "%s%s", PATH_SPOOL, runner_path)) + fatalx("parent_enqueue_offline: filename too long"); - while ((de = readdir(dir))) { - if (de->d_name[0] == '.') - continue; - snprintf(path, sizeof path, "%s%s/%s", PATH_SPOOL, - PATH_OFFLINE, de->d_name); - log_debug("%s: file %s", __func__, path); - fd = open(path, O_RDONLY); - if (fd < 0) - continue; - if (fchflags(fd, 0) < 0 || fstat(fd, &sb) < 0 || - unlink(path) < 0 || !S_ISREG(sb.st_mode) || - (pw = getpwuid(sb.st_uid)) == NULL) { - close(fd); - continue; + if (! path_starts_with(path, PATH_SPOOL PATH_OFFLINE)) + fatalx("parent_enqueue_offline: path outside offline dir"); + + if (lstat(path, &sb) == -1) { + if (errno == ENOENT) { + log_warn("parent_enqueue_offline: %s", path); + return (0); } - break; + fatal("parent_enqueue_offline: lstat"); } - closedir(dir); - - if (de == NULL) - return; + if (chflags(path, 0) == -1) { + if (errno == ENOENT) { + log_warn("parent_enqueue_offline: %s", path); + return (0); + } + fatal("parent_enqueue_offline: chflags"); + } - pid = fork(); - if (pid < 0) - fatal("fork"); + errno = 0; + if ((pw = getpwuid(sb.st_uid)) == NULL) { + log_warn("parent_enqueue_offline: getpwuid for uid %d failed", + sb.st_uid); + unlink(path); + return (0); + } - if (pid) { - child_add(env, pid, CHILD_ENQUEUE_OFFLINE, -1); - return; + if (! S_ISREG(sb.st_mode)) { + log_warnx("file %s (uid %d) not regular, removing", path, sb.st_uid); + if (S_ISDIR(sb.st_mode)) + rmdir(path); + else + unlink(path); + return (0); } - if (chdir(pw->pw_dir) < 0 && chdir("/") < 0) - _exit(1); - if (dup2(fd, STDIN_FILENO) < 0) - _exit(1); - if (closefrom(STDERR_FILENO + 1) < 0) - _exit(1); - 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)) - _exit(1); - if (setsid() < 0) - _exit(1); - 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) - _exit(1); + if ((pid = fork()) == -1) + fatal("parent_enqueue_offline: fork"); - line = malloc(1); - if (line == NULL) - _exit(1); - line_sz = 1; - line[0] = '\0'; - do { - if (read(STDIN_FILENO, charstr, 1) <= 0) + if (pid == 0) { + char *envp[2], *p, *tmp; + FILE *fp; + size_t len; + arglist args; + + bzero(&args, sizeof(args)); + + 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) || + closefrom(STDERR_FILENO + 1) == -1) { + unlink(path); _exit(1); - charstr[1] = '\0'; - line = realloc(line, ++line_sz); - if (line == NULL) + } + + if ((fp = fopen(path, "r")) == NULL) { + unlink(path); + _exit(1); + } + unlink(path); + + if (chdir(pw->pw_dir) == -1 && chdir("/") == -1) + _exit(1); + + if (setsid() == -1 || + signal(SIGPIPE, SIG_DFL) == SIG_ERR || + dup2(fileno(fp), STDIN_FILENO) == -1) + _exit(1); + + if ((p = fgetln(fp, &len)) == NULL) + _exit(1); + + if (p[len - 1] != '\n') + _exit(1); + p[len - 1] = '\0'; + + addargs(&args, "%s", "sendmail"); + + while ((tmp = strsep(&p, "|")) != NULL) + addargs(&args, "%s", tmp); + + if (lseek(fileno(fp), len, SEEK_SET) == -1) _exit(1); - strlcat(line, charstr, line_sz); - } while (charstr[0] != '\n'); - line[strcspn(line, "\n")] = '\0'; - - bzero(&args, sizeof args); - addargs(&args, "%s", "sendmail"); - while ((tmp = strsep(&line, "|"))) { - log_debug("%s: arg %s", __func__, tmp); - addargs(&args, "%s", tmp); + + envp[0] = "PATH=" _PATH_DEFPATH; + envp[1] = (char *)NULL; + environ = envp; + + execvp(PATH_SMTPCTL, args.list); + _exit(1); } - envp[0] = "PATH=" _PATH_DEFPATH; - envp[1] = (char *)NULL; - environ = envp; - execvp(PATH_SMTPCTL, args.list); - _exit(1); + + child_add(env, pid, CHILD_ENQUEUE_OFFLINE, -1); + + return (1); } int @@ -974,6 +1131,18 @@ parent_forward_open(char *username) } int +path_starts_with(char *file, char *prefix) +{ + char rprefix[MAXPATHLEN]; + char rfile[MAXPATHLEN]; + + if (realpath(file, rfile) == NULL || realpath(prefix, rprefix) == NULL) + return (-1); + + return (strncmp(rfile, rprefix, strlen(rprefix)) == 0); +} + +int child_cmp(struct child *c1, struct child *c2) { if (c1->pid < c2->pid) @@ -1019,25 +1188,4 @@ imsg_dispatch(int fd, short event, void *p) imsg_event_add(iev); } -void -setup_spool(struct passwd *pw) -{ - if (mkdir(PATH_SPOOL, 0711) < 0 && errno != EEXIST) - err(1, "mkdir: %s", PATH_SPOOL); - if (chmod(PATH_SPOOL, 0711) < 0) - err(1, "chmod: %s", PATH_SPOOL); - if (chown(PATH_SPOOL, 0, 0) < 0) - err(1, "chown: %s", PATH_SPOOL); - - if (queue_be_init(PATH_SPOOL, pw->pw_uid, pw->pw_gid) < 0) - err(1, "backend init failed"); - - if (mkdir(PATH_SPOOL PATH_OFFLINE, 01777) < 0 && errno != EEXIST) - err(1, "mkdir: %s", PATH_SPOOL PATH_OFFLINE); - if (chmod(PATH_SPOOL PATH_OFFLINE, 01777) < 0) - err(1, "chmod: %s", PATH_SPOOL PATH_OFFLINE); - if (chown(PATH_SPOOL PATH_OFFLINE, 0, 0) < 0) - err(1, "chmod: %s", PATH_SPOOL PATH_OFFLINE); -} - SPLAY_GENERATE(childtree, child, entry, child_cmp); diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h index 85fc2d5a917..034f117f6f9 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.189 2010/06/01 14:21:52 jacekm Exp $ */ +/* $OpenBSD: smtpd.h,v 1.190 2010/06/01 19:47:09 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -23,11 +23,15 @@ #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif +#define IMSG_SIZE_CHECK(p) do { \ + if (IMSG_DATA_SIZE(&imsg) != sizeof(*p)) \ + fatalx("bad length imsg received"); \ +} while (0) #define IMSG_DATA_SIZE(imsg) ((imsg)->hdr.len - IMSG_HEADER_SIZE) #define CONF_FILE "/etc/mail/smtpd.conf" #define MAX_LISTEN 16 -#define PROC_COUNT 8 +#define PROC_COUNT 9 #define MAX_NAME_SIZE 64 #define MAX_HOPS_COUNT 100 @@ -36,21 +40,40 @@ #define MAX_LINE_SIZE 1024 #define MAX_LOCALPART_SIZE 128 #define MAX_DOMAINPART_SIZE MAXHOSTNAMELEN +#define MAX_ID_SIZE 64 #define MAX_TAG_SIZE 32 /* return and forward path size */ #define MAX_PATH_SIZE 256 -#define SMTPD_EXPIRE (4 * 24 * 60 * 60) +#define SMTPD_QUEUE_INTERVAL (15 * 60) +#define SMTPD_QUEUE_MAXINTERVAL (4 * 60 * 60) +#define SMTPD_QUEUE_EXPIRY (4 * 24 * 60 * 60) #define SMTPD_USER "_smtpd" #define SMTPD_SOCKET "/var/run/smtpd.sock" #define SMTPD_BANNER "220 %s ESMTP OpenSMTPD" #define SMTPD_SESSION_TIMEOUT 300 #define SMTPD_BACKLOG 5 +#define PATH_MAILLOCAL "/usr/libexec/mail.local" #define PATH_SMTPCTL "/usr/sbin/smtpctl" + +#define DIRHASH_BUCKETS 4096 + #define PATH_SPOOL "/var/spool/smtpd" + +#define PATH_ENQUEUE "/enqueue" +#define PATH_INCOMING "/incoming" +#define PATH_QUEUE "/queue" +#define PATH_PURGE "/purge" + +#define PATH_MESSAGE "/message" +#define PATH_ENVELOPES "/envelopes" + +#define PATH_RUNQUEUE "/runqueue" + #define PATH_OFFLINE "/offline" +#define PATH_BOUNCE "/bounce" /* number of MX records to lookup */ #define MAX_MX_COUNT 10 @@ -112,31 +135,38 @@ enum imsg_type { IMSG_CONF_RELOAD, IMSG_LKA_MAIL, IMSG_LKA_RCPT, - IMSG_LKA_RULEMATCH, IMSG_LKA_SECRET, + IMSG_LKA_RULEMATCH, + IMSG_MDA_SESS_NEW, IMSG_MDA_DONE, - IMSG_MFA_MAIL, IMSG_MFA_RCPT, + IMSG_MFA_MAIL, - IMSG_QUEUE_CREATE, - IMSG_QUEUE_APPEND, - IMSG_QUEUE_OPEN, - IMSG_QUEUE_CLOSE, - IMSG_QUEUE_DELETE, + IMSG_QUEUE_CREATE_MESSAGE, + IMSG_QUEUE_SUBMIT_ENVELOPE, + IMSG_QUEUE_COMMIT_ENVELOPES, + IMSG_QUEUE_REMOVE_MESSAGE, + IMSG_QUEUE_COMMIT_MESSAGE, + IMSG_QUEUE_TEMPFAIL, IMSG_QUEUE_STATS, IMSG_QUEUE_PAUSE_LOCAL, - IMSG_QUEUE_PAUSE_RELAY, + IMSG_QUEUE_PAUSE_OUTGOING, IMSG_QUEUE_RESUME_LOCAL, - IMSG_QUEUE_RESUME_RELAY, + IMSG_QUEUE_RESUME_OUTGOING, + + IMSG_QUEUE_REMOVE_SUBMISSION, + IMSG_QUEUE_MESSAGE_UPDATE, + IMSG_QUEUE_MESSAGE_FD, + IMSG_QUEUE_MESSAGE_FILE, IMSG_QUEUE_SCHEDULE, IMSG_QUEUE_REMOVE, IMSG_BATCH_CREATE, IMSG_BATCH_APPEND, IMSG_BATCH_CLOSE, - IMSG_BATCH_UPDATE, IMSG_BATCH_DONE, + IMSG_PARENT_ENQUEUE_OFFLINE, IMSG_PARENT_FORWARD_OPEN, IMSG_PARENT_FORK_MDA, IMSG_PARENT_STATS, @@ -195,7 +225,8 @@ enum smtp_proc_type { PROC_QUEUE, PROC_MDA, PROC_MTA, - PROC_CONTROL + PROC_CONTROL, + PROC_RUNNER, } smtpd_process; struct peer { @@ -371,93 +402,45 @@ struct expandnode { RB_HEAD(expandtree, expandnode); -struct action { - SLIST_ENTRY(action) entry; - u_int64_t id; - char arg[]; -}; - -struct content { - SLIST_HEAD(,action) actions; - u_int64_t id; - u_int32_t ref; - struct event *ev; +enum message_type { + T_MDA_MESSAGE = 0x1, + T_MTA_MESSAGE = 0x2, + T_BOUNCE_MESSAGE = 0x4 }; -#define NO_RETRY_EXPIRED 0 -#define RETRY_NOW 1 - -struct batch { - SLIST_ENTRY(batch) entry; - SLIST_HEAD(,action) actions; - struct content *content; - time_t retry; - char sortkey[]; -}; - -struct incoming { - SLIST_ENTRY(incoming) entry; - SLIST_HEAD(,batch) batches[3]; - struct content *content; - char **local; - int local_sz; - int nlocal; -}; - -#define Q_LOCAL 0 -#define Q_RELAY 1 -#define Q_BOUNCE 2 - -struct queue { - SLIST_HEAD(,batch) head; - struct event ev; - struct smtpd *env; - char *name; - void **session; - int session_sz; - size_t sessions; - size_t max; -}; - -struct aux { - char *mode; - char *mail_from; - char *rcpt_to; - char *user_from; - char *user_to; - char *path; - char *rcpt; - char *relay_via; - char *port; - char *ssl; - char *cert; - char *auth; -}; - -struct bounce { - struct event ev; - struct batch *batch; - struct smtp_client *pcb; - int id; +enum message_status { + S_MESSAGE_PERMFAILURE = 0x2, + S_MESSAGE_TEMPFAILURE = 0x4, + S_MESSAGE_REJECTED = 0x8, + S_MESSAGE_ACCEPTED = 0x10, + S_MESSAGE_RETRY = 0x20, + S_MESSAGE_EDNS = 0x40, + S_MESSAGE_ECONNECT = 0x80 }; enum message_flags { - F_MESSAGE_AUTHENTICATED = 0x1 -}; - -enum message_status { - S_MESSAGE_PERMFAILURE = 0x1, - S_MESSAGE_TEMPFAILURE = 0x2 + F_MESSAGE_RESOLVED = 0x1, + F_MESSAGE_SCHEDULED = 0x2, + F_MESSAGE_PROCESSING = 0x4, + F_MESSAGE_AUTHENTICATED = 0x8, + F_MESSAGE_ENQUEUED = 0x10, + F_MESSAGE_FORCESCHEDULE = 0x20, + F_MESSAGE_BOUNCE = 0x40 }; struct message { TAILQ_ENTRY(message) entry; + enum message_type type; + + u_int64_t id; + u_int64_t session_id; + u_int64_t batch_id; + char tag[MAX_TAG_SIZE]; - u_int32_t id; /* smtp session id */ - u_int32_t session_id; /* smtp session id */ - u_int32_t queue_id; + char message_id[MAX_ID_SIZE]; + char message_uid[MAX_ID_SIZE]; char session_helo[MAXHOSTNAMELEN]; char session_hostname[MAXHOSTNAMELEN]; @@ -475,6 +458,23 @@ struct message { enum message_status status; }; +enum batch_type { + T_MDA_BATCH = 0x1, + T_MTA_BATCH = 0x2, + T_BOUNCE_BATCH = 0x4 +}; + +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]; + TAILQ_HEAD(, message) messages; +}; + enum child_type { CHILD_INVALID, CHILD_DAEMON, @@ -559,7 +559,7 @@ enum session_flags { struct session { SPLAY_ENTRY(session) s_nodes; - u_int32_t s_id; + u_int64_t s_id; enum session_flags s_flags; enum session_state s_state; @@ -585,9 +585,6 @@ struct session { FILE *datafp; int mboxfd; int messagefd; - - u_int32_t queue_id; - u_int64_t content_id; }; struct smtpd { @@ -603,6 +600,7 @@ struct smtpd { #define SMTPD_MTA_PAUSED 0x00000008 #define SMTPD_SMTP_PAUSED 0x00000010 u_int32_t sc_flags; + struct timeval sc_qintval; u_int32_t sc_maxconn; struct event sc_ev; int *sc_pipes[PROC_COUNT] @@ -620,6 +618,7 @@ struct smtpd { SPLAY_HEAD(msgtree, message) sc_messages; SPLAY_HEAD(ssltree, ssl) *sc_ssl; + SPLAY_HEAD(batchtree, batch) batch_queue; SPLAY_HEAD(childtree, child) children; SPLAY_HEAD(lkatree, lkasession) lka_sessions; SPLAY_HEAD(mtatree, mta_session) mta_sessions; @@ -633,8 +632,14 @@ struct s_parent { }; struct s_queue { - size_t length; - size_t inserts; + size_t inserts_local; + size_t inserts_remote; +}; + +struct s_runner { + size_t active; + size_t bounces_active; + size_t bounces; }; struct s_session { @@ -672,12 +677,25 @@ struct s_control { struct stats { struct s_parent parent; struct s_queue queue; + struct s_runner runner; struct s_session mta; struct s_mda mda; struct s_session smtp; struct s_control control; }; +struct sched { + int fd; + char mid[MAX_ID_SIZE]; + int ret; +}; + +struct remove { + int fd; + char mid[MAX_ID_SIZE]; + int ret; +}; + struct reload { int fd; int ret; @@ -688,6 +706,7 @@ struct submit_status { int code; union submit_path { struct path path; + char msgid[MAX_ID_SIZE]; char errormsg[MAX_LINE_SIZE]; } u; enum message_flags flags; @@ -696,7 +715,7 @@ struct submit_status { }; struct forward_req { - u_int32_t id; + u_int64_t id; u_int8_t status; char pw_name[MAXLOGNAME]; }; @@ -711,23 +730,25 @@ struct dns { struct dns *next; }; +struct secret { + u_int64_t id; + char host[MAXHOSTNAMELEN]; + char secret[MAX_LINE_SIZE]; +}; + struct mda_session { LIST_ENTRY(mda_session) entry; + struct message msg; struct msgbuf w; struct event ev; - time_t birth; - u_int64_t content_id; - u_int64_t action_id; u_int32_t id; - struct aux aux; - char *auxraw; FILE *datafp; }; struct deliver { - int mode; char to[PATH_MAX]; char user[MAXLOGNAME]; + short mode; }; struct rulematch { @@ -741,7 +762,7 @@ enum lkasession_flags { struct lkasession { SPLAY_ENTRY(lkasession) nodes; - u_int32_t id; + u_int64_t id; struct path path; struct deliverylist deliverylist; @@ -759,6 +780,7 @@ enum mta_state { MTA_INVALID_STATE, MTA_INIT, MTA_SECRET, + MTA_DATA, MTA_MX, MTA_CONNECT, MTA_PTR, @@ -766,6 +788,12 @@ enum mta_state { MTA_DONE }; +/* mta session flags */ +#define MTA_FORCE_ANYSSL 0x1 +#define MTA_FORCE_SMTPS 0x2 +#define MTA_ALLOW_PLAIN 0x4 +#define MTA_USE_AUTH 0x8 + struct mta_relay { TAILQ_ENTRY(mta_relay) entry; struct sockaddr_storage sa; @@ -773,31 +801,25 @@ struct mta_relay { int used; }; -struct recipient { - TAILQ_ENTRY(recipient) entry; - u_int64_t action_id; - char status[128]; - char address[]; -}; - struct mta_session { SPLAY_ENTRY(mta_session) entry; - u_int32_t id; + u_int64_t id; struct smtpd *env; enum mta_state state; - time_t birth; - u_int64_t content_id; - struct aux aux; - char *auxraw; - TAILQ_HEAD(,recipient) recipients; + char *host; + int port; + int flags; + TAILQ_HEAD(,message) recipients; TAILQ_HEAD(,mta_relay) relays; char *secret; int fd; int datafd; struct event ev; + char *cert; void *pcb; }; + /* maps return structures */ struct map_secret { char username[MAX_LINE_SIZE]; @@ -883,9 +905,53 @@ SPLAY_PROTOTYPE(lkatree, lkasession, nodes, lkasession_cmp); /* mfa.c */ pid_t mfa(struct smtpd *); +int msg_cmp(struct message *, struct message *); /* queue.c */ pid_t queue(struct smtpd *); +int queue_load_envelope(struct message *, char *); +int queue_update_envelope(struct message *); +int queue_remove_envelope(struct message *); +void queue_submit_envelope(struct smtpd *, struct message *); +void queue_commit_envelopes(struct smtpd *, struct message*); +int batch_cmp(struct batch *, struct batch *); +struct batch *batch_by_id(struct smtpd *, u_int64_t); +u_int16_t queue_hash(char *); + +/* queue_shared.c */ +int queue_create_layout_message(char *, char *); +void queue_delete_layout_message(char *, char *); +int queue_record_layout_envelope(char *, struct message *); +int queue_remove_layout_envelope(char *, struct message *); +int queue_commit_layout_message(char *, struct message *); +int queue_open_layout_messagefile(char *, struct message *); +int enqueue_create_layout(char *); +void enqueue_delete_message(char *); +int enqueue_record_envelope(struct message *); +int enqueue_remove_envelope(struct message *); +int enqueue_commit_message(struct message *); +int enqueue_open_messagefile(struct message *); +int bounce_create_layout(char *, struct message *); +void bounce_delete_message(char *); +int bounce_record_envelope(struct message *); +int bounce_remove_envelope(struct message *); +int bounce_commit_message(struct message *); +int bounce_record_message(struct message *); +int queue_create_incoming_layout(char *); +void queue_delete_incoming_message(char *); +int queue_record_incoming_envelope(struct message *); +int queue_remove_incoming_envelope(struct message *); +int queue_commit_incoming_message(struct message *); +int queue_open_incoming_message_file(struct message *); +int queue_open_message_file(char *msgid); +void queue_message_update(struct message *); +void queue_delete_message(char *); +struct qwalk *qwalk_new(char *); +int qwalk(struct qwalk *, char *); +void qwalk_close(struct qwalk *); +void show_queue(char *, int); + +u_int16_t queue_hash(char *); /* map.c */ void *map_lookup(struct smtpd *, objid_t, char *, enum map_kind); @@ -908,6 +974,11 @@ int session_socket_error(int); int enqueue(int, char **); int enqueue_offline(int, char **); +/* runner.c */ +pid_t runner(struct smtpd *); +void message_reset_flags(struct message *); +SPLAY_PROTOTYPE(batchtree, batch, b_nodes, batch_cmp); + /* smtp.c */ pid_t smtp(struct smtpd *); void smtp_resume(struct smtpd *); @@ -915,7 +986,7 @@ void smtp_resume(struct smtpd *); /* smtp_session.c */ void session_init(struct listener *, struct session *); int session_cmp(struct session *, struct session *); -void session_pickup(struct session *); +void session_pickup(struct session *, struct submit_status *); void session_destroy(struct session *); void session_respond(struct session *, char *, ...) __attribute__ ((format (printf, 2, 3))); @@ -970,20 +1041,21 @@ void addargs(arglist *, char *, ...) __attribute__((format(printf, 2, 3))); int bsnprintf(char *, size_t, const char *, ...) __attribute__ ((format (printf, 3, 4))); +int safe_fclose(FILE *); int hostname_match(char *, char *); int recipient_to_path(struct path *, char *); int valid_localpart(char *); int valid_domainpart(char *); char *ss_to_text(struct sockaddr_storage *); +int valid_message_id(char *); +int valid_message_uid(char *); char *time_to_text(time_t); int secure_file(int, char *, struct passwd *, int); void lowercase(char *, char *, size_t); -void sa_set_port(struct sockaddr *, char *); +void message_set_errormsg(struct message *, char *, ...); +char *message_get_errormsg(struct message *); +void sa_set_port(struct sockaddr *, int); struct path *path_dup(struct path *); u_int64_t generate_uid(void); void fdlimit(double); int availdesc(void); -int table_alloc(void ***, int *); -void *table_lookup(void **, int, int); -void auxsplit(struct aux *, char *); -char *rcpt_pretty(struct aux *); diff --git a/usr.sbin/smtpd/smtpd/Makefile b/usr.sbin/smtpd/smtpd/Makefile index 4d25c8ee59c..fa57ef46aae 100644 --- a/usr.sbin/smtpd/smtpd/Makefile +++ b/usr.sbin/smtpd/smtpd/Makefile @@ -1,11 +1,12 @@ -# $OpenBSD: Makefile,v 1.18 2010/05/31 23:38:56 jacekm Exp $ +# $OpenBSD: Makefile,v 1.19 2010/06/01 19:47:09 jacekm Exp $ PROG= smtpd -SRCS= aliases.c authenticate.c client.c \ +SRCS= aliases.c authenticate.c bounce.c client.c \ config.c control.c dns.c expand.c forward.c \ lka.c log.c map.c map_backend.c map_parser.c mda.c \ - mfa.c mta.c parse.y queue.c queue_backend.c ruleset.c \ - smtp.c smtp_session.c smtpd.c ssl.c ssl_privsep.c util.c + mfa.c mta.c parse.y queue.c queue_shared.c ruleset.c \ + runner.c smtp.c smtp_session.c smtpd.c ssl.c \ + ssl_privsep.c util.c MAN= smtpd.8 smtpd.conf.5 BINDIR= /usr/sbin diff --git a/usr.sbin/smtpd/ssl.c b/usr.sbin/smtpd/ssl.c index b4b173644ee..90cf5d842e0 100644 --- a/usr.sbin/smtpd/ssl.c +++ b/usr.sbin/smtpd/ssl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssl.c,v 1.26 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: ssl.c,v 1.27 2010/06/01 19:47:09 jacekm Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -550,7 +550,7 @@ ssl_session_accept(int fd, short event, void *p) session_bufferevent_new(s); event_set(&s->s_bev->ev_read, s->s_fd, EV_READ, ssl_read, s->s_bev); event_set(&s->s_bev->ev_write, s->s_fd, EV_WRITE, ssl_write, s->s_bev); - session_pickup(s); + session_pickup(s, NULL); return; retry: diff --git a/usr.sbin/smtpd/util.c b/usr.sbin/smtpd/util.c index 140ddfd329d..bb3c2a7631b 100644 --- a/usr.sbin/smtpd/util.c +++ b/usr.sbin/smtpd/util.c @@ -1,9 +1,9 @@ -/* $OpenBSD: util.c,v 1.33 2010/05/31 23:38:56 jacekm Exp $ */ +/* $OpenBSD: util.c,v 1.34 2010/06/01 19:47:09 jacekm Exp $ */ /* * Copyright (c) 2000,2001 Markus Friedl. All rights reserved. * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> - * Copyright (c) 2009-2010 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -58,6 +58,28 @@ bsnprintf(char *str, size_t size, const char *format, ...) return 1; } +/* Close file, signifying temporary error condition (if any) to the caller. */ +int +safe_fclose(FILE *fp) +{ + if (ferror(fp)) { + fclose(fp); + return 0; + } + if (fflush(fp)) { + fclose(fp); + if (errno == ENOSPC) + return 0; + fatal("safe_fclose: fflush"); + } + if (fsync(fileno(fp))) + fatal("safe_fclose: fsync"); + if (fclose(fp)) + fatal("safe_fclose: fclose"); + + return 1; +} + int hostname_match(char *hostname, char *pattern) { @@ -178,6 +200,53 @@ ss_to_text(struct sockaddr_storage *ss) return (buf); } +int +valid_message_id(char *mid) +{ + u_int8_t cnt; + + /* [0-9]{10}\.[a-zA-Z0-9]{16} */ + for (cnt = 0; cnt < 10; ++cnt, ++mid) + if (! isdigit((int)*mid)) + return 0; + + if (*mid++ != '.') + return 0; + + for (cnt = 0; cnt < 16; ++cnt, ++mid) + if (! isalnum((int)*mid)) + return 0; + + return (*mid == '\0'); +} + +int +valid_message_uid(char *muid) +{ + u_int8_t cnt; + + /* [0-9]{10}\.[a-zA-Z0-9]{16}\.[0-9]{0,} */ + for (cnt = 0; cnt < 10; ++cnt, ++muid) + if (! isdigit((int)*muid)) + return 0; + + if (*muid++ != '.') + return 0; + + for (cnt = 0; cnt < 16; ++cnt, ++muid) + if (! isalnum((int)*muid)) + return 0; + + if (*muid++ != '.') + return 0; + + for (cnt = 0; *muid != '\0'; ++cnt, ++muid) + if (! isdigit((int)*muid)) + return 0; + + return (cnt != 0); +} + char * time_to_text(time_t when) { @@ -302,22 +371,49 @@ lowercase(char *buf, char *s, size_t len) } void -sa_set_port(struct sockaddr *sa, char *port) +message_set_errormsg(struct message *messagep, char *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + + ret = vsnprintf(messagep->session_errorline, MAX_LINE_SIZE, fmt, ap); + if (ret >= MAX_LINE_SIZE) + strlcpy(messagep->session_errorline + (MAX_LINE_SIZE - 4), "...", 4); + + /* this should not happen */ + if (ret == -1) + err(1, "vsnprintf"); + + va_end(ap); +} + +char * +message_get_errormsg(struct message *messagep) +{ + return messagep->session_errorline; +} + +void +sa_set_port(struct sockaddr *sa, int port) { - char hbuf[NI_MAXHOST]; + char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; struct addrinfo hints, *res; int error; - error = getnameinfo(sa, sa->sa_len, hbuf, sizeof hbuf, NULL, 0, NI_NUMERICHOST); + error = getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); if (error) fatalx("sa_set_port: getnameinfo failed"); memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_NUMERICHOST; + hints.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV; + + snprintf(sbuf, sizeof(sbuf), "%d", port); - error = getaddrinfo(hbuf, port, &hints, &res); + error = getaddrinfo(hbuf, sbuf, &hints, &res); if (error) fatalx("sa_set_port: getaddrinfo failed"); @@ -330,7 +426,7 @@ path_dup(struct path *path) { struct path *pathp; - pathp = calloc(1, sizeof(struct path)); + pathp = calloc(sizeof(struct path), 1); if (pathp == NULL) fatal("calloc"); @@ -413,8 +509,7 @@ session_socket_no_linger(int fd) int session_socket_error(int fd) { - socklen_t len; - int error; + int error, len; len = sizeof(error); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) @@ -422,104 +517,3 @@ session_socket_error(int fd) return (error); } - -/* - * Find unused slot in a pointer table. - */ -int -table_alloc(void ***p, int *szp) -{ - void **array; - int array_sz, i, new; - - array = *p; - array_sz = *szp; - - for (i = 0; i < array_sz; i++) - if (array[i] == NULL) - break; - - /* array full? */ - if (i == array_sz) { - if (array_sz * 2 < array_sz) - fatalx("table_alloc: overflow"); - array_sz *= 2; - array = realloc(array, ++array_sz * sizeof *array); - if (array == NULL) - fatal("array_alloc"); - for (new = i; new < array_sz; new++) - array[new] = NULL; - *p = array; - *szp = array_sz; - } - - return i; -} - -/* - * Retrieve table entry residing at given index. - */ -void * -table_lookup(void **p, int sz, int i) -{ - if (i < 0 || i >= sz) - return (NULL); - return p[i]; -} - -void -auxsplit(struct aux *a, char *aux) -{ - int col; - char *val; - - bzero(a, sizeof *a); - col = 0; - for (;;) { - val = strsep(&aux, "|"); - if (val == NULL) - break; - col++; - if (col == 1) - a->mode = val; - else if (col == 2) - a->mail_from = val; - else if (col == 3) - a->rcpt_to = val; - else if (col == 4) - a->user_from = val; - else if (a->mode[0] == 'R') { - if (col == 5) - a->rcpt = val; - else if (col == 6) - a->relay_via = val; - else if (col == 7) - a->port = val; - else if (col == 8) - a->ssl = val; - else if (col == 9) - a->cert = val; - else if (col == 10) - a->auth = val; - } else if (col == 5) - a->user_to = val; - else if (col == 6) - a->path = val; - } -} - -char * -rcpt_pretty(struct aux *aux) -{ - switch (aux->mode[0]) { - case 'M': - case 'D': - case 'P': - return aux->user_to; - case 'F': - return aux->path; - case 'R': - return aux->rcpt; - } - return NULL; -} |