diff options
author | Jacek Masiulaniec <jacekm@cvs.openbsd.org> | 2010-05-31 23:38:57 +0000 |
---|---|---|
committer | Jacek Masiulaniec <jacekm@cvs.openbsd.org> | 2010-05-31 23:38:57 +0000 |
commit | d307483c8c212fa059ff0cd0e59abc3e3d3b2ca3 (patch) | |
tree | 39d8b72f5535369d2504027c31822e039f4f731a /usr.sbin/smtpd | |
parent | 591293015f3e6c1412e51ad20d7817e6987a652f (diff) |
Rewrite entire queue code.
Major goals:
1) Fix bad performance caused by the runner process doing full queue
read in 1s intervals. My Soekris can now happily accept >50 msg/s
while having multi-thousand queue; before, one hundred queue would
bring the system to its knees.
2) Introduce Qmail-like scheduler that doesn't write as much to the
disk so that it needs less code for servicing error conditions,
which in some places can be tricky to get right.
3) Introduce separation between the scheduler and the backend; these
two queue aspects shouldn't be too tied too each other. This means
that eg. storing queue in SQL requires rewrite of just queue_backend.c.
4) Make on-disk queue format architecture independent, and more
easily extensible, to reduce number of flag days in the future.
Minor goals:
ENOSPC no longer prevents delivery attempts, fixed session limiting
for relayed mail, improved batching of "relay via" mails, human-readable
mailq output, "show queue raw" command, clearer logging, sending
of single bounce about multiple recipients, exact delay= computation,
zero delay between deliveries while within session limit (currently
1s delay between re-scheduling is enforced), mta no longer requests
content fd, corrected session limit for bounce submissions, tiny
<100B queue files instead of multi-KB, detect loops before accepting
mail, reduce traffic on imsg channels by killing enormous struct
submit_status.
Diffstat (limited to 'usr.sbin/smtpd')
30 files changed, 2663 insertions, 3414 deletions
diff --git a/usr.sbin/smtpd/aliases.c b/usr.sbin/smtpd/aliases.c index 9d7dcb44e49..25c232fb264 100644 --- a/usr.sbin/smtpd/aliases.c +++ b/usr.sbin/smtpd/aliases.c @@ -1,4 +1,4 @@ -/* $OpenBSD: aliases.c,v 1.33 2010/05/19 20:57:10 gilles Exp $ */ +/* $OpenBSD: aliases.c,v 1.34 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -67,20 +67,13 @@ aliases_exist(struct smtpd *env, objid_t mapid, char *username) } int -aliases_get(struct smtpd *env, objid_t mapid, struct expandtree *expandtree, char *username) +aliases_get(struct smtpd *env, objid_t mapid, struct expandtree *tree, char *username) { - struct map *map; struct map_alias *map_alias; struct expandnode *expnode; - char buf[MAXLOGNAME]; size_t nbaliases; - map = map_find(env, mapid); - if (map == NULL) - return 0; - - lowercase(buf, username, sizeof(buf)); - map_alias = map_lookup(env, mapid, buf, K_ALIAS); + map_alias = map_lookup(env, mapid, username, K_ALIAS); if (map_alias == NULL) return 0; @@ -88,9 +81,9 @@ aliases_get(struct smtpd *env, objid_t mapid, struct expandtree *expandtree, cha nbaliases = 0; RB_FOREACH(expnode, expandtree, &map_alias->expandtree) { if (expnode->type == EXPAND_INCLUDE) - nbaliases += aliases_expand_include(expandtree, expnode->u.filename); + nbaliases += aliases_expand_include(tree, expnode->u.filename); else { - expandtree_increment_node(expandtree, expnode); + expandtree_increment_node(tree, expnode); nbaliases++; } } diff --git a/usr.sbin/smtpd/bounce.c b/usr.sbin/smtpd/bounce.c deleted file mode 100644 index 35eb10c8119..00000000000 --- a/usr.sbin/smtpd/bounce.c +++ /dev/null @@ -1,167 +0,0 @@ -/* $OpenBSD: bounce.c,v 1.19 2010/05/19 20:57:10 gilles 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 97c54d0dab3..c9687750c1f 100644 --- a/usr.sbin/smtpd/client.c +++ b/usr.sbin/smtpd/client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: client.c,v 1.28 2010/05/26 16:44:32 nicm Exp $ */ +/* $OpenBSD: client.c,v 1.29 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> @@ -955,5 +955,7 @@ 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 b4ec43365a9..4776727d3a4 100644 --- a/usr.sbin/smtpd/control.c +++ b/usr.sbin/smtpd/control.c @@ -1,4 +1,4 @@ -/* $OpenBSD: control.c,v 1.49 2010/04/21 18:54:43 jacekm Exp $ */ +/* $OpenBSD: control.c,v 1.50 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -36,6 +36,7 @@ #include <unistd.h> #include "smtpd.h" +#include "queue_backend.h" #define CONTROL_BACKLOG 5 @@ -63,8 +64,7 @@ control_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { struct ctl_conn *c; struct reload *reload; - struct remove *rem; - struct sched *sched; + int error; if (iev->proc == PROC_SMTP) { switch (imsg->hdr.type) { @@ -81,23 +81,17 @@ 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: - rem = imsg->data; - c = control_connbyfd(rem->fd); + c = control_connbyfd(imsg->hdr.peerid); if (c == NULL) return; - imsg_compose_event(&c->iev, - rem->ret ? IMSG_CTL_OK : IMSG_CTL_FAIL, 0, 0, - -1, NULL, 0); + 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); return; } } @@ -394,47 +388,14 @@ 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: { - struct sched *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_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)) + case IMSG_QUEUE_SCHEDULE: + case IMSG_QUEUE_REMOVE: + if (euid || IMSG_DATA_SIZE(&imsg) != sizeof(u_int64_t)) 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)); + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + imsg.hdr.type, fd, 0, -1, imsg.data, + sizeof(u_int64_t)); break; - } /* case IMSG_CONF_RELOAD: { struct reload r; @@ -491,17 +452,19 @@ 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_OUTGOING: + case IMSG_QUEUE_PAUSE_RELAY: if (euid) goto badcred; @@ -512,7 +475,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_OUTGOING, 0, 0, -1, NULL, 0); + IMSG_QUEUE_PAUSE_RELAY, 0, 0, -1, NULL, 0); imsg_compose_event(&c->iev, IMSG_CTL_OK, 0, 0, -1, NULL, 0); break; case IMSG_SMTP_PAUSE: @@ -533,17 +496,19 @@ 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_OUTGOING: + case IMSG_QUEUE_RESUME_RELAY: if (euid) goto badcred; @@ -554,7 +519,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_OUTGOING, 0, 0, -1, NULL, 0); + IMSG_QUEUE_RESUME_RELAY, 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 cbaf2f1420a..9544425b451 100644 --- a/usr.sbin/smtpd/enqueue.c +++ b/usr.sbin/smtpd/enqueue.c @@ -1,4 +1,4 @@ -/* $OpenBSD: enqueue.c,v 1.33 2010/04/21 17:50:28 jacekm Exp $ */ +/* $OpenBSD: enqueue.c,v 1.34 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2005 Henning Brauer <henning@bulabula.org> @@ -163,7 +163,6 @@ 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 c0bfbc44e81..07a4b371c83 100644 --- a/usr.sbin/smtpd/expand.c +++ b/usr.sbin/smtpd/expand.c @@ -1,4 +1,4 @@ -/* $OpenBSD: expand.c,v 1.5 2010/05/19 20:57:10 gilles Exp $ */ +/* $OpenBSD: expand.c,v 1.6 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2009 Gilles Chehade <gilles@openbsd.org> @@ -33,27 +33,26 @@ #include "smtpd.h" struct expandnode * -expandtree_lookup(struct expandtree *expandtree, struct expandnode *node) +expandtree_lookup(struct expandtree *tree, struct expandnode *node) { struct expandnode key; key = *node; - return RB_FIND(expandtree, expandtree, &key); + return RB_FIND(expandtree, tree, &key); } void -expandtree_increment_node(struct expandtree *expandtree, struct expandnode *node) +expandtree_increment_node(struct expandtree *tree, struct expandnode *node) { struct expandnode *p; - p = expandtree_lookup(expandtree, node); + p = expandtree_lookup(tree, node); if (p == NULL) { - p = calloc(1, sizeof(struct expandnode)); + p = malloc(sizeof *node); if (p == NULL) - fatal("calloc"); - *p = *node; - if (RB_INSERT(expandtree, expandtree, p)) - fatalx("expandtree_increment_node: node already exists"); + fatal(NULL); + *p = *node; /* XXX p->refcnt == node->refcnt */ + RB_INSERT(expandtree, tree, p); } p->refcnt++; } @@ -86,10 +85,8 @@ void expandtree_free_nodes(struct expandtree *expandtree) { struct expandnode *p; - struct expandnode *nxt; - for (p = RB_MIN(expandtree, expandtree); p != NULL; p = nxt) { - nxt = RB_NEXT(expandtree, expandtree, p); + while ((p = RB_MIN(expandtree, expandtree))) { RB_REMOVE(expandtree, expandtree, p); free(p); } diff --git a/usr.sbin/smtpd/lka.c b/usr.sbin/smtpd/lka.c index 38bd521682a..c5d1dddbdb4 100644 --- a/usr.sbin/smtpd/lka.c +++ b/usr.sbin/smtpd/lka.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka.c,v 1.108 2010/05/27 15:36:04 gilles Exp $ */ +/* $OpenBSD: lka.c,v 1.109 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -52,30 +52,35 @@ 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 submit_status *); -void lka_request_forwardfile(struct smtpd *, struct lkasession *, char *); +struct lkasession *lka_session_init(struct smtpd *, struct message *); +void lka_request_forwardfile(struct smtpd *, struct lkasession *, struct path *); void lka_clear_expandtree(struct expandtree *); void lka_clear_deliverylist(struct deliverylist *); -int lka_encode_credentials(char *, size_t, struct map_secret *); +char *lka_encode_secret(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 *, struct submit_status *); +void lka_session_fail(struct smtpd *, struct lkasession *); +void lka_queue_append(struct smtpd *, struct lkasession *, int); + +u_int32_t lka_id; 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 secret *secret; + struct message *m; 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) { @@ -86,67 +91,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: - 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_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; - } - imsg_compose_event(iev, IMSG_LKA_RULEMATCH, 0, 0, -1, - ss, sizeof *ss); + 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); return; 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); + 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; + } + 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); 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); + return; + } + } + if (iev->proc == PROC_MTA) { switch (imsg->hdr.type) { - case IMSG_LKA_SECRET: { - struct map_secret *map_secret; - secret = imsg->data; + case IMSG_LKA_SECRET: map = map_findbyname(env, "secrets"); if (map == NULL) fatalx("lka: secrets map not found"); - 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); + 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); free(map_secret); return; } - } } if (iev->proc == PROC_PARENT) { @@ -557,32 +562,34 @@ lka_resolve_node(struct smtpd *env, char *tag, struct path *path, struct expandn } void -lka_expand_pickup(struct smtpd *env, struct lkasession *lkasession) +lka_expand_pickup(struct smtpd *env, struct lkasession *s) { 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. */ - 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) + 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) break; } - if (lkasession->pending) - return; - - lka_expansion_done(env, lkasession); + lka_expansion_done(env, s); } int @@ -631,45 +638,75 @@ lka_expand_resume(struct smtpd *env, struct lkasession *lkasession) } void -lka_expansion_done(struct smtpd *env, struct lkasession *lkasession) +lka_expansion_done(struct smtpd *env, struct lkasession *s) { - struct message message; - struct path *path; + int status; /* delivery list is empty OR expansion led to an error, reject */ - 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; - } + 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); +} - /* 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); +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; } - queue_commit_envelopes(env, &message); -done: - lka_clear_expandtree(&lkasession->expandtree); - lka_clear_deliverylist(&lkasession->deliverylist); - lka_session_destroy(env, lkasession); + /* 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); + } + 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); } int -lka_resolve_path(struct smtpd *env, struct lkasession *lkasession, struct path *path) +lka_resolve_path(struct smtpd *env, struct lkasession *s, struct path *path) { if (IS_RELAY(*path)) { path = path_dup(path); path->flags |= F_PATH_RELAY; - TAILQ_INSERT_TAIL(&lkasession->deliverylist, path, entry); + TAILQ_INSERT_TAIL(&s->deliverylist, path, entry); return 1; } @@ -689,37 +726,27 @@ lka_resolve_path(struct smtpd *env, struct lkasession *lkasession, struct path * if (aliases_exist(env, path->rule.r_amap, username)) { path->flags |= F_PATH_ALIAS; - if (! aliases_get(env, path->rule.r_amap, - &lkasession->expandtree, path->user)) - return 0; - return 1; + return aliases_get(env, path->rule.r_amap, + &s->expandtree, username); } - if (strlen(username) >= MAXLOGNAME) - return 0; - path->flags |= F_PATH_ACCOUNT; pw = getpwnam(username); if (pw == NULL) return 0; - (void)strlcpy(path->pw_name, pw->pw_name, - sizeof(path->pw_name)); + strlcpy(path->pw_name, pw->pw_name, sizeof path->pw_name); if (path->flags & F_PATH_FORWARDED) - TAILQ_INSERT_TAIL(&lkasession->deliverylist, path, entry); + TAILQ_INSERT_TAIL(&s->deliverylist, path, entry); else - lka_request_forwardfile(env, lkasession, path->pw_name); - + lka_request_forwardfile(env, s, path); return 1; } case C_VDOM: { if (aliases_virtual_exist(env, path->rule.r_condition.c_map, path)) { path->flags |= F_PATH_VIRTUAL; - if (! aliases_virtual_get(env, path->rule.r_condition.c_map, - &lkasession->expandtree, path)) - return 0; - return 1; + return aliases_virtual_get(env, path->rule.r_condition.c_map, &s->expandtree, path); } break; } @@ -785,72 +812,75 @@ lka_clear_deliverylist(struct deliverylist *deliverylist) } } -int -lka_encode_credentials(char *dst, size_t size, struct map_secret *map_secret) +char * +lka_encode_secret(struct map_secret *map_secret) { - char *buf; - int buflen; + static char dst[1024]; + char *src; + int src_sz; - if ((buflen = asprintf(&buf, "%c%s%c%s", '\0', map_secret->username, - '\0', map_secret->password)) == -1) + src_sz = asprintf(&src, "%c%s%c%s", '\0', map_secret->username, '\0', + map_secret->password); + if (src_sz == -1) fatal(NULL); - - if (__b64_ntop((unsigned char *)buf, buflen, dst, size) == -1) { - free(buf); - return 0; + if (__b64_ntop(src, src_sz, dst, sizeof dst) == -1) { + free(src); + return NULL; } + dst[sizeof(dst) - 1] = '\0'; - free(buf); - return 1; + return dst; } struct lkasession * -lka_session_init(struct smtpd *env, struct submit_status *ss) +lka_session_init(struct smtpd *env, struct message *m) { - 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; + 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; } void -lka_session_fail(struct smtpd *env, struct lkasession *lkasession, struct submit_status *ss) +lka_session_fail(struct smtpd *env, struct lkasession *s) { - ss->code = 530; + 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, 0, 0, -1, - ss, sizeof(*ss)); - lka_session_destroy(env, lkasession); + &status, sizeof status); + lka_session_destroy(env, s); } void -lka_session_destroy(struct smtpd *env, struct lkasession *lkasession) +lka_session_destroy(struct smtpd *env, struct lkasession *s) { - SPLAY_REMOVE(lkatree, &env->lka_sessions, lkasession); - free(lkasession); + SPLAY_REMOVE(lkatree, &env->lka_sessions, s); + free(s); } void -lka_request_forwardfile(struct smtpd *env, struct lkasession *lkasession, char *username) +lka_request_forwardfile(struct smtpd *env, struct lkasession *s, struct path *path) { struct forward_req fwreq; - fwreq.id = lkasession->id; - (void)strlcpy(fwreq.pw_name, username, sizeof(fwreq.pw_name)); + fwreq.id = s->id; + strlcpy(fwreq.pw_name, path->pw_name, sizeof fwreq.pw_name); imsg_compose_event(env->sc_ievs[PROC_PARENT], IMSG_PARENT_FORWARD_OPEN, 0, 0, -1, - &fwreq, sizeof(fwreq)); - ++lkasession->pending; + &fwreq, sizeof fwreq); + s->pending++; } SPLAY_GENERATE(lkatree, lkasession, nodes, lkasession_cmp); diff --git a/usr.sbin/smtpd/log.c b/usr.sbin/smtpd/log.c index 47f0e6e2291..50986b6d2c2 100644 --- a/usr.sbin/smtpd/log.c +++ b/usr.sbin/smtpd/log.c @@ -1,4 +1,4 @@ -/* $OpenBSD: log.c,v 1.5 2010/05/19 20:57:10 gilles Exp $ */ +/* $OpenBSD: log.c,v 1.6 2010/05/31 23:38:56 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_PID | LOG_NDELAY, LOG_MAIL); + openlog(__progname, LOG_NDELAY, LOG_MAIL); tzset(); } diff --git a/usr.sbin/smtpd/map.c b/usr.sbin/smtpd/map.c index 4dc3dfff778..dba969a76a1 100644 --- a/usr.sbin/smtpd/map.c +++ b/usr.sbin/smtpd/map.c @@ -1,4 +1,4 @@ -/* $OpenBSD: map.c,v 1.16 2010/04/27 09:49:23 gilles Exp $ */ +/* $OpenBSD: map.c,v 1.17 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -64,13 +64,12 @@ 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 = NULL; - char *result = NULL; - char *ret = NULL; + void *hdl; + char *result, *tmp; size_t len; struct map *map; - struct map_backend *backend = NULL; - struct map_parser *parser = NULL; + struct map_backend *backend; + struct map_parser *parser; map = map_find(env, mapid); if (map == NULL) @@ -85,16 +84,17 @@ map_lookup(struct smtpd *env, objid_t mapid, char *key, enum map_kind kind) return NULL; } - ret = result = backend->get(hdl, key, &len); - if (ret == NULL) + result = backend->get(hdl, key, &len); + if (result == NULL) goto end; - if (parser->extract != NULL) { - ret = parser->extract(key, result, len); - free(result); + if (parser->extract) { + tmp = result; + result = parser->extract(key, result, len); + free(tmp); } end: backend->close(hdl); - return ret; + return result; } diff --git a/usr.sbin/smtpd/mda.c b/usr.sbin/smtpd/mda.c index a91e540295c..31e4197df55 100644 --- a/usr.sbin/smtpd/mda.c +++ b/usr.sbin/smtpd/mda.c @@ -1,9 +1,9 @@ -/* $OpenBSD: mda.c,v 1.44 2010/05/26 13:56:08 nicm Exp $ */ +/* $OpenBSD: mda.c,v 1.45 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> - * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009-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 @@ -36,6 +36,7 @@ #include <vis.h> #include "smtpd.h" +#include "queue_backend.h" void mda_imsg(struct smtpd *, struct imsgev *, struct imsg *); __dead void mda_shutdown(void); @@ -46,74 +47,65 @@ 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 path *path; + struct action *action; + size_t action_sz; if (iev->proc == PROC_QUEUE) { switch (imsg->hdr.type) { - case IMSG_MDA_SESS_NEW: - /* make new session based on provided args */ - s = calloc(1, sizeof *s); + case IMSG_BATCH_CREATE: + s = malloc(sizeof *s); if (s == NULL) fatal(NULL); msgbuf_init(&s->w); - s->msg = *(struct message *)imsg->data; - s->msg.status = S_MESSAGE_TEMPFAILURE; - s->id = mda_id++; + bzero(&s->ev, sizeof s->ev); + s->id = imsg->hdr.peerid; + s->content_id = *(u_int64_t *)imsg->data; 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; - /* 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); + 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); snprintf(deliver.to, sizeof deliver.to, - "%s -f %s@%s %s", PATH_MAILLOCAL, - s->msg.sender.user, s->msg.sender.domain, - path->pw_name); - break; - - 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"); + "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); } - imsg_compose_event(env->sc_ievs[PROC_PARENT], IMSG_PARENT_FORK_MDA, s->id, 0, -1, &deliver, sizeof deliver); @@ -136,6 +128,10 @@ 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. */ @@ -162,12 +158,11 @@ mda_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) buf[len] = '\0'; ln = buf; } - strlcpy(output, "\"", sizeof output); - strnvis(output + 1, ln, - sizeof(output) - 2, + strlcpy(output, "100 \"", sizeof output); + strnvis(output + 5, ln, + sizeof(output) - 6, VIS_SAFE | VIS_CSTYLE); strlcat(output, "\"", sizeof output); - log_debug("mda_out: %s", output); } free(buf); fclose(fp); @@ -178,11 +173,11 @@ mda_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) * child's output, the latter having preference over * the former. */ - error = NULL; - parent_error = imsg->data; - if (strcmp(parent_error, "exited okay") == 0) { + if (strcmp(parent_error + 4, "exited okay") == 0) { if (!feof(s->datafp) || s->w.queued) - error = "mda exited prematurely"; + error = "100 mda exited prematurely"; + else + error = "200 ok"; } else { if (output[0]) error = output; @@ -191,35 +186,24 @@ mda_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) } /* update queue entry */ - if (error == NULL) - s->msg.status = S_MESSAGE_ACCEPTED; - else - strlcpy(s->msg.session_errorline, error, - sizeof s->msg.session_errorline); + 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); imsg_compose_event(env->sc_ievs[PROC_QUEUE], - 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; + 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); /* log status */ - if (error && asprintf(&error, "Error (%s)", error) < 0) - fatal("mda: asprintf"); - log_info("%s: to=<%s@%s>, delay=%d, stat=%s", - s->msg.message_id, path->user, path->domain, - time(NULL) - s->msg.creation, - error ? error : "Sent"); - free(error); + 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' ? "" : ")"); /* destroy session */ LIST_REMOVE(s, entry); @@ -229,11 +213,9 @@ 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); - - /* update queue's session count */ - imsg_compose_event(env->sc_ievs[PROC_QUEUE], - IMSG_MDA_SESS_NEW, 0, 0, -1, NULL, 0); + free(parent_error); return; case IMSG_CTL_VERBOSE: @@ -325,13 +307,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); - event_dispatch(); + if (event_dispatch() < 0) + log_warn("event_dispatch"); mda_shutdown(); return (0); @@ -344,14 +326,12 @@ mda_store(struct mda_session *s) struct ibuf *buf; int len; - 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); + /* 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); else - len = asprintf(&p, "Delivered-To: %s@%s\n", - s->msg.session_rcpt.user, s->msg.session_rcpt.domain); + len = asprintf(&p, "Delivered-To: %s\n", s->aux.rcpt_to); if (len == -1) fatal("mda_store: asprintf"); diff --git a/usr.sbin/smtpd/mfa.c b/usr.sbin/smtpd/mfa.c index b13c8d5f84c..454b632b70a 100644 --- a/usr.sbin/smtpd/mfa.c +++ b/usr.sbin/smtpd/mfa.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mfa.c,v 1.45 2010/04/21 08:29:01 jacekm Exp $ */ +/* $OpenBSD: mfa.c,v 1.46 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -42,7 +42,6 @@ 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); @@ -66,14 +65,15 @@ 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, 0, 0, -1, imsg->data, - sizeof(struct submit_status)); + IMSG_MFA_MAIL, imsg->hdr.peerid, 0, -1, imsg->data, + imsg->hdr.len - sizeof imsg->hdr); return; - case IMSG_LKA_RULEMATCH: - mfa_test_rcpt_resume(env, imsg->data); + 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); return; } } @@ -181,38 +181,20 @@ 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) { - struct submit_status ss; + int status; - 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))) + if (strip_source_route(m->sender.user, sizeof(m->sender.user))) goto refuse; - if (! valid_localpart(ss.u.path.user) || - ! valid_domainpart(ss.u.path.domain)) { + if (! valid_localpart(m->sender.user) || + ! valid_domainpart(m->sender.domain)) { /* * "MAIL FROM:<>" is the exception we allow. */ - if (!(ss.u.path.user[0] == '\0' && ss.u.path.domain[0] == '\0')) + if (!(m->sender.user[0] == '\0' && m->sender.domain[0] == '\0')) goto refuse; } @@ -220,61 +202,39 @@ mfa_test_mail(struct smtpd *env, struct message *m) goto accept; refuse: - imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_MAIL, 0, 0, -1, &ss, - sizeof(ss)); + status = S_MESSAGE_PERMFAILURE; + imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_MAIL, m->id, 0, -1, + &status, sizeof status); return; accept: - ss.code = 250; imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_MAIL, 0, - 0, -1, &ss, sizeof(ss)); + 0, -1, m, sizeof *m); } void mfa_test_rcpt(struct smtpd *env, struct message *m) { - struct submit_status ss; - - if (! valid_message_id(m->message_id)) - fatalx("mfa_test_rcpt: received corrupted message_id"); + int status; - 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; + m->recipient = m->session_rcpt; - strip_source_route(ss.u.path.user, sizeof(ss.u.path.user)); + strip_source_route(m->recipient.user, sizeof(m->recipient.user)); - if (! valid_localpart(ss.u.path.user) || - ! valid_domainpart(ss.u.path.domain)) + if (! valid_localpart(m->recipient.user) || + ! valid_domainpart(m->recipient.domain)) goto refuse; - 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)); + if (m->flags & F_MESSAGE_AUTHENTICATED) + m->recipient.flags |= F_PATH_AUTHENTICATED; + imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_RCPT, 0, 0, -1, + m, sizeof *m); return; refuse: - 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)); + status = S_MESSAGE_PERMFAILURE; + imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_RCPT, m->id, 0, -1, + &status, sizeof status); } int diff --git a/usr.sbin/smtpd/mta.c b/usr.sbin/smtpd/mta.c index 9184b97f905..995d090ce49 100644 --- a/usr.sbin/smtpd/mta.c +++ b/usr.sbin/smtpd/mta.c @@ -1,9 +1,9 @@ -/* $OpenBSD: mta.c,v 1.87 2010/04/21 18:54:43 jacekm Exp $ */ +/* $OpenBSD: mta.c,v 1.88 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> - * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009-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 @@ -40,114 +40,83 @@ #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_int64_t); +struct mta_session *mta_lookup(struct smtpd *, u_int32_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_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_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_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 message *m; - struct secret *secret; - struct batch *b; + struct action *action; struct dns *dns; struct ssl *ssl; + size_t rcpt_sz; if (iev->proc == PROC_QUEUE) { switch (imsg->hdr.type) { case IMSG_BATCH_CREATE: - b = imsg->data; + if (imsg->fd < 0) + fatalx("mta: fd pass fail"); s = calloc(1, sizeof *s); if (s == NULL) fatal(NULL); - s->id = b->id; - s->state = MTA_INIT; - s->env = env; - 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); + s->id = imsg->hdr.peerid; + s->state = MTA_INIT; + s->env = env; + s->datafd = imsg->fd; + memcpy(&s->content_id, imsg->data, + sizeof s->content_id); SPLAY_INSERT(mtatree, &env->mta_sessions, s); return; case IMSG_BATCH_APPEND: - m = imsg->data; - s = mta_lookup(env, m->batch_id); - m = malloc(sizeof *m); - if (m == NULL) + 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) fatal(NULL); - *m = *(struct message *)imsg->data; - strlcpy(m->session_errorline, "000 init", - sizeof(m->session_errorline)); - TAILQ_INSERT_TAIL(&s->recipients, m, entry); + 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); return; case IMSG_BATCH_CLOSE: - 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); + s = mta_lookup(env, imsg->hdr.peerid); + memcpy(&s->birth, imsg->data, sizeof s->birth); + mta_pickup(s, NULL); return; } } @@ -155,8 +124,7 @@ mta_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_LKA) { switch (imsg->hdr.type) { case IMSG_LKA_SECRET: - secret = imsg->data; - mta_pickup(mta_lookup(env, secret->id), secret->secret); + mta_pickup(mta_lookup(env, imsg->hdr.peerid), imsg->data); return; case IMSG_DNS_A: @@ -322,24 +290,25 @@ mta_session_cmp(struct mta_session *a, struct mta_session *b) } struct mta_session * -mta_lookup(struct smtpd *env, u_int64_t id) +mta_lookup(struct smtpd *env, u_int32_t id) { struct mta_session key, *res; key.id = id; - if ((res = SPLAY_FIND(mtatree, &env->mta_sessions, &key)) == NULL) + res = SPLAY_FIND(mtatree, &env->mta_sessions, &key); + if (res == 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 message *m; + struct recipient *rcpt; struct smtp_client *pcb; + char *host; int max_reuse; s->state = newstate; @@ -349,33 +318,34 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) /* * Lookup AUTH secret. */ - bzero(&secret, sizeof(secret)); - secret.id = s->id; - strlcpy(secret.host, s->host, sizeof(secret.host)); + if (s->aux.relay_via[0]) + host = s->aux.relay_via; + else { + rcpt = TAILQ_FIRST(&s->recipients); + host = strchr(rcpt->address, '@') + 1; + } imsg_compose_event(s->env->sc_ievs[PROC_LKA], IMSG_LKA_SECRET, - 0, 0, -1, &secret, sizeof(secret)); + s->id, 0, -1, host, strlen(host) + 1); break; case MTA_MX: /* * Lookup MX record. */ - 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); + 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); break; case MTA_CONNECT: /* * Connect to the MX. */ - if (s->flags & MTA_FORCE_ANYSSL) + if (strcmp(s->aux.ssl, "ssl") == 0) max_reuse = 2; else max_reuse = 1; @@ -392,18 +362,19 @@ 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->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); + 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"); 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("mta cannot create socket"); + fatal("socket"); session_socket_blockmode(s->fd, BM_NONBLOCK); session_socket_no_linger(s->fd); @@ -441,10 +412,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->cert) { - struct ssl key, *res; + if (s->aux.cert[0]) { + struct ssl key, *res; - strlcpy(key.ssl_name, s->cert, sizeof(key.ssl_name)); + strlcpy(key.ssl_name, s->aux.cert, sizeof key.ssl_name); res = SPLAY_FIND(ssltree, s->env->sc_ssl, &key); if (res == NULL) { client_close(pcb); @@ -459,11 +430,11 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) /* choose SMTPS vs. STARTTLS */ relay = TAILQ_FIRST(&s->relays); - if ((s->flags & MTA_FORCE_ANYSSL) && relay->used == 1) + if (strcmp(s->aux.ssl, "ssl") == 0 && relay->used == 1) client_ssl_smtps(pcb); - else if (s->flags & MTA_FORCE_SMTPS) + else if (strcmp(s->aux.ssl, "smtps") == 0) client_ssl_smtps(pcb); - else if (s->flags & MTA_ALLOW_PLAIN) + else if (s->aux.ssl[0] == '\0') client_ssl_optional(pcb); /* enable AUTH */ @@ -471,17 +442,14 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) client_auth(pcb, s->secret); /* set envelope sender */ - 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); + if (s->aux.mail_from[0]) + client_sender(pcb, "%s", s->aux.mail_from); else client_sender(pcb, ""); /* set envelope recipients */ - TAILQ_FOREACH(m, &s->recipients, entry) - client_rcpt(pcb, m, "%s@%s", m->recipient.user, - m->recipient.domain); + TAILQ_FOREACH(rcpt, &s->recipients, entry) + client_rcpt(pcb, rcpt, "%s", rcpt->address); s->pcb = pcb; event_set(&s->ev, s->fd, EV_READ|EV_WRITE, mta_event, s); @@ -494,11 +462,11 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) */ /* update queue status */ - while ((m = TAILQ_FIRST(&s->recipients))) - mta_message_done(s, m); + while ((rcpt = TAILQ_FIRST(&s->recipients))) + mta_rcpt_done(s, rcpt); imsg_compose_event(s->env->sc_ievs[PROC_QUEUE], - IMSG_BATCH_DONE, 0, 0, -1, NULL, 0); + IMSG_BATCH_DONE, s->id, 0, -1, NULL, 0); /* deallocate resources */ SPLAY_REMOVE(mtatree, &s->env->mta_sessions, s); @@ -507,9 +475,8 @@ 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; @@ -525,7 +492,7 @@ mta_pickup(struct mta_session *s, void *p) switch (s->state) { case MTA_INIT: - if (s->flags & MTA_USE_AUTH) + if (s->aux.auth[0]) mta_enter_state(s, MTA_SECRET, NULL); else mta_enter_state(s, MTA_MX, NULL); @@ -556,15 +523,6 @@ 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; @@ -593,6 +551,7 @@ 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"); @@ -605,9 +564,10 @@ mta_event(int fd, short event, void *p) case CLIENT_STOP_WRITE: goto ro; case CLIENT_RCPT_FAIL: - mta_message_status(pcb->rcptfail, pcb->reply); - mta_message_log(s, pcb->rcptfail); - mta_message_done(s, pcb->rcptfail); + rcpt = pcb->rcptfail; + mta_rcpt_status(pcb->rcptfail, pcb->reply); + mta_rcpt_log(s, pcb->rcptfail); + mta_rcpt_done(s, pcb->rcptfail); goto rw; case CLIENT_DONE: mta_status(s, "%s", pcb->status); @@ -620,10 +580,13 @@ out: client_close(pcb); s->pcb = NULL; - if (TAILQ_EMPTY(&s->recipients)) + if (TAILQ_EMPTY(&s->recipients)) { + log_debug("%s: leaving", __func__); mta_enter_state(s, MTA_DONE, NULL); - else + } else { + log_debug("%s: connecting to next", __func__); mta_enter_state(s, MTA_CONNECT, NULL); + } return; rw: @@ -640,7 +603,7 @@ void mta_status(struct mta_session *s, const char *fmt, ...) { char *status; - struct message *m, *next; + struct recipient *rcpt, *next; va_list ap; va_start(ap, fmt); @@ -648,16 +611,16 @@ mta_status(struct mta_session *s, const char *fmt, ...) fatal("vasprintf"); va_end(ap); - for (m = TAILQ_FIRST(&s->recipients); m; m = next) { - next = TAILQ_NEXT(m, entry); + for (rcpt = TAILQ_FIRST(&s->recipients); rcpt; rcpt = next) { + next = TAILQ_NEXT(rcpt, entry); /* save new status */ - mta_message_status(m, status); + mta_rcpt_status(rcpt, status); /* remove queue entry */ if (*status == '2' || *status == '5' || *status == '6') { - mta_message_log(s, m); - mta_message_done(s, m); + mta_rcpt_log(s, rcpt); + mta_rcpt_done(s, rcpt); } } @@ -665,58 +628,56 @@ mta_status(struct mta_session *s, const char *fmt, ...) } void -mta_message_status(struct message *m, char *status) +mta_rcpt_status(struct recipient *rcpt, 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(m->session_errorline, status, 3) > 0) + if (*status != '2' && strncmp(rcpt->status, status, 3) > 0) return; /* change 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)); + log_debug("mta: new status for %s: %s", rcpt->address, status); + strlcpy(rcpt->status, status, sizeof rcpt->status); } void -mta_message_log(struct mta_session *s, struct message *m) +mta_rcpt_log(struct mta_session *s, struct recipient *rcpt) { - struct mta_relay *relay = TAILQ_FIRST(&s->relays); - char *status = m->session_errorline; + struct mta_relay *relay; + + relay = TAILQ_FIRST(&s->relays); - 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, + 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, relay ? relay->fqdn : "(none)", relay ? ss_to_text(&relay->sa) : "", - *status == '2' ? "Sent" : - *status == '5' ? "RemoteError" : - *status == '4' ? "RemoteError" : "LocalError", - status + 4); + rcpt->status[0] == '2' ? "Sent" : + rcpt->status[0] == '5' ? "RemoteError" : + rcpt->status[0] == '4' ? "RemoteError" : "LocalError", + rcpt->status + 4); } void -mta_message_done(struct mta_session *s, struct message *m) +mta_rcpt_done(struct mta_session *s, struct recipient *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); + 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); } void @@ -725,17 +686,4 @@ 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 5aece32fd7e..5a580029c38 100644 --- a/usr.sbin/smtpd/parse.y +++ b/usr.sbin/smtpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.59 2010/05/27 15:36:04 gilles Exp $ */ +/* $OpenBSD: parse.y,v 1.60 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -116,7 +116,7 @@ typedef struct { %} -%token QUEUE INTERVAL SIZE LISTEN ON ALL PORT +%token 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,10 +266,7 @@ tag : TAG STRING { | /* empty */ { $$ = NULL; } ; -main : QUEUE INTERVAL interval { - conf->sc_qintval = $3; - } - | SIZE size { +main : SIZE size { conf->sc_maxsize = $2; } | LISTEN ON STRING port ssl certname auth tag { @@ -1059,7 +1056,6 @@ lookup(char *s) { "hash", HASH }, { "hostname", HOSTNAME }, { "include", INCLUDE }, - { "interval", INTERVAL }, { "list", LIST }, { "listen", LISTEN }, { "local", LOCAL }, @@ -1071,7 +1067,6 @@ lookup(char *s) { "on", ON }, { "plain", PLAIN }, { "port", PORT }, - { "queue", QUEUE }, { "reject", REJECT }, { "relay", RELAY }, { "single", SINGLE }, @@ -1463,8 +1458,6 @@ 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 055ba67d07f..5f7d09cf24b 100644 --- a/usr.sbin/smtpd/parser.c +++ b/usr.sbin/smtpd/parser.c @@ -1,4 +1,4 @@ -/* $OpenBSD: parser.c,v 1.11 2010/01/10 16:42:35 gilles Exp $ */ +/* $OpenBSD: parser.c,v 1.12 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -58,6 +58,7 @@ 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[]; @@ -78,12 +79,18 @@ static const struct token t_main[] = { }; static const struct token t_show[] = { - {KEYWORD, "queue", SHOW_QUEUE, NULL}, + {KEYWORD, "queue", SHOW_QUEUE, t_show_queue}, {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}, @@ -99,12 +106,12 @@ static const struct token t_resume[] = { }; static const struct token t_schedule[] = { - {VARIABLE, "message id/uid", SCHEDULE, NULL}, + {VARIABLE, "message", SCHEDULE, NULL}, {ENDTOKEN, "", NONE, NULL} }; static const struct token t_remove[] = { - {VARIABLE, "message id/uid", REMOVE, NULL}, + {VARIABLE, "message", REMOVE, NULL}, {ENDTOKEN, "", NONE, NULL} }; diff --git a/usr.sbin/smtpd/parser.h b/usr.sbin/smtpd/parser.h index 5a9b56c9c04..88142c943e9 100644 --- a/usr.sbin/smtpd/parser.h +++ b/usr.sbin/smtpd/parser.h @@ -1,4 +1,4 @@ -/* $OpenBSD: parser.h,v 1.10 2010/01/10 16:42:35 gilles Exp $ */ +/* $OpenBSD: parser.h,v 1.11 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -26,6 +26,7 @@ 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 71ddb9c0421..0d930e67b38 100644 --- a/usr.sbin/smtpd/queue.c +++ b/usr.sbin/smtpd/queue.c @@ -1,6 +1,7 @@ -/* $OpenBSD: queue.c,v 1.81 2010/04/22 12:13:33 jacekm Exp $ */ +/* $OpenBSD: queue.c,v 1.82 2010/05/31 23:38:56 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> * @@ -23,11 +24,14 @@ #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> @@ -36,156 +40,308 @@ #include <unistd.h> #include "smtpd.h" +#include "queue_backend.h" +#include "client.h" -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 *, 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 *env, struct imsgev *iev, struct imsg *imsg) { - struct submit_status ss; + struct action *update; + struct incoming *s; + struct batch *batch; struct message *m; - struct batch *b; - int fd, ret; + u_int64_t content_id; + u_int rq; + int i, fd, error; + time_t now; + struct iovec iov[2]; + char aux[2048]; /* XXX */ if (iev->proc == PROC_SMTP) { - m = imsg->data; - switch (imsg->hdr.type) { - 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); + 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); return; - 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); + 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); return; - 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); + 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); return; - 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); + 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 = 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); return; case IMSG_SMTP_ENQUEUE: - queue_pass_to_runner(env, iev, imsg); + queue_bounce_init(imsg->hdr.peerid, imsg->fd); return; } } if (iev->proc == PROC_LKA) { - m = imsg->data; - switch (imsg->hdr.type) { - case IMSG_QUEUE_SUBMIT_ENVELOPE: - m->id = generate_uid(); - ss.id = m->session_id; + case IMSG_QUEUE_APPEND: + m = imsg->data; + s = table_lookup(incoming, incoming_sz, m->queue_id); + if (s == NULL) + fatalx("queue: bogus append"); - if (IS_MAILBOX(m->recipient) || IS_EXT(m->recipient)) - m->type = T_MDA_MESSAGE; - else - m->type = T_MTA_MESSAGE; + 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; - /* Write to disk */ - if (m->flags & F_MESSAGE_ENQUEUED) - ret = enqueue_record_envelope(m); - else - ret = queue_record_incoming_envelope(m); + 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 (ret == 0) { - ss.code = 421; - imsg_compose_event(env->sc_ievs[PROC_SMTP], - IMSG_QUEUE_TEMPFAIL, 0, 0, -1, &ss, - sizeof ss); - } - return; + 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; - 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); + default: + fatalx("queue: bad r_action"); + } + 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; } } - 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) { + if (iev->proc == PROC_MDA) { switch (imsg->hdr.type) { - 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); + case IMSG_BATCH_UPDATE: + update = imsg->data; + queue_update(Q_LOCAL, imsg->hdr.peerid, update->id, + update->arg); return; - case IMSG_QUEUE_MESSAGE_UPDATE: case IMSG_BATCH_DONE: - queue_pass_to_runner(env, iev, imsg); + queue_done(Q_LOCAL, imsg->hdr.peerid); return; + } } - if (iev->proc == PROC_MDA) { + if (iev->proc == PROC_MTA) { switch (imsg->hdr.type) { - case IMSG_QUEUE_MESSAGE_UPDATE: - case IMSG_MDA_SESS_NEW: - queue_pass_to_runner(env, iev, imsg); + 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); return; } } @@ -193,25 +349,59 @@ 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: - case IMSG_QUEUE_PAUSE_OUTGOING: + 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_RESUME_LOCAL: - case IMSG_QUEUE_RESUME_OUTGOING: + 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_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: - queue_pass_to_runner(env, iev, imsg); + 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); 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; } } @@ -219,12 +409,169 @@ 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->retry = 0; + batch->content = s->content; + strlcpy(batch->sortkey, sortkey, batch_sz - sizeof *batch); + SLIST_INSERT_HEAD(&s->batches[rq], batch, entry); + } + + return batch; +} + void -queue_pass_to_runner(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) +queue_destroy(struct incoming *s) { - 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); + 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); } void @@ -243,26 +590,16 @@ queue_sig_handler(int sig, short event, void *p) void queue_shutdown(void) { - log_info("queue handler exiting"); + log_info("queue 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; @@ -272,8 +609,7 @@ queue(struct smtpd *env) { PROC_SMTP, imsg_dispatch }, { PROC_MDA, imsg_dispatch }, { PROC_MTA, imsg_dispatch }, - { PROC_LKA, imsg_dispatch }, - { PROC_RUNNER, imsg_dispatch } + { PROC_LKA, imsg_dispatch } }; switch (pid = fork()) { @@ -302,9 +638,38 @@ 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); @@ -312,64 +677,759 @@ 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; + /* - * 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. + * Sort actions into batches. */ - fdlimit(1.0); - if ((env->sc_maxconn = availdesc() / 4) < 1) - fatalx("runner: fd starvation"); + for (;;) { + if (queue_be_getnext(&a) < 0) + fatal("queue: backend error"); + if (a.action_id == 0) + break; + auxsplit(&aux, a.aux); - config_pipes(env, peers, nitems(peers)); - config_peers(env, peers, nitems(peers)); + /* + * 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)); - queue_purge(PATH_INCOMING); - queue_purge(PATH_ENQUEUE); + 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; + } + } - queue_setup_events(env); - event_dispatch(); - queue_shutdown(); + 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); - return (0); + 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 + 1); + 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. + */ + 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); } -struct batch * -batch_by_id(struct smtpd *env, u_int64_t id) +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 batch lookup; + 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); + } - lookup.id = id; - return SPLAY_FIND(batchtree, &env->batch_queue, &lookup); + 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_purge(char *queuepath) +queue_update(int rq, int i, u_int64_t action_id, char *new_status) { - char path[MAXPATHLEN]; - struct qwalk *q; + 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"); - q = qwalk_new(queuepath); + 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); - while (qwalk(q, path)) - queue_delete_layout_message(queuepath, basename(path)); + 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; + } - qwalk_close(q); + 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_submit_envelope(struct smtpd *env, struct message *message) +queue_done(int rq, int i) { - imsg_compose_event(env->sc_ievs[PROC_QUEUE], - IMSG_QUEUE_SUBMIT_ENVELOPE, 0, 0, -1, - message, sizeof(struct message)); + 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); } +/* + * Insert batch into runqueue in retry time order. + */ void -queue_commit_envelopes(struct smtpd *env, struct message *message) +queue_schedule(int rq, struct batch *batch) { - imsg_compose_event(env->sc_ievs[PROC_QUEUE], - IMSG_QUEUE_COMMIT_ENVELOPES, 0, 0, -1, - message, sizeof(struct message)); + struct batch *b, *prev; + + prev = NULL; + + 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; + } + + if (b == NULL) { + if (prev) + SLIST_INSERT_AFTER(prev, batch, entry); + else + SLIST_INSERT_HEAD(&runqs[rq].head, batch, entry); + } +} + +void +queue_sleep(int rq) +{ + struct timeval tv; + struct batch *next; + time_t now; + + evtimer_del(&runqs[rq].ev); + + 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); +} + +/* + * 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) +{ + struct timeval tv; + + 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; + + free(content->ev); + content->ev = NULL; + + 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); +} + +void +queue_bounce_init(int i, int sock) +{ + 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); +} + +void +queue_bounce_event(int fd, short event, void *p) +{ + 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); } diff --git a/usr.sbin/smtpd/queue_backend.c b/usr.sbin/smtpd/queue_backend.c new file mode 100644 index 00000000000..f07481e0ec2 --- /dev/null +++ b/usr.sbin/smtpd/queue_backend.c @@ -0,0 +1,330 @@ +/* $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 new file mode 100644 index 00000000000..6a74e2e9119 --- /dev/null +++ b/usr.sbin/smtpd/queue_backend.h @@ -0,0 +1,44 @@ +/* $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 deleted file mode 100644 index 2ae6486af56..00000000000 --- a/usr.sbin/smtpd/queue_shared.c +++ /dev/null @@ -1,817 +0,0 @@ -/* $OpenBSD: queue_shared.c,v 1.27 2009/12/14 18:16:01 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 deleted file mode 100644 index 40a0f912a1f..00000000000 --- a/usr.sbin/smtpd/runner.c +++ /dev/null @@ -1,889 +0,0 @@ -/* $OpenBSD: runner.c,v 1.87 2010/05/19 20:57:10 gilles 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 4e9a8eba531..4269ccdc6e9 100644 --- a/usr.sbin/smtpd/smtp.c +++ b/usr.sbin/smtpd/smtp.c @@ -1,9 +1,9 @@ -/* $OpenBSD: smtp.c,v 1.71 2010/05/19 20:57:10 gilles Exp $ */ +/* $OpenBSD: smtp.c,v 1.72 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> - * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009-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 @@ -39,6 +39,7 @@ #include <unistd.h> #include "smtpd.h" +#include "queue_backend.h" void smtp_imsg(struct smtpd *, struct imsgev *, struct imsg *); __dead void smtp_shutdown(void); @@ -46,21 +47,22 @@ 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_int64_t); +struct session *session_lookup(struct smtpd *, u_int32_t); + +u_int32_t smtp_id; 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) { @@ -81,82 +83,56 @@ smtp_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_MFA) { switch (imsg->hdr.type) { - case IMSG_MFA_RCPT: case IMSG_MFA_MAIL: - log_debug("smtp: got imsg_mfa_mail/rcpt"); - ss = imsg->data; - s = session_lookup(env, ss->id); + case IMSG_MFA_RCPT: + s = session_lookup(env, imsg->hdr.peerid); if (s == NULL) return; - session_pickup(s, ss); + memcpy(&status, imsg->data, sizeof status); + s->s_msg.status |= status; + session_pickup(s); return; } } if (iev->proc == PROC_QUEUE) { - ss = imsg->data; - switch (imsg->hdr.type) { - case IMSG_QUEUE_CREATE_MESSAGE: - log_debug("smtp: imsg_queue_create_message returned"); - s = session_lookup(env, ss->id); + case IMSG_QUEUE_CREATE: + s = session_lookup(env, imsg->hdr.peerid); if (s == NULL) return; - strlcpy(s->s_msg.message_id, ss->u.msgid, - sizeof s->s_msg.message_id); - session_pickup(s, ss); + 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); return; - case IMSG_QUEUE_MESSAGE_FILE: - log_debug("smtp: imsg_queue_message_file returned"); - s = session_lookup(env, ss->id); + case IMSG_QUEUE_OPEN: + s = session_lookup(env, imsg->hdr.peerid); if (s == NULL) { close(imsg->fd); return; } - 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"); + s->datafp = fdopen(imsg->fd, "a"); + if (s->datafp == NULL) + fatal("smtp: fdopen"); + session_pickup(s); return; - case IMSG_QUEUE_COMMIT_ENVELOPES: - log_debug("smtp: got imsg_queue_commit_envelopes"); - s = session_lookup(env, ss->id); + case IMSG_QUEUE_CLOSE: + s = session_lookup(env, imsg->hdr.peerid); if (s == NULL) return; - 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); + memcpy(&status, imsg->data, sizeof status); + s->s_msg.status |= status; + session_pickup(s); return; case IMSG_SMTP_ENQUEUE: - imsg_compose_event(iev, IMSG_SMTP_ENQUEUE, 0, 0, - smtp_enqueue(env, NULL), imsg->data, - sizeof(struct message)); + fd = smtp_enqueue(env, getuid()); + imsg_compose_event(iev, IMSG_SMTP_ENQUEUE, imsg->hdr.peerid, 0, fd, NULL, 0); return; } } @@ -249,7 +225,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, NULL); + session_pickup(s); return; case IMSG_CTL_VERBOSE: @@ -261,9 +237,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, smtp_enqueue(env, imsg->data), - NULL, 0); + imsg->hdr.peerid, 0, fd, NULL, 0); return; case IMSG_SMTP_PAUSE: @@ -436,7 +412,7 @@ smtp_resume(struct smtpd *env) } int -smtp_enqueue(struct smtpd *env, uid_t *euid) +smtp_enqueue(struct smtpd *env, uid_t uid) { static struct listener local, *l; static struct sockaddr_storage sa; @@ -476,16 +452,8 @@ smtp_enqueue(struct smtpd *env, uid_t *euid) 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)); @@ -515,7 +483,6 @@ 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) { @@ -535,7 +502,7 @@ smtp_new(struct listener *l) if ((s = calloc(1, sizeof(*s))) == NULL) fatal(NULL); - s->s_id = generate_uid(); + s->s_id = smtp_id++; s->s_env = env; s->s_l = l; strlcpy(s->s_msg.tag, l->tag, sizeof(s->s_msg.tag)); @@ -551,7 +518,7 @@ smtp_new(struct listener *l) * Helper function for handling IMSG replies. */ struct session * -session_lookup(struct smtpd *env, u_int64_t id) +session_lookup(struct smtpd *env, u_int32_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 6d6dd58a9a0..ea2b386e319 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.132 2010/04/24 19:16:11 chl Exp $ */ +/* $OpenBSD: smtp_session.c,v 1.133 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -41,6 +41,7 @@ #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 *); @@ -424,8 +425,6 @@ 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; @@ -450,6 +449,8 @@ 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, @@ -487,8 +488,8 @@ session_rfc5321_data_handler(struct session *s, char *args) s->s_state = S_DATA_QUEUE; - session_imsg(s, PROC_QUEUE, IMSG_QUEUE_MESSAGE_FILE, 0, 0, -1, - &s->s_msg, sizeof(s->s_msg)); + session_imsg(s, PROC_QUEUE, IMSG_QUEUE_OPEN, s->s_id, 0, -1, + &s->queue_id, sizeof s->queue_id); return 1; } @@ -599,13 +600,12 @@ rfc5321: } void -session_pickup(struct session *s, struct submit_status *ss) +session_pickup(struct session *s) { if (s == NULL) fatal("session_pickup: desynchronized"); - if ((ss != NULL && ss->code == 421) || - (s->s_msg.status & S_MESSAGE_TEMPFAILURE)) { + if (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,48 +635,36 @@ session_pickup(struct session *s, struct submit_status *ss) break; case S_MAIL_MFA: - if (ss == NULL) - fatalx("bad ss at S_MAIL_MFA"); - if (ss->code != 250) { + if (s->s_msg.status & S_MESSAGE_PERMFAILURE) { s->s_state = S_HELO; - session_respond(s, "%d Sender rejected", ss->code); + session_respond(s, "530 Sender rejected"); return; } - s->s_state = S_MAIL_QUEUE; - 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)); + session_imsg(s, PROC_QUEUE, IMSG_QUEUE_CREATE, s->s_id, 0, -1, + NULL, 0); break; case S_MAIL_QUEUE: - if (ss == NULL) - fatalx("bad ss at S_MAIL_QUEUE"); s->s_state = S_MAIL; - session_respond(s, "%d 2.1.0 Sender ok", ss->code); + session_respond(s, "250 2.1.0 Sender ok"); break; case S_RCPT_MFA: - if (ss == NULL) - fatalx("bad ss at S_RCPT_MFA"); /* recipient was not accepted */ - if (ss->code != 250) { + if (s->s_msg.status & S_MESSAGE_PERMFAILURE) { /* 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, "%d 5.0.0 Recipient rejected: %s@%s", ss->code, + session_respond(s, "530 5.0.0 Recipient rejected: %s@%s", s->s_msg.session_rcpt.user, s->s_msg.session_rcpt.domain); return; } - - s->s_state = S_RCPT; s->rcptcount++; - s->s_msg.recipient = ss->u.path; - - session_respond(s, "%d 2.0.0 Recipient ok", ss->code); + s->s_state = S_RCPT; + session_respond(s, "250 2.0.0 Recipient ok"); break; case S_DATA_QUEUE: @@ -688,7 +676,7 @@ session_pickup(struct session *s, struct submit_status *ss) 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" : "", - s->s_msg.message_id); + queue_be_decode(s->content_id)); if (s->s_flags & F_SECURE) { fprintf(s->datafp, "\n\t(version=%s cipher=%s bits=%d)", @@ -707,23 +695,24 @@ session_pickup(struct session *s, struct submit_status *ss) break; case S_DONE: - 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)); - + 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)); + } s->s_state = S_HELO; - s->s_msg.message_id[0] = '\0'; - s->s_msg.message_uid[0] = '\0'; + s->content_id = 0; bzero(&s->s_nresp, sizeof(s->s_nresp)); break; @@ -743,7 +732,7 @@ session_init(struct listener *l, struct session *s) } session_bufferevent_new(s); - session_pickup(s, NULL); + session_pickup(s); } void @@ -827,7 +816,7 @@ session_read_data(struct session *s, char *line) if (strcmp(line, ".") == 0) { s->s_datalen = ftell(s->datafp); - if (! safe_fclose(s->datafp)) + if (fclose(s->datafp) == EOF) s->s_msg.status |= S_MESSAGE_TEMPFAILURE; s->datafp = NULL; @@ -839,8 +828,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_COMMIT_MESSAGE, - 0, 0, -1, &s->s_msg, sizeof(s->s_msg)); + session_imsg(s, PROC_QUEUE, IMSG_QUEUE_CLOSE, + s->s_id, 0, -1, &s->queue_id, sizeof s->queue_id); s->s_state = S_DONE; } @@ -978,10 +967,12 @@ session_destroy(struct session *s) if (s->datafp != NULL) fclose(s->datafp); - if (s->s_msg.message_id[0] != '\0' && s->s_state != S_DONE) + if (s->content_id && s->s_state != S_DONE) { + log_debug("%s: deleting queue session", __func__); imsg_compose_event(s->s_env->sc_ievs[PROC_QUEUE], - IMSG_QUEUE_REMOVE_MESSAGE, 0, 0, -1, &s->s_msg, - sizeof(s->s_msg)); + IMSG_QUEUE_DELETE, 0, 0, -1, &s->queue_id, + sizeof s->queue_id); + } ssl_session_destroy(s); @@ -1001,7 +992,6 @@ session_destroy(struct session *s) } SPLAY_REMOVE(sessiontree, &s->s_env->sc_sessions, s); - bzero(s, sizeof(*s)); free(s); } @@ -1099,9 +1089,10 @@ session_respond(struct session *s, char *fmt, ...) switch (EVBUFFER_DATA(EVBUFFER_OUTPUT(s->s_bev))[n]) { case '5': case '4': - 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, + 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, 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 0bf99c0e48b..3243b7d83b5 100644 --- a/usr.sbin/smtpd/smtpctl.8 +++ b/usr.sbin/smtpd/smtpctl.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: smtpctl.8,v 1.13 2010/02/23 22:09:55 stevesk Exp $ +.\" $OpenBSD: smtpctl.8,v 1.14 2010/05/31 23:38:56 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: February 23 2010 $ +.Dd $Mdocdate: May 31 2010 $ .Dt SMTPCTL 8 .Os .Sh NAME @@ -43,20 +43,19 @@ Temporarily stop deliveries to local users. .It Cm pause outgoing Temporarily stop relaying and deliveries to remote users. -.It Cm remove Ar message-uid | message-id -Removes a single message, or all messages with the same message ID. +.It Cm remove Ar message-id | Cm all +Removes messages with the same message ID, or all messages. .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-uid | message-id -Marks a single message, or all messages with the same message ID, +.It Cm schedule Ar message-id | Cm all +Marks messages with the same message ID, or all messages, as ready for immediate delivery. -.It Cm show queue -Displays information concerning envelopes -that are currently in a queue. +.It Cm show queue Op Ic raw +Displays undelivered messages. .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 c04cd43c9b0..6a05e6ea61f 100644 --- a/usr.sbin/smtpd/smtpctl.c +++ b/usr.sbin/smtpd/smtpctl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpctl.c,v 1.47 2010/04/21 18:54:43 jacekm Exp $ */ +/* $OpenBSD: smtpctl.c,v 1.48 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -43,9 +43,11 @@ #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; @@ -73,6 +75,7 @@ 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; @@ -83,7 +86,7 @@ main(int argc, char *argv[]) else if (strcmp(__progname, "mailq") == 0) { if (geteuid()) errx(1, "need root privileges"); - show_queue(PATH_QUEUE, 0); + show_queue(0); return 0; } else if (strcmp(__progname, "smtpctl") == 0) { /* check for root privileges */ @@ -96,10 +99,12 @@ main(int argc, char *argv[]) /* handle "disconnected" commands */ switch (res->action) { case SHOW_QUEUE: - show_queue(PATH_QUEUE, 0); + show_queue(0); + break; + case SHOW_QUEUE_RAW: + show_queue(1); break; case SHOW_RUNQUEUE: - show_queue(PATH_RUNQUEUE, 0); break; default: goto connected; @@ -146,7 +151,7 @@ connected: imsg_compose(ibuf, IMSG_QUEUE_PAUSE_LOCAL, 0, 0, -1, NULL, 0); break; case PAUSE_MTA: - imsg_compose(ibuf, IMSG_QUEUE_PAUSE_OUTGOING, 0, 0, -1, NULL, 0); + imsg_compose(ibuf, IMSG_QUEUE_PAUSE_RELAY, 0, 0, -1, NULL, 0); break; case PAUSE_SMTP: imsg_compose(ibuf, IMSG_SMTP_PAUSE, 0, 0, -1, NULL, 0); @@ -155,7 +160,7 @@ connected: imsg_compose(ibuf, IMSG_QUEUE_RESUME_LOCAL, 0, 0, -1, NULL, 0); break; case RESUME_MTA: - imsg_compose(ibuf, IMSG_QUEUE_RESUME_OUTGOING, 0, 0, -1, NULL, 0); + imsg_compose(ibuf, IMSG_QUEUE_RESUME_RELAY, 0, 0, -1, NULL, 0); break; case RESUME_SMTP: imsg_compose(ibuf, IMSG_SMTP_RESUME, 0, 0, -1, NULL, 0); @@ -163,24 +168,26 @@ connected: case SHOW_STATS: imsg_compose(ibuf, IMSG_STATS, 0, 0, -1, NULL, 0); break; - 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)); + 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); break; - } - 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)); + 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); break; - } case MONITOR: /* XXX */ break; @@ -264,6 +271,75 @@ 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("%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) { @@ -288,12 +364,8 @@ show_stats_output(struct imsg *imsg) printf("parent.uptime=%d\n", time(NULL) - stats->parent.start); - 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("queue.inserts=%zd\n", stats->queue.inserts); + printf("queue.length=%zd\n", stats->queue.length); 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 d0d4413f0e9..51d0af9c31f 100644 --- a/usr.sbin/smtpd/smtpctl/Makefile +++ b/usr.sbin/smtpd/smtpctl/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.12 2010/05/26 16:44:32 nicm Exp $ +# $OpenBSD: Makefile,v 1.13 2010/05/31 23:38:56 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_shared.c util.c client.c +SRCS= smtpctl.c parser.c log.c enqueue.c queue_backend.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 b0f6a2ec08b..1119dabc9d4 100644 --- a/usr.sbin/smtpd/smtpd.c +++ b/usr.sbin/smtpd/smtpd.c @@ -1,9 +1,9 @@ -/* $OpenBSD: smtpd.c,v 1.108 2010/05/31 22:25:26 chl Exp $ */ +/* $OpenBSD: smtpd.c,v 1.109 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> - * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009-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 @@ -28,6 +28,7 @@ #include <sys/uio.h> #include <sys/mman.h> +#include <dirent.h> #include <err.h> #include <errno.h> #include <event.h> @@ -45,6 +46,7 @@ #include <unistd.h> #include "smtpd.h" +#include "queue_backend.h" void parent_imsg(struct smtpd *, struct imsgev *, struct imsg *); __dead void usage(void); @@ -57,10 +59,8 @@ void parent_sig_handler(int, short, void *); void forkmda(struct smtpd *, struct imsgev *, u_int32_t, struct deliver *); -int parent_enqueue_offline(struct smtpd *, char *); +void parent_enqueue_offline(struct smtpd *); 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,17 +116,6 @@ 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: @@ -180,6 +169,7 @@ 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"); } @@ -384,12 +374,10 @@ parent_sig_handler(int sig, short event, void *p) case CHILD_ENQUEUE_OFFLINE: if (fail) log_warnx("couldn't enqueue offline " - "message; smtpctl %s", cause); + "message; child %s", cause); else log_debug("offline message enqueued"); - imsg_compose_event(env->sc_ievs[PROC_QUEUE], - IMSG_PARENT_ENQUEUE_OFFLINE, 0, 0, -1, - NULL, 0); + parent_enqueue_offline(env); break; default: @@ -483,12 +471,11 @@ 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); - if (!setup_spool(env.sc_pw->pw_uid, 0)) - errx(1, "invalid directory permissions"); - + setup_spool(env.sc_pw); + log_init(debug); log_verbose(verbose); @@ -531,6 +518,8 @@ main(int argc, char *argv[]) bzero(&tv, sizeof(tv)); evtimer_add(&env.sc_ev, &tv); + parent_enqueue_offline(&env); + event_dispatch(); return (0); @@ -563,7 +552,6 @@ 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); @@ -574,7 +562,6 @@ 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); @@ -583,7 +570,6 @@ 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]"); @@ -630,136 +616,6 @@ 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) { @@ -802,8 +658,11 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, errno = 0; pw = getpwnam(deliver->user); if (pw == NULL) { - n = snprintf(ebuf, sizeof ebuf, "getpwnam: %s", - errno ? strerror(errno) : "no such user"); + if (errno) + n = snprintf(ebuf, sizeof ebuf, "getpwnam: %s", + strerror(errno)); + else + n = snprintf(ebuf, sizeof ebuf, "user not found"); imsg_compose_event(iev, IMSG_MDA_DONE, id, 0, -1, ebuf, n + 1); return; } @@ -886,7 +745,8 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, /* avoid hangs by setting 5m timeout */ alarm(300); - if (deliver->mode == A_EXT) { + /* external mda */ + if (deliver->mode == 'P') { char *environ_new[2]; environ_new[0] = "PATH=" _PATH_DEFPATH; @@ -897,7 +757,8 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, error("execle"); } - if (deliver->mode == A_MAILDIR) { + /* internal mda: maildir */ + if (deliver->mode == 'D') { char tmp[PATH_MAX], new[PATH_MAX]; int ch, fd; FILE *fp; @@ -927,10 +788,6 @@ 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); @@ -940,7 +797,8 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, } #undef error2 - if (deliver->mode == A_FILENAME) { + /* internal mda: file */ + if (deliver->mode == 'F') { struct stat sb; time_t now; size_t len; @@ -975,10 +833,6 @@ 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); @@ -989,114 +843,103 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, #undef error #undef error2 -int -parent_enqueue_offline(struct smtpd *env, char *runner_path) +void +parent_enqueue_offline(struct smtpd *env) { - char path[MAXPATHLEN]; - struct passwd *pw; + char path[MAXPATHLEN], *line, charstr[2], *envp[2], *tmp; struct stat sb; + struct dirent *de; + struct passwd *pw; + DIR *dir; pid_t pid; + arglist args; + int fd, line_sz; - log_debug("parent_enqueue_offline: path %s", runner_path); - - if (! bsnprintf(path, sizeof(path), "%s%s", PATH_SPOOL, runner_path)) - fatalx("parent_enqueue_offline: filename too long"); + fd = -1; + pw = NULL; - if (! path_starts_with(path, PATH_SPOOL PATH_OFFLINE)) - fatalx("parent_enqueue_offline: path outside offline dir"); + dir = opendir(PATH_SPOOL PATH_OFFLINE); + if (dir == NULL) + fatal("opendir"); - if (lstat(path, &sb) == -1) { - if (errno == ENOENT) { - log_warn("parent_enqueue_offline: %s", path); - return (0); - } - fatal("parent_enqueue_offline: lstat"); - } - - if (chflags(path, 0) == -1) { - if (errno == ENOENT) { - log_warn("parent_enqueue_offline: %s", path); - return (0); + 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; } - fatal("parent_enqueue_offline: chflags"); - } - - 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 (! 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); + break; } - if ((pid = fork()) == -1) - fatal("parent_enqueue_offline: fork"); - - 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); - } + closedir(dir); - if ((fp = fopen(path, "r")) == NULL) { - unlink(path); - _exit(1); - } - unlink(path); + if (de == NULL) + return; - if (chdir(pw->pw_dir) == -1 && chdir("/") == -1) - _exit(1); + pid = fork(); + if (pid < 0) + fatal("fork"); - if (setsid() == -1 || - signal(SIGPIPE, SIG_DFL) == SIG_ERR || - dup2(fileno(fp), STDIN_FILENO) == -1) - _exit(1); + if (pid) { + child_add(env, pid, CHILD_ENQUEUE_OFFLINE, -1); + return; + } - if ((p = fgetln(fp, &len)) == NULL) - _exit(1); + 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 (p[len - 1] != '\n') + line = malloc(1); + if (line == NULL) + _exit(1); + line_sz = 1; + line[0] = '\0'; + do { + if (read(STDIN_FILENO, charstr, 1) <= 0) _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) + charstr[1] = '\0'; + line = realloc(line, ++line_sz); + if (line == NULL) _exit(1); - - envp[0] = "PATH=" _PATH_DEFPATH; - envp[1] = (char *)NULL; - environ = envp; - - execvp(PATH_SMTPCTL, args.list); - _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); } - - child_add(env, pid, CHILD_ENQUEUE_OFFLINE, -1); - - return (1); + envp[0] = "PATH=" _PATH_DEFPATH; + envp[1] = (char *)NULL; + environ = envp; + execvp(PATH_SMTPCTL, args.list); + _exit(1); } int @@ -1131,18 +974,6 @@ 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) @@ -1188,4 +1019,25 @@ 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 6593a90cc9e..926739b26c1 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.186 2010/05/31 22:25:26 chl Exp $ */ +/* $OpenBSD: smtpd.h,v 1.187 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -23,15 +23,11 @@ #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 9 +#define PROC_COUNT 8 #define MAX_NAME_SIZE 64 #define MAX_HOPS_COUNT 100 @@ -40,40 +36,21 @@ #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_QUEUE_INTERVAL (15 * 60) -#define SMTPD_QUEUE_MAXINTERVAL (4 * 60 * 60) -#define SMTPD_QUEUE_EXPIRY (4 * 24 * 60 * 60) +#define SMTPD_EXPIRE (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_SPOOL "/usr/obj/smtpd" #define PATH_OFFLINE "/offline" -#define PATH_BOUNCE "/bounce" /* number of MX records to lookup */ #define MAX_MX_COUNT 10 @@ -135,38 +112,31 @@ enum imsg_type { IMSG_CONF_RELOAD, IMSG_LKA_MAIL, IMSG_LKA_RCPT, - IMSG_LKA_SECRET, IMSG_LKA_RULEMATCH, - IMSG_MDA_SESS_NEW, + IMSG_LKA_SECRET, IMSG_MDA_DONE, - IMSG_MFA_RCPT, IMSG_MFA_MAIL, + IMSG_MFA_RCPT, - 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_CREATE, + IMSG_QUEUE_APPEND, + IMSG_QUEUE_OPEN, + IMSG_QUEUE_CLOSE, + IMSG_QUEUE_DELETE, IMSG_QUEUE_STATS, IMSG_QUEUE_PAUSE_LOCAL, - IMSG_QUEUE_PAUSE_OUTGOING, + IMSG_QUEUE_PAUSE_RELAY, IMSG_QUEUE_RESUME_LOCAL, - IMSG_QUEUE_RESUME_OUTGOING, - - IMSG_QUEUE_REMOVE_SUBMISSION, - IMSG_QUEUE_MESSAGE_UPDATE, - IMSG_QUEUE_MESSAGE_FD, - IMSG_QUEUE_MESSAGE_FILE, + IMSG_QUEUE_RESUME_RELAY, 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, @@ -225,8 +195,7 @@ enum smtp_proc_type { PROC_QUEUE, PROC_MDA, PROC_MTA, - PROC_CONTROL, - PROC_RUNNER, + PROC_CONTROL } smtpd_process; struct peer { @@ -402,45 +371,92 @@ struct expandnode { RB_HEAD(expandtree, expandnode); -enum message_type { - T_MDA_MESSAGE = 0x1, - T_MTA_MESSAGE = 0x2, - T_BOUNCE_MESSAGE = 0x4 +struct action { + SLIST_ENTRY(action) entry; + u_int64_t id; + char arg[]; }; -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 +struct content { + SLIST_HEAD(,action) actions; + u_int64_t id; + u_int32_t ref; + struct event *ev; +}; + +#define NO_RETRY_EXPIRED 0 + +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_flags { - 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 + F_MESSAGE_AUTHENTICATED = 0x1 +}; + +enum message_status { + S_MESSAGE_PERMFAILURE = 0x1, + S_MESSAGE_TEMPFAILURE = 0x2 }; 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]; - char message_id[MAX_ID_SIZE]; - char message_uid[MAX_ID_SIZE]; + u_int32_t id; /* smtp session id */ + u_int32_t session_id; /* smtp session id */ + u_int32_t queue_id; char session_helo[MAXHOSTNAMELEN]; char session_hostname[MAXHOSTNAMELEN]; @@ -458,23 +474,6 @@ 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 +558,7 @@ enum session_flags { struct session { SPLAY_ENTRY(session) s_nodes; - u_int64_t s_id; + u_int32_t s_id; enum session_flags s_flags; enum session_state s_state; @@ -585,6 +584,9 @@ struct session { FILE *datafp; int mboxfd; int messagefd; + + u_int32_t queue_id; + u_int64_t content_id; }; struct smtpd { @@ -600,7 +602,6 @@ 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] @@ -618,7 +619,6 @@ 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; @@ -632,14 +632,8 @@ struct s_parent { }; struct s_queue { - size_t inserts_local; - size_t inserts_remote; -}; - -struct s_runner { - size_t active; - size_t bounces_active; - size_t bounces; + size_t length; + size_t inserts; }; struct s_session { @@ -677,25 +671,12 @@ 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; @@ -706,7 +687,6 @@ 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; @@ -715,7 +695,7 @@ struct submit_status { }; struct forward_req { - u_int64_t id; + u_int32_t id; u_int8_t status; char pw_name[MAXLOGNAME]; }; @@ -730,25 +710,23 @@ 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 { @@ -762,7 +740,7 @@ enum lkasession_flags { struct lkasession { SPLAY_ENTRY(lkasession) nodes; - u_int64_t id; + u_int32_t id; struct path path; struct deliverylist deliverylist; @@ -780,7 +758,6 @@ enum mta_state { MTA_INVALID_STATE, MTA_INIT, MTA_SECRET, - MTA_DATA, MTA_MX, MTA_CONNECT, MTA_PTR, @@ -788,12 +765,6 @@ 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; @@ -801,25 +772,31 @@ 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_int64_t id; + u_int32_t id; struct smtpd *env; enum mta_state state; - char *host; - int port; - int flags; - TAILQ_HEAD(,message) recipients; + time_t birth; + u_int64_t content_id; + struct aux aux; + char *auxraw; + TAILQ_HEAD(,recipient) 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]; @@ -905,53 +882,9 @@ 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); @@ -974,11 +907,6 @@ 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 *); @@ -986,7 +914,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 *, struct submit_status *); +void session_pickup(struct session *); void session_destroy(struct session *); void session_respond(struct session *, char *, ...) __attribute__ ((format (printf, 2, 3))); @@ -1041,21 +969,20 @@ 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 message_set_errormsg(struct message *, char *, ...); -char *message_get_errormsg(struct message *); -void sa_set_port(struct sockaddr *, int); +void sa_set_port(struct sockaddr *, char *); 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 663f189ed9f..4d25c8ee59c 100644 --- a/usr.sbin/smtpd/smtpd/Makefile +++ b/usr.sbin/smtpd/smtpd/Makefile @@ -1,12 +1,11 @@ -# $OpenBSD: Makefile,v 1.17 2010/05/26 16:44:32 nicm Exp $ +# $OpenBSD: Makefile,v 1.18 2010/05/31 23:38:56 jacekm Exp $ PROG= smtpd -SRCS= aliases.c authenticate.c bounce.c client.c \ +SRCS= aliases.c authenticate.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_shared.c ruleset.c \ - runner.c smtp.c smtp_session.c smtpd.c ssl.c \ - ssl_privsep.c util.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 MAN= smtpd.8 smtpd.conf.5 BINDIR= /usr/sbin diff --git a/usr.sbin/smtpd/ssl.c b/usr.sbin/smtpd/ssl.c index edd89acf730..b4b173644ee 100644 --- a/usr.sbin/smtpd/ssl.c +++ b/usr.sbin/smtpd/ssl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssl.c,v 1.25 2010/05/26 13:56:08 nicm Exp $ */ +/* $OpenBSD: ssl.c,v 1.26 2010/05/31 23:38:56 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, NULL); + session_pickup(s); return; retry: diff --git a/usr.sbin/smtpd/util.c b/usr.sbin/smtpd/util.c index b19020987a6..140ddfd329d 100644 --- a/usr.sbin/smtpd/util.c +++ b/usr.sbin/smtpd/util.c @@ -1,9 +1,9 @@ -/* $OpenBSD: util.c,v 1.32 2009/12/23 17:16:03 jacekm Exp $ */ +/* $OpenBSD: util.c,v 1.33 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2000,2001 Markus Friedl. All rights reserved. * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> - * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009-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 @@ -58,28 +58,6 @@ 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) { @@ -200,53 +178,6 @@ 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) { @@ -371,49 +302,22 @@ lowercase(char *buf, char *s, size_t len) } void -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) +sa_set_port(struct sockaddr *sa, char *port) { - char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; + char hbuf[NI_MAXHOST]; 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|AI_NUMERICSERV; - - snprintf(sbuf, sizeof(sbuf), "%d", port); + hints.ai_flags = AI_NUMERICHOST; - error = getaddrinfo(hbuf, sbuf, &hints, &res); + error = getaddrinfo(hbuf, port, &hints, &res); if (error) fatalx("sa_set_port: getaddrinfo failed"); @@ -426,7 +330,7 @@ path_dup(struct path *path) { struct path *pathp; - pathp = calloc(sizeof(struct path), 1); + pathp = calloc(1, sizeof(struct path)); if (pathp == NULL) fatal("calloc"); @@ -509,7 +413,8 @@ session_socket_no_linger(int fd) int session_socket_error(int fd) { - int error, len; + socklen_t len; + int error; len = sizeof(error); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) @@ -517,3 +422,104 @@ 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; +} |