diff options
Diffstat (limited to 'usr.sbin/smtpd')
-rw-r--r-- | usr.sbin/smtpd/aliases.c | 17 | ||||
-rw-r--r-- | usr.sbin/smtpd/client.c | 2 | ||||
-rw-r--r-- | usr.sbin/smtpd/control.c | 82 | ||||
-rw-r--r-- | usr.sbin/smtpd/enqueue.c | 4 | ||||
-rw-r--r-- | usr.sbin/smtpd/expand.c | 23 | ||||
-rw-r--r-- | usr.sbin/smtpd/lka.c | 363 | ||||
-rw-r--r-- | usr.sbin/smtpd/log.c | 4 | ||||
-rw-r--r-- | usr.sbin/smtpd/map.c | 24 | ||||
-rw-r--r-- | usr.sbin/smtpd/mda.c | 172 | ||||
-rw-r--r-- | usr.sbin/smtpd/mfa.c | 96 | ||||
-rw-r--r-- | usr.sbin/smtpd/mta.c | 320 | ||||
-rw-r--r-- | usr.sbin/smtpd/parse.y | 72 | ||||
-rw-r--r-- | usr.sbin/smtpd/parser.c | 19 | ||||
-rw-r--r-- | usr.sbin/smtpd/parser.h | 3 | ||||
-rw-r--r-- | usr.sbin/smtpd/queue.c | 1494 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp.c | 115 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp_session.c | 105 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpctl.8 | 17 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpctl.c | 129 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpctl/Makefile | 4 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.c | 403 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.h | 320 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd/Makefile | 9 | ||||
-rw-r--r-- | usr.sbin/smtpd/ssl.c | 4 | ||||
-rw-r--r-- | usr.sbin/smtpd/util.c | 216 |
25 files changed, 1592 insertions, 2425 deletions
diff --git a/usr.sbin/smtpd/aliases.c b/usr.sbin/smtpd/aliases.c index 9a69d927827..7c0410318dd 100644 --- a/usr.sbin/smtpd/aliases.c +++ b/usr.sbin/smtpd/aliases.c @@ -1,4 +1,4 @@ -/* $OpenBSD: aliases.c,v 1.36 2010/06/01 23:06:23 jacekm Exp $ */ +/* $OpenBSD: aliases.c,v 1.37 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -67,13 +67,20 @@ aliases_exist(struct smtpd *env, objid_t mapid, char *username) } int -aliases_get(struct smtpd *env, objid_t mapid, struct expandtree *tree, char *username) +aliases_get(struct smtpd *env, objid_t mapid, struct expandtree *expandtree, char *username) { + struct map *map; struct map_alias *map_alias; struct expandnode *expnode; + char buf[MAXLOGNAME]; size_t nbaliases; - map_alias = map_lookup(env, mapid, username, K_ALIAS); + map = map_find(env, mapid); + if (map == NULL) + return 0; + + lowercase(buf, username, sizeof(buf)); + map_alias = map_lookup(env, mapid, buf, K_ALIAS); if (map_alias == NULL) return 0; @@ -81,9 +88,9 @@ aliases_get(struct smtpd *env, objid_t mapid, struct expandtree *tree, char *use nbaliases = 0; RB_FOREACH(expnode, expandtree, &map_alias->expandtree) { if (expnode->type == EXPAND_INCLUDE) - nbaliases += aliases_expand_include(tree, expnode->u.filename); + nbaliases += aliases_expand_include(expandtree, expnode->u.filename); else { - expandtree_increment_node(tree, expnode); + expandtree_increment_node(expandtree, expnode); nbaliases++; } } diff --git a/usr.sbin/smtpd/client.c b/usr.sbin/smtpd/client.c index 47adb6e341d..a9b1586cfef 100644 --- a/usr.sbin/smtpd/client.c +++ b/usr.sbin/smtpd/client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: client.c,v 1.31 2010/06/01 23:06:23 jacekm Exp $ */ +/* $OpenBSD: client.c,v 1.32 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> diff --git a/usr.sbin/smtpd/control.c b/usr.sbin/smtpd/control.c index 5215b1a8aa0..d02eb7af37c 100644 --- a/usr.sbin/smtpd/control.c +++ b/usr.sbin/smtpd/control.c @@ -1,4 +1,4 @@ -/* $OpenBSD: control.c,v 1.53 2010/06/02 19:16:53 chl Exp $ */ +/* $OpenBSD: control.c,v 1.54 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -36,7 +36,6 @@ #include <unistd.h> #include "smtpd.h" -#include "queue_backend.h" #define CONTROL_BACKLOG 5 @@ -64,7 +63,8 @@ control_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { struct ctl_conn *c; struct reload *reload; - int error; + struct remove *rem; + struct sched *sched; if (iev->proc == PROC_SMTP) { switch (imsg->hdr.type) { @@ -81,17 +81,23 @@ control_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_QUEUE) { switch (imsg->hdr.type) { case IMSG_QUEUE_SCHEDULE: + sched = imsg->data; + c = control_connbyfd(sched->fd); + if (c == NULL) + return; + imsg_compose_event(&c->iev, + sched->ret ? IMSG_CTL_OK : IMSG_CTL_FAIL, 0, 0, -1, + NULL, 0); + return; + case IMSG_QUEUE_REMOVE: - c = control_connbyfd(imsg->hdr.peerid); + rem = imsg->data; + c = control_connbyfd(rem->fd); if (c == NULL) return; - memcpy(&error, imsg->data, sizeof error); - if (error) - imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 0, 0, - -1, NULL, 0); - else - imsg_compose_event(&c->iev, IMSG_CTL_OK, 0, 0, - -1, NULL, 0); + imsg_compose_event(&c->iev, + rem->ret ? IMSG_CTL_OK : IMSG_CTL_FAIL, 0, 0, + -1, NULL, 0); return; } } @@ -217,6 +223,7 @@ control(struct smtpd *env) config_pipes(env, peers, nitems(peers)); config_peers(env, peers, nitems(peers)); control_listen(env); + if (event_dispatch() < 0) fatal("event_dispatch"); control_shutdown(); @@ -389,14 +396,47 @@ control_dispatch_ext(int fd, short event, void *arg) imsg_compose_event(&c->iev, IMSG_STATS, 0, 0, -1, env->stats, sizeof(struct stats)); break; - case IMSG_QUEUE_SCHEDULE: - case IMSG_QUEUE_REMOVE: - if (euid || IMSG_DATA_SIZE(&imsg) != sizeof(u_int64_t)) + case IMSG_QUEUE_SCHEDULE: { + struct sched *s = imsg.data; + + if (euid) goto badcred; - imsg_compose_event(env->sc_ievs[PROC_QUEUE], - imsg.hdr.type, fd, 0, -1, imsg.data, - sizeof(u_int64_t)); + + if (IMSG_DATA_SIZE(&imsg) != sizeof(*s)) + goto badcred; + + s->fd = fd; + + if (! valid_message_id(s->mid) && ! valid_message_uid(s->mid)) { + imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 0, 0, -1, + NULL, 0); + break; + } + + imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_SCHEDULE, 0, 0, -1, s, sizeof(*s)); break; + } + + case IMSG_QUEUE_REMOVE: { + struct remove *s = imsg.data; + + if (euid) + goto badcred; + + if (IMSG_DATA_SIZE(&imsg) != sizeof(*s)) + goto badcred; + + s->fd = fd; + + if (! valid_message_id(s->mid) && ! valid_message_uid(s->mid)) { + imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 0, 0, -1, + NULL, 0); + break; + } + + imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_REMOVE, 0, 0, -1, s, sizeof(*s)); + break; + } /* case IMSG_CONF_RELOAD: { struct reload r; @@ -463,7 +503,7 @@ control_dispatch_ext(int fd, short event, void *arg) IMSG_QUEUE_PAUSE_LOCAL, 0, 0, -1, NULL, 0); imsg_compose_event(&c->iev, IMSG_CTL_OK, 0, 0, -1, NULL, 0); break; - case IMSG_QUEUE_PAUSE_RELAY: + case IMSG_QUEUE_PAUSE_OUTGOING: if (euid) goto badcred; @@ -474,7 +514,7 @@ control_dispatch_ext(int fd, short event, void *arg) } env->sc_flags |= SMTPD_MTA_PAUSED; imsg_compose_event(env->sc_ievs[PROC_QUEUE], - IMSG_QUEUE_PAUSE_RELAY, 0, 0, -1, NULL, 0); + IMSG_QUEUE_PAUSE_OUTGOING, 0, 0, -1, NULL, 0); imsg_compose_event(&c->iev, IMSG_CTL_OK, 0, 0, -1, NULL, 0); break; case IMSG_SMTP_PAUSE: @@ -505,7 +545,7 @@ control_dispatch_ext(int fd, short event, void *arg) IMSG_QUEUE_RESUME_LOCAL, 0, 0, -1, NULL, 0); imsg_compose_event(&c->iev, IMSG_CTL_OK, 0, 0, -1, NULL, 0); break; - case IMSG_QUEUE_RESUME_RELAY: + case IMSG_QUEUE_RESUME_OUTGOING: if (euid) goto badcred; @@ -516,7 +556,7 @@ control_dispatch_ext(int fd, short event, void *arg) } env->sc_flags &= ~SMTPD_MTA_PAUSED; imsg_compose_event(env->sc_ievs[PROC_QUEUE], - IMSG_QUEUE_RESUME_RELAY, 0, 0, -1, NULL, 0); + IMSG_QUEUE_RESUME_OUTGOING, 0, 0, -1, NULL, 0); imsg_compose_event(&c->iev, IMSG_CTL_OK, 0, 0, -1, NULL, 0); break; case IMSG_SMTP_RESUME: diff --git a/usr.sbin/smtpd/enqueue.c b/usr.sbin/smtpd/enqueue.c index 0c840038d6a..f9887382d87 100644 --- a/usr.sbin/smtpd/enqueue.c +++ b/usr.sbin/smtpd/enqueue.c @@ -1,4 +1,4 @@ -/* $OpenBSD: enqueue.c,v 1.38 2010/08/02 11:49:02 jacekm Exp $ */ +/* $OpenBSD: enqueue.c,v 1.39 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2005 Henning Brauer <henning@bulabula.org> @@ -163,6 +163,7 @@ enqueue(int argc, char *argv[]) case 'x': break; case 'q': + /* XXX: implement "process all now" */ return (0); default: usage(); @@ -188,7 +189,6 @@ enqueue(int argc, char *argv[]) } signal(SIGALRM, sighdlr); - signal(SIGPIPE, SIG_IGN); alarm(300); fp = tmpfile(); diff --git a/usr.sbin/smtpd/expand.c b/usr.sbin/smtpd/expand.c index 797f63af912..503fb58609d 100644 --- a/usr.sbin/smtpd/expand.c +++ b/usr.sbin/smtpd/expand.c @@ -1,4 +1,4 @@ -/* $OpenBSD: expand.c,v 1.8 2010/06/01 23:06:23 jacekm Exp $ */ +/* $OpenBSD: expand.c,v 1.9 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2009 Gilles Chehade <gilles@openbsd.org> @@ -33,26 +33,27 @@ #include "smtpd.h" struct expandnode * -expandtree_lookup(struct expandtree *tree, struct expandnode *node) +expandtree_lookup(struct expandtree *expandtree, struct expandnode *node) { struct expandnode key; key = *node; - return RB_FIND(expandtree, tree, &key); + return RB_FIND(expandtree, expandtree, &key); } void -expandtree_increment_node(struct expandtree *tree, struct expandnode *node) +expandtree_increment_node(struct expandtree *expandtree, struct expandnode *node) { struct expandnode *p; - p = expandtree_lookup(tree, node); + p = expandtree_lookup(expandtree, node); if (p == NULL) { - p = malloc(sizeof *node); + p = calloc(1, sizeof(struct expandnode)); if (p == NULL) - fatal(NULL); - *p = *node; /* XXX p->refcnt == node->refcnt */ - RB_INSERT(expandtree, tree, p); + fatal("calloc"); + *p = *node; + if (RB_INSERT(expandtree, expandtree, p)) + fatalx("expandtree_increment_node: node already exists"); } p->refcnt++; } @@ -85,8 +86,10 @@ void expandtree_free_nodes(struct expandtree *expandtree) { struct expandnode *p; + struct expandnode *nxt; - while ((p = RB_MIN(expandtree, expandtree))) { + for (p = RB_MIN(expandtree, expandtree); p != NULL; p = nxt) { + nxt = RB_NEXT(expandtree, expandtree, p); RB_REMOVE(expandtree, expandtree, p); free(p); } diff --git a/usr.sbin/smtpd/lka.c b/usr.sbin/smtpd/lka.c index 2b6ef76815f..c2376e32db2 100644 --- a/usr.sbin/smtpd/lka.c +++ b/usr.sbin/smtpd/lka.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka.c,v 1.118 2010/09/20 09:01:09 gilles Exp $ */ +/* $OpenBSD: lka.c,v 1.119 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -52,35 +52,30 @@ int lka_resolve_node(struct smtpd *, char *tag, struct path *, struct expandnod int lka_verify_mail(struct smtpd *, struct path *); struct rule *ruleset_match(struct smtpd *, char *, struct path *, struct sockaddr_storage *); int lka_resolve_path(struct smtpd *, struct lkasession *, struct path *); -struct lkasession *lka_session_init(struct smtpd *, struct message *); -void lka_request_forwardfile(struct smtpd *, struct lkasession *, struct path *); +struct lkasession *lka_session_init(struct smtpd *, struct submit_status *); +void lka_request_forwardfile(struct smtpd *, struct lkasession *, char *); void lka_clear_expandtree(struct expandtree *); void lka_clear_deliverylist(struct deliverylist *); -char *lka_encode_secret(struct map_secret *); -size_t lka_expand(char *, size_t, struct path *,struct path *); +int lka_encode_credentials(char *, size_t, struct map_secret *); +size_t lka_expand(char *, size_t, struct path *, 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 *); -int lka_queue_append(struct smtpd *, struct lkasession *, int); - -u_int32_t lka_id; +void lka_session_fail(struct smtpd *, struct lkasession *, struct submit_status *); void lka_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { struct lkasession skey; + struct submit_status *ss; struct forward_req *fwreq; struct lkasession *s; - struct message *m; + struct secret *secret; struct mapel *mapel; struct rule *rule; struct path *path; struct map *map; - struct map_secret *map_secret; - char *secret; void *tmp; - int status; if (imsg->hdr.type == IMSG_DNS_A || imsg->hdr.type == IMSG_DNS_MX || imsg->hdr.type == IMSG_DNS_PTR) { @@ -91,67 +86,67 @@ lka_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_MFA) { switch (imsg->hdr.type) { case IMSG_LKA_MAIL: - m = imsg->data; - status = 0; - if (m->sender.user[0] || m->sender.domain[0]) - if (! lka_verify_mail(env, &m->sender)) - status = S_MESSAGE_PERMFAILURE; - imsg_compose_event(iev, IMSG_LKA_MAIL, - m->id, 0, -1, &status, sizeof status); + ss = imsg->data; + ss->code = 530; + if (ss->u.path.user[0] == '\0' && + ss->u.path.domain[0] == '\0') + ss->code = 250; + else + if (lka_verify_mail(env, &ss->u.path)) + ss->code = 250; + imsg_compose_event(iev, IMSG_LKA_MAIL, 0, 0, -1, ss, + sizeof *ss); return; - case IMSG_LKA_RCPT: - m = imsg->data; - rule = ruleset_match(env, m->tag, &m->recipient, &m->session_ss); - if (rule == NULL) { - log_debug("lka: rule not found"); - status = S_MESSAGE_PERMFAILURE; - imsg_compose_event(iev, IMSG_LKA_RCPT, m->id, 0, -1, - &status, sizeof status); - return; + case IMSG_LKA_RULEMATCH: + ss = imsg->data; + ss->code = 530; + rule = ruleset_match(env, ss->msg.tag, &ss->u.path, + &ss->ss); + if (rule) { + ss->code = 250; + ss->u.path.rule = *rule; } - m->recipient.rule = *rule; - s = lka_session_init(env, m); - if (! lka_resolve_path(env, s, &m->recipient)) - lka_session_fail(env, s); - else - lka_expand_pickup(env, s); + imsg_compose_event(iev, IMSG_LKA_RULEMATCH, 0, 0, -1, + ss, sizeof *ss); return; - } - } - if (iev->proc == PROC_QUEUE) { - switch (imsg->hdr.type) { - case IMSG_QUEUE_APPEND: - skey.id = imsg->hdr.peerid; - s = SPLAY_FIND(lkatree, &env->lka_sessions, &skey); - if (s == NULL) - fatalx("lka: session missing"); - memcpy(&status, imsg->data, sizeof status); - lka_queue_append(env, s, status); + case IMSG_LKA_RCPT: + ss = imsg->data; + ss->code = 250; + path = &ss->u.path; + s = lka_session_init(env, ss); + if (! lka_resolve_path(env, s, path)) + lka_session_fail(env, s, ss); + else + lka_expand_pickup(env, s); return; } } if (iev->proc == PROC_MTA) { switch (imsg->hdr.type) { - case IMSG_LKA_SECRET: + case IMSG_LKA_SECRET: { + struct map_secret *map_secret; + secret = imsg->data; map = map_findbyname(env, "secrets"); if (map == NULL) fatalx("lka: secrets map not found"); - map_secret = map_lookup(env, map->m_id, imsg->data, K_SECRET); - if (map_secret) - secret = lka_encode_secret(map_secret); - else - secret = ""; - if (*secret == '\0') - log_warnx("%s secret not found", (char *)imsg->data); - imsg_compose_event(iev, IMSG_LKA_SECRET, - imsg->hdr.peerid, 0, -1, secret, - strlen(secret) + 1); + map_secret = map_lookup(env, map->m_id, secret->host, K_SECRET); + log_debug("lka: %s secret lookup (%d)", secret->host, + map_secret != NULL); + secret->secret[0] = '\0'; + if (map_secret == NULL) + log_warnx("%s secret not found", secret->host); + else if (lka_encode_credentials(secret->secret, + sizeof secret->secret, map_secret) == 0) + log_warnx("%s secret parse fail", secret->host); + imsg_compose_event(iev, IMSG_LKA_SECRET, 0, 0, -1, secret, + sizeof *secret); free(map_secret); return; } + } } if (iev->proc == PROC_PARENT) { @@ -378,29 +373,29 @@ lka_expand(char *buf, size_t len, struct path *path, struct path *sender) struct rule r; size_t ret, lret = 0; struct passwd *pw; - + bzero(r.r_value.buffer, MAX_RULEBUFFER_LEN); pbuf = r.r_value.buffer; - + ret = 0; for (p = path->rule.r_value.buffer; *p != '\0'; - ++p, len -= lret, pbuf += lret, ret += lret) { + ++p, len -= lret, pbuf += lret, ret += lret) { if (p == path->rule.r_value.buffer && *p == '~') { if (*(p + 1) == '/' || *(p + 1) == '\0') { pw = getpwnam(path->pw_name); if (pw == NULL) return 0; - + lret = strlcat(pbuf, pw->pw_dir, len); if (lret >= len) return 0; continue; } - + if (*(p + 1) != '/') { char username[MAXLOGNAME]; char *delim; - + lret = strlcpy(username, p + 1, sizeof(username)); if (lret >= sizeof(username)) @@ -473,10 +468,10 @@ copy: lret = 1; *pbuf = *p; } - + /* + 1 to include the NUL byte. */ memcpy(path->rule.r_value.buffer, r.r_value.buffer, ret + 1); - + return ret; } @@ -555,34 +550,32 @@ lka_resolve_node(struct smtpd *env, char *tag, struct path *path, struct expandn } void -lka_expand_pickup(struct smtpd *env, struct lkasession *s) +lka_expand_pickup(struct smtpd *env, struct lkasession *lkasession) { int ret; - if (s->pending) - return; - - if (s->flags & F_ERROR) { - lka_expansion_done(env, s); - return; - } - /* we want to do five iterations of lka_expand_resume() but * we need to be interruptible in case lka_expand_resume() * has sent an imsg and expects an answer. */ - while (s->iterations < 5) { - s->iterations++; - ret = lka_expand_resume(env, s); - if (ret == -1) - s->flags |= F_ERROR; - if (s->pending) - return; - if (ret == 0) + ret = 0; + while (! (lkasession->flags & F_ERROR) && + ! lkasession->pending && lkasession->iterations < 5) { + ++lkasession->iterations; + ret = lka_expand_resume(env, lkasession); + if (ret == -1) { + lkasession->ss.code = 530; + lkasession->flags |= F_ERROR; + } + + if (lkasession->pending || ret <= 0) break; } - lka_expansion_done(env, s); + if (lkasession->pending) + return; + + lka_expansion_done(env, lkasession); } int @@ -631,91 +624,45 @@ lka_expand_resume(struct smtpd *env, struct lkasession *lkasession) } void -lka_expansion_done(struct smtpd *env, struct lkasession *s) +lka_expansion_done(struct smtpd *env, struct lkasession *lkasession) { - int status; + struct message message; + struct path *path; /* delivery list is empty OR expansion led to an error, reject */ - if (TAILQ_EMPTY(&s->deliverylist) || s->flags & F_ERROR) { - if (TAILQ_EMPTY(&s->deliverylist)) - log_debug("lka_expansion_done: list empty"); - else - log_debug("lka_expansion_done: session error"); - goto error; - } else if (! lka_queue_append(env, s, 0)) - goto error; - return; - -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); -} - -int -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; - int ret; - - 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 0; + if (TAILQ_FIRST(&lkasession->deliverylist) == NULL || + lkasession->flags & F_ERROR) { + imsg_compose_event(env->sc_ievs[PROC_MFA], IMSG_LKA_RCPT, 0, 0, + -1, &lkasession->ss, sizeof(struct submit_status)); + goto done; } - /* send next item to queue */ - message = s->message; - if (path->rule.r_action != A_RELAY && - path->rule.r_action != A_RELAYVIA) { - log_debug("lka_expand: before: [%s]", path->rule.r_value.buffer); - ret = lka_expand(path->rule.r_value.buffer, + /* 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.buffer, sizeof(path->rule.r_value.buffer), path, &message.sender); - log_debug("lka_expand: after: [%s]", path->rule.r_value.buffer); - if (! ret) { - log_debug("lka_expand: returned failure."); - return 0; - } + message.recipient = *path; + queue_submit_envelope(env, &message); + + TAILQ_REMOVE(&lkasession->deliverylist, path, entry); + free(path); } + queue_commit_envelopes(env, &message); - 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); - return 1; +done: + lka_clear_expandtree(&lkasession->expandtree); + lka_clear_deliverylist(&lkasession->deliverylist); + lka_session_destroy(env, lkasession); } int -lka_resolve_path(struct smtpd *env, struct lkasession *s, struct path *path) +lka_resolve_path(struct smtpd *env, struct lkasession *lkasession, struct path *path) { if (IS_RELAY(*path)) { path = path_dup(path); path->flags |= F_PATH_RELAY; - TAILQ_INSERT_TAIL(&s->deliverylist, path, entry); + TAILQ_INSERT_TAIL(&lkasession->deliverylist, path, entry); return 1; } @@ -735,27 +682,37 @@ lka_resolve_path(struct smtpd *env, struct lkasession *s, struct path *path) if (aliases_exist(env, path->rule.r_amap, username)) { path->flags |= F_PATH_ALIAS; - return aliases_get(env, path->rule.r_amap, - &s->expandtree, username); + if (! aliases_get(env, path->rule.r_amap, + &lkasession->expandtree, path->user)) + return 0; + return 1; } + if (strlen(username) >= MAXLOGNAME) + return 0; + path->flags |= F_PATH_ACCOUNT; pw = getpwnam(username); if (pw == NULL) return 0; - strlcpy(path->pw_name, pw->pw_name, sizeof path->pw_name); + (void)strlcpy(path->pw_name, pw->pw_name, + sizeof(path->pw_name)); if (path->flags & F_PATH_FORWARDED) - TAILQ_INSERT_TAIL(&s->deliverylist, path, entry); + TAILQ_INSERT_TAIL(&lkasession->deliverylist, path, entry); else - lka_request_forwardfile(env, s, path); + lka_request_forwardfile(env, lkasession, path->pw_name); + return 1; } case C_VDOM: { if (aliases_virtual_exist(env, path->rule.r_condition.c_map, path)) { path->flags |= F_PATH_VIRTUAL; - return aliases_virtual_get(env, path->rule.r_condition.c_map, &s->expandtree, path); + if (! aliases_virtual_get(env, path->rule.r_condition.c_map, + &lkasession->expandtree, path)) + return 0; + return 1; } break; } @@ -821,76 +778,72 @@ lka_clear_deliverylist(struct deliverylist *deliverylist) } } -char * -lka_encode_secret(struct map_secret *map_secret) +int +lka_encode_credentials(char *dst, size_t size, struct map_secret *map_secret) { - static char dst[1024]; - char *src; - int src_sz; + char *buf; + int buflen; - src_sz = asprintf(&src, "%c%s%c%s", '\0', map_secret->username, '\0', - map_secret->password); - if (src_sz == -1) + if ((buflen = asprintf(&buf, "%c%s%c%s", '\0', map_secret->username, + '\0', map_secret->password)) == -1) fatal(NULL); - if (__b64_ntop(src, src_sz, dst, sizeof dst) == -1) { - free(src); - return NULL; + + if (__b64_ntop((unsigned char *)buf, buflen, dst, size) == -1) { + free(buf); + return 0; } - free(src); - dst[sizeof(dst) - 1] = '\0'; - return dst; + free(buf); + return 1; } struct lkasession * -lka_session_init(struct smtpd *env, struct message *m) +lka_session_init(struct smtpd *env, struct submit_status *ss) { - struct lkasession *s; - - s = calloc(1, sizeof *s); - if (s == NULL) - fatal(NULL); - - s->id = lka_id++; - s->path = m->recipient; - s->message = *m; - - RB_INIT(&s->expandtree); - TAILQ_INIT(&s->deliverylist); - SPLAY_INSERT(lkatree, &env->lka_sessions, s); - - return s; + struct lkasession *lkasession; + + lkasession = calloc(1, sizeof(struct lkasession)); + if (lkasession == NULL) + fatal("lka_session_init: calloc"); + + lkasession->id = generate_uid(); + lkasession->path = ss->u.path; + lkasession->message = ss->msg; + lkasession->ss = *ss; + + RB_INIT(&lkasession->expandtree); + TAILQ_INIT(&lkasession->deliverylist); + SPLAY_INSERT(lkatree, &env->lka_sessions, lkasession); + + return lkasession; } void -lka_session_fail(struct smtpd *env, struct lkasession *s) +lka_session_fail(struct smtpd *env, struct lkasession *lkasession, struct submit_status *ss) { - int status; - - log_debug("lka: initina lka_resolve_path failed"); - status = S_MESSAGE_PERMFAILURE; - imsg_compose_event(env->sc_ievs[PROC_MFA], IMSG_LKA_RCPT, - s->message.id, 0, -1, &status, sizeof status); - lka_session_destroy(env, s); + ss->code = 530; + imsg_compose_event(env->sc_ievs[PROC_MFA], IMSG_LKA_RCPT, 0, 0, -1, + ss, sizeof(*ss)); + lka_session_destroy(env, lkasession); } void -lka_session_destroy(struct smtpd *env, struct lkasession *s) +lka_session_destroy(struct smtpd *env, struct lkasession *lkasession) { - SPLAY_REMOVE(lkatree, &env->lka_sessions, s); - free(s); + SPLAY_REMOVE(lkatree, &env->lka_sessions, lkasession); + free(lkasession); } void -lka_request_forwardfile(struct smtpd *env, struct lkasession *s, struct path *path) +lka_request_forwardfile(struct smtpd *env, struct lkasession *lkasession, char *username) { struct forward_req fwreq; - fwreq.id = s->id; - strlcpy(fwreq.pw_name, path->pw_name, sizeof fwreq.pw_name); + fwreq.id = lkasession->id; + (void)strlcpy(fwreq.pw_name, username, sizeof(fwreq.pw_name)); imsg_compose_event(env->sc_ievs[PROC_PARENT], IMSG_PARENT_FORWARD_OPEN, 0, 0, -1, - &fwreq, sizeof fwreq); - s->pending++; + &fwreq, sizeof(fwreq)); + ++lkasession->pending; } SPLAY_GENERATE(lkatree, lkasession, nodes, lkasession_cmp); diff --git a/usr.sbin/smtpd/log.c b/usr.sbin/smtpd/log.c index c570f113cca..df030196682 100644 --- a/usr.sbin/smtpd/log.c +++ b/usr.sbin/smtpd/log.c @@ -1,4 +1,4 @@ -/* $OpenBSD: log.c,v 1.8 2010/06/01 23:06:23 jacekm Exp $ */ +/* $OpenBSD: log.c,v 1.9 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> @@ -51,7 +51,7 @@ log_init(int n_debug) verbose = n_debug; if (!debug) - openlog(__progname, LOG_NDELAY, LOG_MAIL); + openlog(__progname, LOG_PID | LOG_NDELAY, LOG_MAIL); tzset(); } diff --git a/usr.sbin/smtpd/map.c b/usr.sbin/smtpd/map.c index 578fa134936..30bc42f09a2 100644 --- a/usr.sbin/smtpd/map.c +++ b/usr.sbin/smtpd/map.c @@ -1,4 +1,4 @@ -/* $OpenBSD: map.c,v 1.19 2010/06/01 23:06:23 jacekm Exp $ */ +/* $OpenBSD: map.c,v 1.20 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -64,12 +64,13 @@ map_find(struct smtpd *env, objid_t id) void * map_lookup(struct smtpd *env, objid_t mapid, char *key, enum map_kind kind) { - void *hdl; - char *result, *tmp; + void *hdl = NULL; + char *result = NULL; + char *ret = NULL; size_t len; struct map *map; - struct map_backend *backend; - struct map_parser *parser; + struct map_backend *backend = NULL; + struct map_parser *parser = NULL; map = map_find(env, mapid); if (map == NULL) @@ -84,17 +85,16 @@ map_lookup(struct smtpd *env, objid_t mapid, char *key, enum map_kind kind) return NULL; } - result = backend->get(hdl, key, &len); - if (result == NULL) + ret = result = backend->get(hdl, key, &len); + if (ret == NULL) goto end; - if (parser->extract) { - tmp = result; - result = parser->extract(key, result, len); - free(tmp); + if (parser->extract != NULL) { + ret = parser->extract(key, result, len); + free(result); } end: backend->close(hdl); - return result; + return ret; } diff --git a/usr.sbin/smtpd/mda.c b/usr.sbin/smtpd/mda.c index d71b74b72ee..ba9e543d46e 100644 --- a/usr.sbin/smtpd/mda.c +++ b/usr.sbin/smtpd/mda.c @@ -1,9 +1,9 @@ -/* $OpenBSD: mda.c,v 1.48 2010/06/02 19:16:53 chl Exp $ */ +/* $OpenBSD: mda.c,v 1.49 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> - * Copyright (c) 2009-2010 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -36,7 +36,6 @@ #include <vis.h> #include "smtpd.h" -#include "queue_backend.h" void mda_imsg(struct smtpd *, struct imsgev *, struct imsg *); __dead void mda_shutdown(void); @@ -47,64 +46,74 @@ void mda_store(struct mda_session *); void mda_store_event(int, short, void *); struct mda_session *mda_lookup(struct smtpd *, u_int32_t); +u_int32_t mda_id; + void mda_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { char output[128], *error, *parent_error; struct deliver deliver; struct mda_session *s; - struct action *action; + struct path *path; if (iev->proc == PROC_QUEUE) { switch (imsg->hdr.type) { - case IMSG_BATCH_CREATE: - s = malloc(sizeof *s); + case IMSG_MDA_SESS_NEW: + /* make new session based on provided args */ + s = calloc(1, sizeof *s); if (s == NULL) fatal(NULL); msgbuf_init(&s->w); - bzero(&s->ev, sizeof s->ev); - s->id = imsg->hdr.peerid; - s->content_id = *(u_int64_t *)imsg->data; + s->msg = *(struct message *)imsg->data; + s->msg.status = S_MESSAGE_TEMPFAILURE; + s->id = mda_id++; s->datafp = fdopen(imsg->fd, "r"); if (s->datafp == NULL) fatalx("mda: fdopen"); LIST_INSERT_HEAD(&env->mda_sessions, s, entry); - return; - - case IMSG_BATCH_APPEND: - LIST_FOREACH(s, &env->mda_sessions, entry) - if (s->id == imsg->hdr.peerid) - break; - if (s == NULL) - fatalx("mda: bogus append"); - action = imsg->data; - s->action_id = action->id; - s->auxraw = strdup(action->data); - if (s->auxraw == NULL) - fatal(NULL); - auxsplit(&s->aux, s->auxraw); - return; - case IMSG_BATCH_CLOSE: - LIST_FOREACH(s, &env->mda_sessions, entry) - if (s->id == imsg->hdr.peerid) - break; - if (s == NULL) - fatalx("mda: bogus close"); - memcpy(&s->birth, imsg->data, sizeof s->birth); - - /* request helper process from parent */ - if (s->aux.mode[0] == 'M') { - deliver.mode = 'P'; - strlcpy(deliver.user, "root", sizeof deliver.user); + /* request parent to fork a helper process */ + path = &s->msg.recipient; + switch (path->rule.r_action) { + case A_EXT: + deliver.mode = A_EXT; + strlcpy(deliver.user, path->pw_name, + sizeof deliver.user); + strlcpy(deliver.to, path->rule.r_value.buffer, + sizeof deliver.to); + break; + + case A_MBOX: + deliver.mode = A_EXT; + strlcpy(deliver.user, "root", + sizeof deliver.user); snprintf(deliver.to, sizeof deliver.to, - "exec /usr/libexec/mail.local %s", - s->aux.user_to); - } else { - deliver.mode = s->aux.mode[0]; - strlcpy(deliver.user, s->aux.user_to, sizeof deliver.user); - strlcpy(deliver.to, s->aux.path, sizeof deliver.to); + "%s -f %s@%s %s", PATH_MAILLOCAL, + s->msg.sender.user, s->msg.sender.domain, + path->pw_name); + break; + + case A_MAILDIR: + deliver.mode = A_MAILDIR; + strlcpy(deliver.user, path->pw_name, + sizeof deliver.user); + strlcpy(deliver.to, path->rule.r_value.buffer, + sizeof deliver.to); + break; + + case A_FILENAME: + deliver.mode = A_FILENAME; + /* XXX: unconditional SMTPD_USER is wrong. */ + strlcpy(deliver.user, SMTPD_USER, + sizeof deliver.user); + strlcpy(deliver.to, path->u.filename, + sizeof deliver.to); + break; + + default: + fatalx("mda: unknown rule action"); } + imsg_compose_event(env->sc_ievs[PROC_PARENT], IMSG_PARENT_FORK_MDA, s->id, 0, -1, &deliver, sizeof deliver); @@ -127,10 +136,6 @@ mda_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) case IMSG_MDA_DONE: s = mda_lookup(env, imsg->hdr.peerid); - /* all parent errors are temporary */ - if (asprintf(&parent_error, "100 %s", (char *)imsg->data) < 0) - fatal("mda: asprintf"); - /* * Grab last line of mda stdout/stderr if available. */ @@ -157,11 +162,12 @@ mda_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) buf[len] = '\0'; ln = buf; } - strlcpy(output, "100 \"", sizeof output); - strnvis(output + 5, ln, - sizeof(output) - 6, + strlcpy(output, "\"", sizeof output); + strnvis(output + 1, ln, + sizeof(output) - 2, VIS_SAFE | VIS_CSTYLE); strlcat(output, "\"", sizeof output); + log_debug("mda_out: %s", output); } free(buf); fclose(fp); @@ -172,11 +178,11 @@ mda_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) * child's output, the latter having preference over * the former. */ - if (strcmp(parent_error + 4, "exited okay") == 0) { + error = NULL; + parent_error = imsg->data; + if (strcmp(parent_error, "exited okay") == 0) { if (!feof(s->datafp) || s->w.queued) - error = "100 mda exited prematurely"; - else - error = "200 ok"; + error = "mda exited prematurely"; } else { if (output[0]) error = output; @@ -185,24 +191,35 @@ mda_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) } /* update queue entry */ - action = malloc(sizeof *action + strlen(error)); - if (action == NULL) - fatal(NULL); - action->id = s->action_id; - strlcpy(action->data, error, strlen(error) + 1); + if (error == NULL) + s->msg.status = S_MESSAGE_ACCEPTED; + else + strlcpy(s->msg.session_errorline, error, + sizeof s->msg.session_errorline); imsg_compose_event(env->sc_ievs[PROC_QUEUE], - IMSG_BATCH_UPDATE, s->id, 0, -1, action, - sizeof *action + strlen(error)); - imsg_compose_event(env->sc_ievs[PROC_QUEUE], - IMSG_BATCH_DONE, s->id, 0, -1, NULL, 0); + IMSG_QUEUE_MESSAGE_UPDATE, 0, 0, -1, &s->msg, + sizeof s->msg); + + /* + * XXX: which struct path gets used for logging depends + * on whether lka did aliases or .forward processing; + * lka may need to be changed to present data in more + * unified way. + */ + if (s->msg.recipient.rule.r_action == A_MAILDIR || + s->msg.recipient.rule.r_action == A_MBOX) + path = &s->msg.recipient; + else + path = &s->msg.session_rcpt; /* log status */ - log_info("%s: to=%s, delay=%d, stat=%s%s%s", - queue_be_decode(s->content_id), rcpt_pretty(&s->aux), - time(NULL) - s->birth, - *error == '2' ? "Sent" : "Error (", - *error == '2' ? "" : error + 4, - *error == '2' ? "" : ")"); + if (error && asprintf(&error, "Error (%s)", error) < 0) + fatal("mda: asprintf"); + log_info("%s: to=<%s@%s>, delay=%d, stat=%s", + s->msg.message_id, path->user, path->domain, + time(NULL) - s->msg.creation, + error ? error : "Sent"); + free(error); /* destroy session */ LIST_REMOVE(s, entry); @@ -212,9 +229,11 @@ mda_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) fclose(s->datafp); msgbuf_clear(&s->w); event_del(&s->ev); - free(s->auxraw); free(s); - free(parent_error); + + /* update queue's session count */ + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_MDA_SESS_NEW, 0, 0, -1, NULL, 0); return; case IMSG_CTL_VERBOSE: @@ -306,6 +325,7 @@ 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)); @@ -325,12 +345,14 @@ mda_store(struct mda_session *s) struct ibuf *buf; int len; - /* XXX: remove user provided Return-Path, if any */ - if (s->aux.mail_from[0]) - len = asprintf(&p, "Return-Path: %s\nDelivered-To: %s\n", - s->aux.mail_from, s->aux.rcpt_to); + if (s->msg.sender.user[0] && s->msg.sender.domain[0]) + /* XXX: remove user provided Return-Path, if any */ + len = asprintf(&p, "Return-Path: %s@%s\nDelivered-To: %s@%s\n", + s->msg.sender.user, s->msg.sender.domain, + s->msg.session_rcpt.user, s->msg.session_rcpt.domain); else - len = asprintf(&p, "Delivered-To: %s\n", s->aux.rcpt_to); + len = asprintf(&p, "Delivered-To: %s@%s\n", + s->msg.session_rcpt.user, s->msg.session_rcpt.domain); if (len == -1) fatal("mda_store: asprintf"); diff --git a/usr.sbin/smtpd/mfa.c b/usr.sbin/smtpd/mfa.c index 2c5cab35722..984229ddaa1 100644 --- a/usr.sbin/smtpd/mfa.c +++ b/usr.sbin/smtpd/mfa.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mfa.c,v 1.49 2010/06/02 19:16:53 chl Exp $ */ +/* $OpenBSD: mfa.c,v 1.50 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -42,6 +42,7 @@ void mfa_disable_events(struct smtpd *); void mfa_test_mail(struct smtpd *, struct message *); void mfa_test_rcpt(struct smtpd *, struct message *); +void mfa_test_rcpt_resume(struct smtpd *, struct submit_status *); int strip_source_route(char *, size_t); @@ -65,15 +66,14 @@ mfa_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_LKA) { switch (imsg->hdr.type) { case IMSG_LKA_MAIL: + case IMSG_LKA_RCPT: imsg_compose_event(env->sc_ievs[PROC_SMTP], - IMSG_MFA_MAIL, imsg->hdr.peerid, 0, -1, imsg->data, - imsg->hdr.len - sizeof imsg->hdr); + IMSG_MFA_MAIL, 0, 0, -1, imsg->data, + sizeof(struct submit_status)); return; - case IMSG_LKA_RCPT: - imsg_compose_event(env->sc_ievs[PROC_SMTP], - IMSG_MFA_RCPT, imsg->hdr.peerid, 0, -1, imsg->data, - imsg->hdr.len - sizeof imsg->hdr); + case IMSG_LKA_RULEMATCH: + mfa_test_rcpt_resume(env, imsg->data); return; } } @@ -182,20 +182,38 @@ mfa(struct smtpd *env) return (0); } +int +msg_cmp(struct message *m1, struct message *m2) +{ + /* + * do not return u_int64_t's + */ + if (m1->id - m2->id > 0) + return (1); + else if (m1->id - m2->id < 0) + return (-1); + else + return (0); +} + void mfa_test_mail(struct smtpd *env, struct message *m) { - int status; + struct submit_status ss; - if (strip_source_route(m->sender.user, sizeof(m->sender.user))) + ss.id = m->id; + ss.code = 530; + ss.u.path = m->sender; + + if (strip_source_route(ss.u.path.user, sizeof(ss.u.path.user))) goto refuse; - if (! valid_localpart(m->sender.user) || - ! valid_domainpart(m->sender.domain)) { + if (! valid_localpart(ss.u.path.user) || + ! valid_domainpart(ss.u.path.domain)) { /* * "MAIL FROM:<>" is the exception we allow. */ - if (!(m->sender.user[0] == '\0' && m->sender.domain[0] == '\0')) + if (!(ss.u.path.user[0] == '\0' && ss.u.path.domain[0] == '\0')) goto refuse; } @@ -203,39 +221,61 @@ mfa_test_mail(struct smtpd *env, struct message *m) goto accept; refuse: - status = S_MESSAGE_PERMFAILURE; - imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_MAIL, m->id, 0, -1, - &status, sizeof status); + imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_MAIL, 0, 0, -1, &ss, + sizeof(ss)); return; accept: + ss.code = 250; imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_MAIL, 0, - 0, -1, m, sizeof *m); + 0, -1, &ss, sizeof(ss)); } void mfa_test_rcpt(struct smtpd *env, struct message *m) { - int status; + struct submit_status ss; + + if (! valid_message_id(m->message_id)) + fatalx("mfa_test_rcpt: received corrupted message_id"); - m->recipient = m->session_rcpt; + ss.id = m->session_id; + ss.code = 530; + ss.u.path = m->session_rcpt; + ss.ss = m->session_ss; + ss.msg = *m; + ss.msg.recipient = m->session_rcpt; + ss.flags = m->flags; - strip_source_route(m->recipient.user, sizeof(m->recipient.user)); + strip_source_route(ss.u.path.user, sizeof(ss.u.path.user)); - if (! valid_localpart(m->recipient.user) || - ! valid_domainpart(m->recipient.domain)) + if (! valid_localpart(ss.u.path.user) || + ! valid_domainpart(ss.u.path.domain)) goto refuse; - if (m->flags & F_MESSAGE_AUTHENTICATED) - m->recipient.flags |= F_PATH_AUTHENTICATED; + if (ss.flags & F_MESSAGE_AUTHENTICATED) + ss.u.path.flags |= F_PATH_AUTHENTICATED; + + imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_RULEMATCH, 0, 0, -1, + &ss, sizeof(ss)); - imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_RCPT, 0, 0, -1, - m, sizeof *m); return; refuse: - status = S_MESSAGE_PERMFAILURE; - imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_RCPT, m->id, 0, -1, - &status, sizeof status); + imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_RCPT, 0, 0, -1, &ss, + sizeof(ss)); +} + +void +mfa_test_rcpt_resume(struct smtpd *env, struct submit_status *ss) { + if (ss->code != 250) { + imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_RCPT, 0, 0, -1, ss, + sizeof(*ss)); + return; + } + + ss->msg.recipient = ss->u.path; + imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_RCPT, 0, 0, -1, + ss, sizeof(*ss)); } int diff --git a/usr.sbin/smtpd/mta.c b/usr.sbin/smtpd/mta.c index c3d99197f06..b9119a80c91 100644 --- a/usr.sbin/smtpd/mta.c +++ b/usr.sbin/smtpd/mta.c @@ -1,9 +1,9 @@ -/* $OpenBSD: mta.c,v 1.92 2010/06/02 19:16:53 chl Exp $ */ +/* $OpenBSD: mta.c,v 1.93 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> - * Copyright (c) 2009-2010 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -40,81 +40,114 @@ #include "smtpd.h" #include "client.h" -#include "queue_backend.h" void mta_imsg(struct smtpd *, struct imsgev *, struct imsg *); __dead void mta_shutdown(void); void mta_sig_handler(int, short, void *); -struct mta_session *mta_lookup(struct smtpd *, u_int32_t); +struct mta_session *mta_lookup(struct smtpd *, u_int64_t); void mta_enter_state(struct mta_session *, int, void *); void mta_pickup(struct mta_session *, void *); void mta_event(int, short, void *); void mta_status(struct mta_session *, const char *, ...); -void mta_rcpt_status(struct recipient *, char *); -void mta_rcpt_log(struct mta_session *, struct recipient *); -void mta_rcpt_done(struct mta_session *, struct recipient *); +void mta_message_status(struct message *, char *); +void mta_message_log(struct mta_session *, struct message *); +void mta_message_done(struct mta_session *, struct message *); void mta_connect_done(int, short, void *); +void mta_request_datafd(struct mta_session *); void mta_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { - struct aux aux; struct mta_session *s; - struct recipient *rcpt; struct mta_relay *relay; - struct action *action; + struct message *m; + struct secret *secret; + struct batch *b; struct dns *dns; struct ssl *ssl; if (iev->proc == PROC_QUEUE) { switch (imsg->hdr.type) { case IMSG_BATCH_CREATE: - if (imsg->fd < 0) - fatalx("mta: fd pass fail"); + b = imsg->data; s = calloc(1, sizeof *s); if (s == NULL) fatal(NULL); - TAILQ_INIT(&s->recipients); - TAILQ_INIT(&s->relays); - s->id = imsg->hdr.peerid; + s->id = b->id; s->state = MTA_INIT; s->env = env; - s->datafd = imsg->fd; - memcpy(&s->content_id, imsg->data, - sizeof s->content_id); + s->datafd = -1; + + /* establish host name */ + if (b->rule.r_action == A_RELAYVIA) + s->host = strdup(b->rule.r_value.relayhost.hostname); + else + s->host = strdup(b->hostname); + if (s->host == NULL) + fatal(NULL); + + /* establish port */ + s->port = ntohs(b->rule.r_value.relayhost.port); /* XXX */ + + /* have cert? */ + s->cert = strdup(b->rule.r_value.relayhost.cert); + if (s->cert == NULL) + fatal(NULL); + else if (s->cert[0] == '\0') { + free(s->cert); + s->cert = NULL; + } + + /* use auth? */ + if ((b->rule.r_value.relayhost.flags & F_SSL) && + (b->rule.r_value.relayhost.flags & F_AUTH)) + s->flags |= MTA_USE_AUTH; + + /* force a particular SSL mode? */ + switch (b->rule.r_value.relayhost.flags & F_SSL) { + case F_SSL: + s->flags |= MTA_FORCE_ANYSSL; + break; + + case F_SMTPS: + s->flags |= MTA_FORCE_SMTPS; + + case F_STARTTLS: + /* client_* API by default requires STARTTLS */ + break; + + default: + s->flags |= MTA_ALLOW_PLAIN; + } + + TAILQ_INIT(&s->recipients); + TAILQ_INIT(&s->relays); SPLAY_INSERT(mtatree, &env->mta_sessions, s); return; case IMSG_BATCH_APPEND: - action = imsg->data; - s = mta_lookup(env, imsg->hdr.peerid); - if (s->auxraw == NULL) { - /* - * XXX: queue can batch together actions with - * different relay params. - */ - s->auxraw = strdup(action->data); - if (s->auxraw == NULL) - fatal(NULL); - auxsplit(&s->aux, s->auxraw); - } - auxsplit(&aux, action->data); - rcpt = malloc(sizeof *rcpt + strlen(aux.rcpt)); - if (rcpt == NULL) + m = imsg->data; + s = mta_lookup(env, m->batch_id); + m = malloc(sizeof *m); + if (m == NULL) fatal(NULL); - rcpt->action_id = action->id; - strlcpy(rcpt->address, aux.rcpt, strlen(aux.rcpt) + 1); - strlcpy(rcpt->status, "000 init", sizeof rcpt->status); - TAILQ_INSERT_TAIL(&s->recipients, rcpt, entry); + *m = *(struct message *)imsg->data; + strlcpy(m->session_errorline, "000 init", + sizeof(m->session_errorline)); + TAILQ_INSERT_TAIL(&s->recipients, m, entry); return; case IMSG_BATCH_CLOSE: - s = mta_lookup(env, imsg->hdr.peerid); - memcpy(&s->birth, imsg->data, sizeof s->birth); - mta_pickup(s, NULL); + b = imsg->data; + mta_pickup(mta_lookup(env, b->id), NULL); + return; + + case IMSG_QUEUE_MESSAGE_FD: + b = imsg->data; + mta_pickup(mta_lookup(env, b->id), &imsg->fd); return; } } @@ -122,7 +155,8 @@ mta_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_LKA) { switch (imsg->hdr.type) { case IMSG_LKA_SECRET: - mta_pickup(mta_lookup(env, imsg->hdr.peerid), imsg->data); + secret = imsg->data; + mta_pickup(mta_lookup(env, secret->id), secret->secret); return; case IMSG_DNS_A: @@ -289,25 +323,24 @@ mta_session_cmp(struct mta_session *a, struct mta_session *b) } struct mta_session * -mta_lookup(struct smtpd *env, u_int32_t id) +mta_lookup(struct smtpd *env, u_int64_t id) { struct mta_session key, *res; key.id = id; - res = SPLAY_FIND(mtatree, &env->mta_sessions, &key); - if (res == NULL) + if ((res = SPLAY_FIND(mtatree, &env->mta_sessions, &key)) == NULL) fatalx("mta_lookup: session not found"); - return res; + return (res); } void mta_enter_state(struct mta_session *s, int newstate, void *p) { + struct secret secret; struct mta_relay *relay; struct sockaddr *sa; - struct recipient *rcpt; + struct message *m; struct smtp_client *pcb; - char *host; int max_reuse; s->state = newstate; @@ -317,34 +350,33 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) /* * Lookup AUTH secret. */ - if (s->aux.relay_via[0]) - host = s->aux.relay_via; - else { - rcpt = TAILQ_FIRST(&s->recipients); - host = strchr(rcpt->address, '@') + 1; - } + bzero(&secret, sizeof(secret)); + secret.id = s->id; + strlcpy(secret.host, s->host, sizeof(secret.host)); imsg_compose_event(s->env->sc_ievs[PROC_LKA], IMSG_LKA_SECRET, - s->id, 0, -1, host, strlen(host) + 1); + 0, 0, -1, &secret, sizeof(secret)); break; case MTA_MX: /* * Lookup MX record. */ - if (s->aux.relay_via[0]) - host = s->aux.relay_via; - else { - rcpt = TAILQ_FIRST(&s->recipients); - host = strchr(rcpt->address, '@') + 1; - } - dns_query_mx(s->env, host, 0, s->id); + dns_query_mx(s->env, s->host, 0, s->id); + break; + + case MTA_DATA: + /* + * Obtain message body fd. + */ + log_debug("mta: getting datafd"); + mta_request_datafd(s); break; case MTA_CONNECT: /* * Connect to the MX. */ - if (strcmp(s->aux.ssl, "ssl") == 0) + if (s->flags & MTA_FORCE_ANYSSL) max_reuse = 2; else max_reuse = 1; @@ -361,25 +393,24 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) log_debug("mta: connect %s", ss_to_text(&relay->sa)); sa = (struct sockaddr *)&relay->sa; - if (s->aux.port[0]) - sa_set_port(sa, s->aux.port); - else if (strcmp(s->aux.ssl, "ssl") == 0 && - relay->used == 1) - sa_set_port(sa, "465"); - else if (strcmp(s->aux.ssl, "smtps") == 0) - sa_set_port(sa, "465"); + if (s->port) + sa_set_port(sa, s->port); + else if ((s->flags & MTA_FORCE_ANYSSL) && relay->used == 1) + sa_set_port(sa, 465); + else if (s->flags & MTA_FORCE_SMTPS) + sa_set_port(sa, 465); else - sa_set_port(sa, "25"); + sa_set_port(sa, 25); s->fd = socket(sa->sa_family, SOCK_STREAM, 0); if (s->fd == -1) - fatal("socket"); + fatal("mta cannot create socket"); session_socket_blockmode(s->fd, BM_NONBLOCK); session_socket_no_linger(s->fd); if (connect(s->fd, sa, sa->sa_len) == -1) { if (errno != EINPROGRESS) { - mta_status(s, "110 connect: %s", + mta_status(s, "110 connect error: %s", strerror(errno)); close(s->fd); continue; @@ -411,10 +442,10 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) pcb = client_init(s->fd, s->datafd, s->env->sc_hostname, 1); /* lookup SSL certificate */ - if (s->aux.cert[0]) { - struct ssl key, *res; + if (s->cert) { + struct ssl key, *res; - strlcpy(key.ssl_name, s->aux.cert, sizeof key.ssl_name); + strlcpy(key.ssl_name, s->cert, sizeof(key.ssl_name)); res = SPLAY_FIND(ssltree, s->env->sc_ssl, &key); if (res == NULL) { client_close(pcb); @@ -429,11 +460,11 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) /* choose SMTPS vs. STARTTLS */ relay = TAILQ_FIRST(&s->relays); - if (strcmp(s->aux.ssl, "ssl") == 0 && relay->used == 1) + if ((s->flags & MTA_FORCE_ANYSSL) && relay->used == 1) client_ssl_smtps(pcb); - else if (strcmp(s->aux.ssl, "smtps") == 0) + else if (s->flags & MTA_FORCE_SMTPS) client_ssl_smtps(pcb); - else if (s->aux.ssl[0] == '\0') + else if (s->flags & MTA_ALLOW_PLAIN) client_ssl_optional(pcb); /* enable AUTH */ @@ -441,14 +472,17 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) client_auth(pcb, s->secret); /* set envelope sender */ - if (s->aux.mail_from[0]) - client_sender(pcb, "%s", s->aux.mail_from); + m = TAILQ_FIRST(&s->recipients); + if (m->sender.user[0] && m->sender.domain[0]) + client_sender(pcb, "%s@%s", m->sender.user, + m->sender.domain); else client_sender(pcb, ""); /* set envelope recipients */ - TAILQ_FOREACH(rcpt, &s->recipients, entry) - client_rcpt(pcb, rcpt, "%s", rcpt->address); + TAILQ_FOREACH(m, &s->recipients, entry) + client_rcpt(pcb, m, "%s@%s", m->recipient.user, + m->recipient.domain); s->pcb = pcb; event_set(&s->ev, s->fd, EV_READ|EV_WRITE, mta_event, s); @@ -461,11 +495,11 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) */ /* update queue status */ - while ((rcpt = TAILQ_FIRST(&s->recipients))) - mta_rcpt_done(s, rcpt); + while ((m = TAILQ_FIRST(&s->recipients))) + mta_message_done(s, m); imsg_compose_event(s->env->sc_ievs[PROC_QUEUE], - IMSG_BATCH_DONE, s->id, 0, -1, NULL, 0); + IMSG_BATCH_DONE, 0, 0, -1, NULL, 0); /* deallocate resources */ SPLAY_REMOVE(mtatree, &s->env->mta_sessions, s); @@ -474,8 +508,9 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) free(relay); } close(s->datafd); - free(s->auxraw); free(s->secret); + free(s->host); + free(s->cert); free(s); break; @@ -491,7 +526,7 @@ mta_pickup(struct mta_session *s, void *p) switch (s->state) { case MTA_INIT: - if (s->aux.auth[0]) + if (s->flags & MTA_USE_AUTH) mta_enter_state(s, MTA_SECRET, NULL); else mta_enter_state(s, MTA_MX, NULL); @@ -522,6 +557,15 @@ mta_pickup(struct mta_session *s, void *p) mta_status(s, "600 Unable to resolve DNS for domain"); mta_enter_state(s, MTA_DONE, NULL); } else + mta_enter_state(s, MTA_DATA, NULL); + break; + + case MTA_DATA: + /* QUEUE replied to body fd request. */ + s->datafd = *(int *)p; + if (s->datafd == -1) + fatalx("mta cannot obtain msgfd"); + else mta_enter_state(s, MTA_CONNECT, NULL); break; @@ -529,7 +573,7 @@ mta_pickup(struct mta_session *s, void *p) /* Remote accepted/rejected connection. */ error = session_socket_error(s->fd); if (error) { - mta_status(s, "110 connect: %s", strerror(error)); + mta_status(s, "110 connect error: %s", strerror(error)); close(s->fd); mta_enter_state(s, MTA_CONNECT, NULL); } else @@ -550,7 +594,6 @@ mta_event(int fd, short event, void *p) { struct mta_session *s = p; struct smtp_client *pcb = s->pcb; - struct recipient *rcpt; if (event & EV_TIMEOUT) { mta_status(s, "150 timeout"); @@ -563,10 +606,9 @@ mta_event(int fd, short event, void *p) case CLIENT_STOP_WRITE: goto ro; case CLIENT_RCPT_FAIL: - rcpt = pcb->rcptfail; - mta_rcpt_status(pcb->rcptfail, pcb->reply); - mta_rcpt_log(s, pcb->rcptfail); - mta_rcpt_done(s, pcb->rcptfail); + mta_message_status(pcb->rcptfail, pcb->reply); + mta_message_log(s, pcb->rcptfail); + mta_message_done(s, pcb->rcptfail); goto rw; case CLIENT_DONE: mta_status(s, "%s", pcb->status); @@ -579,13 +621,10 @@ out: client_close(pcb); s->pcb = NULL; - if (TAILQ_EMPTY(&s->recipients)) { - log_debug("%s: leaving", __func__); + if (TAILQ_EMPTY(&s->recipients)) mta_enter_state(s, MTA_DONE, NULL); - } else { - log_debug("%s: connecting to next", __func__); + else mta_enter_state(s, MTA_CONNECT, NULL); - } return; rw: @@ -602,7 +641,7 @@ void mta_status(struct mta_session *s, const char *fmt, ...) { char *status; - struct recipient *rcpt, *next; + struct message *m, *next; va_list ap; va_start(ap, fmt); @@ -610,16 +649,16 @@ mta_status(struct mta_session *s, const char *fmt, ...) fatal("vasprintf"); va_end(ap); - for (rcpt = TAILQ_FIRST(&s->recipients); rcpt; rcpt = next) { - next = TAILQ_NEXT(rcpt, entry); + for (m = TAILQ_FIRST(&s->recipients); m; m = next) { + next = TAILQ_NEXT(m, entry); /* save new status */ - mta_rcpt_status(rcpt, status); + mta_message_status(m, status); /* remove queue entry */ if (*status == '2' || *status == '5' || *status == '6') { - mta_rcpt_log(s, rcpt); - mta_rcpt_done(s, rcpt); + mta_message_log(s, m); + mta_message_done(s, m); } } @@ -627,54 +666,58 @@ mta_status(struct mta_session *s, const char *fmt, ...) } void -mta_rcpt_status(struct recipient *rcpt, char *status) +mta_message_status(struct message *m, char *status) { /* * Previous delivery attempts might have assigned an errorline of * higher status (eg. 5yz is of higher status than 4yz), so check * this before deciding to overwrite existing status with a new one. */ - if (*status != '2' && strncmp(rcpt->status, status, 3) > 0) + if (*status != '2' && strncmp(m->session_errorline, status, 3) > 0) return; /* change status */ - log_debug("mta: new status for %s: %s", rcpt->address, status); - strlcpy(rcpt->status, status, sizeof rcpt->status); + log_debug("mta: new status for %s@%s: %s", m->recipient.user, + m->recipient.domain, status); + strlcpy(m->session_errorline, status, sizeof(m->session_errorline)); } void -mta_rcpt_log(struct mta_session *s, struct recipient *rcpt) +mta_message_log(struct mta_session *s, struct message *m) { - struct mta_relay *relay; - - relay = TAILQ_FIRST(&s->relays); + struct mta_relay *relay = TAILQ_FIRST(&s->relays); + char *status = m->session_errorline; - log_info("%s: to=%s, delay=%d, relay=%s [%s], stat=%s (%s)", - queue_be_decode(s->content_id), rcpt->address, - time(NULL) - s->birth, + log_info("%s: to=<%s@%s>, delay=%d, relay=%s [%s], stat=%s (%s)", + m->message_id, m->recipient.user, + m->recipient.domain, time(NULL) - m->creation, relay ? relay->fqdn : "(none)", relay ? ss_to_text(&relay->sa) : "", - rcpt->status[0] == '2' ? "Sent" : - rcpt->status[0] == '5' ? "RemoteError" : - rcpt->status[0] == '4' ? "RemoteError" : "LocalError", - rcpt->status + 4); + *status == '2' ? "Sent" : + *status == '5' ? "RemoteError" : + *status == '4' ? "RemoteError" : "LocalError", + status + 4); } void -mta_rcpt_done(struct mta_session *s, struct recipient *rcpt) +mta_message_done(struct mta_session *s, struct message *m) { - struct action *action; - - action = malloc(sizeof *action + strlen(rcpt->status)); - if (action == NULL) - fatal(NULL); - action->id = rcpt->action_id; - strlcpy(action->data, rcpt->status, strlen(rcpt->status) + 1); - imsg_compose_event(s->env->sc_ievs[PROC_QUEUE], IMSG_BATCH_UPDATE, - s->id, 0, -1, action, sizeof *action + strlen(rcpt->status)); - TAILQ_REMOVE(&s->recipients, rcpt, entry); - free(action); - free(rcpt); + switch (m->session_errorline[0]) { + case '6': + case '5': + m->status = S_MESSAGE_PERMFAILURE; + break; + case '2': + m->status = S_MESSAGE_ACCEPTED; + break; + default: + m->status = S_MESSAGE_TEMPFAILURE; + break; + } + imsg_compose_event(s->env->sc_ievs[PROC_QUEUE], + IMSG_QUEUE_MESSAGE_UPDATE, 0, 0, -1, m, sizeof(*m)); + TAILQ_REMOVE(&s->recipients, m, entry); + free(m); } void @@ -683,4 +726,17 @@ mta_connect_done(int fd, short event, void *p) mta_pickup(p, NULL); } +void +mta_request_datafd(struct mta_session *s) +{ + struct batch b; + struct message *m; + + b.id = s->id; + m = TAILQ_FIRST(&s->recipients); + strlcpy(b.message_id, m->message_id, sizeof(b.message_id)); + imsg_compose_event(s->env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_MESSAGE_FD, + 0, 0, -1, &b, sizeof(b)); +} + SPLAY_GENERATE(mtatree, mta_session, entry, mta_session_cmp); diff --git a/usr.sbin/smtpd/parse.y b/usr.sbin/smtpd/parse.y index 10fe04f1b63..f86ca108e20 100644 --- a/usr.sbin/smtpd/parse.y +++ b/usr.sbin/smtpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.66 2010/09/20 09:01:09 gilles Exp $ */ +/* $OpenBSD: parse.y,v 1.67 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -117,7 +117,7 @@ typedef struct { %} -%token EXPIRE SIZE LISTEN ON ALL PORT +%token QUEUE INTERVAL SIZE LISTEN ON ALL PORT EXPIRE %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 @@ -126,8 +126,9 @@ typedef struct { %token <v.string> STRING %token <v.number> NUMBER %type <v.map> map -%type <v.number> decision port from auth ssl size +%type <v.number> quantifier decision port from auth ssl size %type <v.cond> condition +%type <v.tv> interval %type <v.object> mapref %type <v.string> certname user tag on alias @@ -178,7 +179,23 @@ optnl : '\n' optnl nl : '\n' optnl ; -size : NUMBER { +quantifier : /* empty */ { $$ = 1; } + | 'm' { $$ = 60; } + | 'h' { $$ = 3600; } + | 'd' { $$ = 86400; } + ; + +interval : NUMBER quantifier { + if ($1 < 0) { + yyerror("invalid interval: %lld", $1); + YYERROR; + } + $$.tv_usec = 0; + $$.tv_sec = $1 * $2; + } + ; + +size : NUMBER { if ($1 < 0) { yyerror("invalid size: %lld", $1); YYERROR; @@ -251,14 +268,17 @@ tag : TAG STRING { | /* empty */ { $$ = NULL; } ; -main : EXPIRE STRING { - conf->sc_qexpire = delaytonum($2); - if (conf->sc_qexpire == -1) { +main : QUEUE INTERVAL interval { + conf->sc_qintval = $3; + } + | EXPIRE STRING { + conf->sc_qexpire = delaytonum($2); + if (conf->sc_qexpire == -1) { yyerror("invalid expire delay: %s", $2); YYERROR; } - } - | SIZE size { + } + | SIZE size { conf->sc_maxsize = $2; } | LISTEN ON STRING port ssl certname auth tag { @@ -1053,6 +1073,7 @@ lookup(char *s) { "hash", HASH }, { "hostname", HOSTNAME }, { "include", INCLUDE }, + { "interval", INTERVAL }, { "list", LIST }, { "listen", LISTEN }, { "local", LOCAL }, @@ -1064,6 +1085,7 @@ lookup(char *s) { "on", ON }, { "plain", PLAIN }, { "port", PORT }, + { "queue", QUEUE }, { "reject", REJECT }, { "relay", RELAY }, { "single", SINGLE }, @@ -1456,7 +1478,9 @@ parse_config(struct smtpd *x_conf, const char *filename, int opts) SPLAY_INIT(conf->sc_ssl); SPLAY_INIT(&conf->sc_sessions); - conf->sc_qexpire = SMTPD_EXPIRE; + conf->sc_qexpire = SMTPD_QUEUE_EXPIRY; + conf->sc_qintval.tv_sec = SMTPD_QUEUE_INTERVAL; + conf->sc_qintval.tv_usec = 0; conf->sc_opts = opts; if ((file = pushfile(filename, 0)) == NULL) { @@ -1843,45 +1867,45 @@ set_localaddrs(void) int delaytonum(char *str) { - unsigned int factor; - size_t len; - const char *errstr = NULL; - int delay; - + unsigned int factor; + size_t len; + const char *errstr = NULL; + int delay; + /* we need at least 1 digit and 1 unit */ len = strlen(str); if (len < 2) goto bad; - + switch(str[len - 1]) { - + case 's': factor = 1; break; - + case 'm': factor = 60; break; - + case 'h': factor = 60 * 60; break; - + case 'd': factor = 24 * 60 * 60; break; - + default: goto bad; } - + str[len - 1] = '\0'; delay = strtonum(str, 1, INT_MAX / factor, &errstr); if (errstr) goto bad; - + return (delay * factor); - + bad: return (-1); } diff --git a/usr.sbin/smtpd/parser.c b/usr.sbin/smtpd/parser.c index 06936329691..03e759d0e5f 100644 --- a/usr.sbin/smtpd/parser.c +++ b/usr.sbin/smtpd/parser.c @@ -1,4 +1,4 @@ -/* $OpenBSD: parser.c,v 1.15 2010/09/04 21:31:04 tedu Exp $ */ +/* $OpenBSD: parser.c,v 1.16 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -58,7 +58,6 @@ struct token { static const struct token t_main[]; static const struct token t_show[]; -static const struct token t_show_queue[]; static const struct token t_pause[]; static const struct token t_resume[]; static const struct token t_schedule[]; @@ -79,18 +78,12 @@ static const struct token t_main[] = { }; static const struct token t_show[] = { - {KEYWORD, "queue", SHOW_QUEUE, t_show_queue}, + {KEYWORD, "queue", SHOW_QUEUE, NULL}, {KEYWORD, "runqueue", SHOW_RUNQUEUE, NULL}, {KEYWORD, "stats", SHOW_STATS, NULL}, {ENDTOKEN, "", NONE, NULL} }; -static const struct token t_show_queue[] = { - {NOTOKEN, "", NONE, NULL}, - {KEYWORD, "raw", SHOW_QUEUE_RAW, NULL}, - {ENDTOKEN, "", NONE, NULL} -}; - static const struct token t_pause[] = { {KEYWORD, "local", PAUSE_MDA, NULL}, {KEYWORD, "outgoing", PAUSE_MTA, NULL}, @@ -106,12 +99,12 @@ static const struct token t_resume[] = { }; static const struct token t_schedule[] = { - {VARIABLE, "message", SCHEDULE, NULL}, + {VARIABLE, "message id/uid", SCHEDULE, NULL}, {ENDTOKEN, "", NONE, NULL} }; static const struct token t_remove[] = { - {VARIABLE, "message", REMOVE, NULL}, + {VARIABLE, "message id/uid", REMOVE, NULL}, {ENDTOKEN, "", NONE, NULL} }; @@ -122,7 +115,7 @@ static const struct token t_log[] = { }; static const struct token *match_token(const char *, const struct token [], - struct parse_result *res); + struct parse_result *); static void show_valid_args(const struct token []); struct parse_result * @@ -158,7 +151,7 @@ parse(int argc, char *argv[]) return (&res); } -static const struct token * +const struct token * match_token(const char *word, const struct token table[], struct parse_result *res) { diff --git a/usr.sbin/smtpd/parser.h b/usr.sbin/smtpd/parser.h index 170c193710f..d9fefe838ad 100644 --- a/usr.sbin/smtpd/parser.h +++ b/usr.sbin/smtpd/parser.h @@ -1,4 +1,4 @@ -/* $OpenBSD: parser.h,v 1.14 2010/09/04 21:31:04 tedu Exp $ */ +/* $OpenBSD: parser.h,v 1.15 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -26,7 +26,6 @@ enum actions { LOG_VERBOSE, LOG_BRIEF, SHOW_QUEUE, - SHOW_QUEUE_RAW, SHOW_RUNQUEUE, SHOW_STATS, PAUSE_MDA, diff --git a/usr.sbin/smtpd/queue.c b/usr.sbin/smtpd/queue.c index a5a38a335ca..6d774e85bd9 100644 --- a/usr.sbin/smtpd/queue.c +++ b/usr.sbin/smtpd/queue.c @@ -1,7 +1,6 @@ -/* $OpenBSD: queue.c,v 1.90 2010/09/20 09:01:09 gilles Exp $ */ +/* $OpenBSD: queue.c,v 1.91 2010/10/09 22:05:35 gilles Exp $ */ /* - * Copyright (c) 2008-2010 Jacek Masiulaniec <jacekm@dobremiasto.net> * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> * @@ -24,14 +23,11 @@ #include <sys/param.h> #include <sys/socket.h> #include <sys/stat.h> -#include <sys/uio.h> -#include <ctype.h> #include <errno.h> #include <event.h> #include <fcntl.h> #include <libgen.h> -#include <math.h> #include <pwd.h> #include <signal.h> #include <stdio.h> @@ -40,315 +36,156 @@ #include <unistd.h> #include "smtpd.h" -#include "queue_backend.h" -#include "client.h" -void queue_imsg(struct smtpd *, struct imsgev *, struct imsg *); -int queue_append(struct incoming *, char *); -void queue_destroy(struct incoming *); -int queue_control(u_int64_t, int); -__dead void queue_shutdown(void); -void queue_sig_handler(int, short, void *); - -void queue_mem_init(struct smtpd *); -void queue_mem_content_unref(struct content *); - -void queue_send(int, short, void *); -void queue_expire(struct batch *); -void queue_update(int, int, u_int64_t, char *); -void queue_done(int, int); -void queue_schedule(int, struct batch *); -void queue_sleep(int); -time_t queue_retry(int, time_t, time_t); - -void queue_bounce_wait(struct content *); -void queue_bounce_schedule(int, short, void *); -void queue_bounce_init(int, int); -void queue_bounce_event(int, short, void *); - -int queue_detect_loop(struct incoming *); - -struct incoming *incoming_alloc(u_int64_t); -struct batch *incoming_batch(struct incoming *, char *); -void incoming_schedule(struct incoming *); - -struct content *content_alloc(u_int64_t); - -struct batch *batch_alloc(struct content *, char *); - -struct action *action_alloc(u_int64_t); -void action_insert(struct action *, struct batch *); -struct action *action_grow(struct action *, char *); -void action_free(struct action *); - -int batchsort(const void *, const void *); - -/* table of batches in larval state */ -void **incoming; -int incoming_sz; - -struct queue runqs[3]; +void queue_imsg(struct smtpd *, struct imsgev *, struct imsg *); +void queue_pass_to_runner(struct smtpd *, struct imsgev *, struct imsg *); +__dead void queue_shutdown(void); +void queue_sig_handler(int, short, void *); +void queue_setup_events(struct smtpd *); +void queue_disable_events(struct smtpd *); +void queue_purge(char *); + +int queue_create_layout_message(char *, char *); +void queue_delete_layout_message(char *, char *); +int queue_record_layout_envelope(char *, struct message *); +int queue_remove_layout_envelope(char *, struct message *); +int queue_commit_layout_message(char *, struct message *); +int queue_open_layout_messagefile(char *, struct message *); void queue_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { - struct action *update; - struct incoming *s; + struct submit_status ss; struct message *m; - u_int64_t content_id; - int i, fd, error; - struct iovec iov[2]; - char aux[2048]; /* XXX */ + struct batch *b; + int fd, ret; if (iev->proc == PROC_SMTP) { - switch (imsg->hdr.type) { - case IMSG_QUEUE_CREATE: - /* - * Create file that will hold mail content. Its name - * uniquely identifies entire mail transaction. Actions - * will refer to the this file as source of mail content. - */ - if (queue_be_content_create(&content_id) < 0) - content_id = INVALID_ID; - - s = incoming_alloc(content_id); - if (s == NULL) - fatal(NULL); + m = imsg->data; - i = table_alloc(&incoming, &incoming_sz); - incoming[i] = s; - - iov[0].iov_base = &content_id; - iov[0].iov_len = sizeof 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_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"); - - queue_destroy(s); - incoming[i] = NULL; + 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); return; - case IMSG_QUEUE_OPEN: - /* - * Open the file that will hold mail content. - */ - memcpy(&i, imsg->data, sizeof i); - - s = table_lookup(incoming, incoming_sz, i); - if (s == NULL) - fatalx("queue: bogus open req"); - - fd = queue_be_content_open(s->content->id, 1); - if (fd < 0) - fatal("queue: content open error"); - - imsg_compose_event(iev, IMSG_QUEUE_OPEN, - imsg->hdr.peerid, 0, fd, NULL, 0); + case IMSG_QUEUE_REMOVE_MESSAGE: + if (m->flags & F_MESSAGE_ENQUEUED) + enqueue_delete_message(m->message_id); + else + queue_delete_incoming_message(m->message_id); return; - case IMSG_QUEUE_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"); - - 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; + case IMSG_QUEUE_COMMIT_MESSAGE: + ss.id = m->session_id; + if (m->flags & F_MESSAGE_ENQUEUED) { + if (enqueue_commit_message(m)) + env->stats->queue.inserts_local++; + else + ss.code = 421; + } else { + if (queue_commit_incoming_message(m)) + env->stats->queue.inserts_remote++; + else + ss.code = 421; } + imsg_compose_event(iev, IMSG_QUEUE_COMMIT_MESSAGE, 0, 0, -1, + &ss, sizeof ss); + return; - env->stats->queue.inserts++; - env->stats->queue.length++; - - incoming_schedule(s); - incoming[i] = NULL; - for (i = 0; i < s->nlocal; i++) - free(s->local[i]); - free(s->local); - free(s); - - error = 0; - imsg_compose_event(iev, IMSG_QUEUE_CLOSE, - imsg->hdr.peerid, 0, -1, &error, sizeof error); + case IMSG_QUEUE_MESSAGE_FILE: + ss.id = m->session_id; + if (m->flags & F_MESSAGE_ENQUEUED) + fd = enqueue_open_messagefile(m); + else + fd = queue_open_incoming_message_file(m); + if (fd == -1) + ss.code = 421; + imsg_compose_event(iev, IMSG_QUEUE_MESSAGE_FILE, 0, 0, fd, + &ss, sizeof ss); return; case IMSG_SMTP_ENQUEUE: - queue_bounce_init(imsg->hdr.peerid, imsg->fd); + queue_pass_to_runner(env, iev, imsg); return; } } if (iev->proc == PROC_LKA) { - switch (imsg->hdr.type) { - case IMSG_QUEUE_APPEND: - m = imsg->data; - - s = table_lookup(incoming, incoming_sz, m->queue_id); - if (s == NULL) - fatalx("queue: bogus append"); + m = imsg->data; - switch (m->recipient.rule.r_action) { - case A_MBOX: - case A_MAILDIR: - case A_EXT: - /* ?|from|to|user1|user2|path */ - if (m->recipient.rule.r_action == A_MBOX) - strlcpy(aux, "M|", sizeof aux); - else if (m->recipient.rule.r_action == A_MAILDIR) - strlcpy(aux, "D|", sizeof aux); - else - strlcpy(aux, "P|", sizeof aux); - if (m->sender.user[0] && m->sender.domain[0]) { - strlcat(aux, m->sender.user, sizeof aux); - strlcat(aux, "@", sizeof aux); - strlcat(aux, m->sender.domain, sizeof aux); - } - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->session_rcpt.user, sizeof aux); - strlcat(aux, "@", sizeof aux); - strlcat(aux, m->session_rcpt.domain, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->sender.pw_name, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->recipient.pw_name, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->recipient.rule.r_value.buffer, sizeof aux); - break; + switch (imsg->hdr.type) { + case IMSG_QUEUE_SUBMIT_ENVELOPE: + m->id = generate_uid(); + ss.id = m->session_id; - case A_FILENAME: - /* F|from|to|user1|user2|path */ - strlcpy(aux, "F|", sizeof aux); - if (m->sender.user[0] && m->sender.domain[0]) { - strlcat(aux, m->sender.user, sizeof aux); - strlcat(aux, "@", sizeof aux); - strlcat(aux, m->sender.domain, sizeof aux); - } - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->session_rcpt.user, sizeof aux); - strlcat(aux, "@", sizeof aux); - strlcat(aux, m->session_rcpt.domain, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->sender.pw_name, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, SMTPD_USER, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->recipient.u.filename, sizeof aux); - break; + if (IS_MAILBOX(m->recipient) || IS_EXT(m->recipient)) + m->type = T_MDA_MESSAGE; + else + m->type = T_MTA_MESSAGE; - case A_RELAY: - case A_RELAYVIA: - /* R|from|to|user|rcpt|via|port|ssl|cert|auth */ - strlcpy(aux, "R|", sizeof aux); - if (m->sender.user[0] && m->sender.domain[0]) { - strlcat(aux, m->sender.user, sizeof aux); - strlcat(aux, "@", sizeof aux); - strlcat(aux, m->sender.domain, sizeof aux); - } - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->session_rcpt.user, sizeof aux); - strlcat(aux, "@", sizeof aux); - strlcat(aux, m->session_rcpt.domain, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->sender.pw_name, sizeof aux); - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->recipient.user, sizeof aux); - strlcat(aux, "@", sizeof aux); - strlcat(aux, m->recipient.domain, sizeof aux); - strlcat(aux, "|", sizeof aux); - if (m->recipient.rule.r_action == A_RELAYVIA) - strlcat(aux, m->recipient.rule.r_value.relayhost.hostname, sizeof aux); - strlcat(aux, "|", sizeof aux); - if (m->recipient.rule.r_value.relayhost.port) { - char port[10]; - snprintf(port, sizeof port, "%d", ntohs(m->recipient.rule.r_value.relayhost.port)); - strlcat(aux, port, sizeof aux); - } - strlcat(aux, "|", sizeof aux); - switch (m->recipient.rule.r_value.relayhost.flags & F_SSL) { - case F_SSL: - strlcat(aux, "ssl", sizeof aux); - break; - case F_SMTPS: - strlcat(aux, "smtps", sizeof aux); - break; - case F_STARTTLS: - strlcat(aux, "starttls", sizeof aux); - break; - } - strlcat(aux, "|", sizeof aux); - strlcat(aux, m->recipient.rule.r_value.relayhost.cert, sizeof aux); - strlcat(aux, "|", sizeof aux); - if (m->recipient.rule.r_value.relayhost.flags & F_AUTH) - strlcat(aux, "secrets", sizeof aux); - break; + /* Write to disk */ + if (m->flags & F_MESSAGE_ENQUEUED) + ret = enqueue_record_envelope(m); + else + ret = queue_record_incoming_envelope(m); - default: - fatalx("queue: bad r_action"); + if (ret == 0) { + ss.code = 421; + imsg_compose_event(env->sc_ievs[PROC_SMTP], + IMSG_QUEUE_TEMPFAIL, 0, 0, -1, &ss, + sizeof ss); } + return; - 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); + case IMSG_QUEUE_COMMIT_ENVELOPES: + ss.id = m->session_id; + ss.code = 250; + imsg_compose_event(env->sc_ievs[PROC_SMTP], + IMSG_QUEUE_COMMIT_ENVELOPES, 0, 0, -1, &ss, + sizeof ss); return; } } - if (iev->proc == PROC_MDA) { + if (iev->proc == PROC_RUNNER) { + /* forward imsgs from runner on its behalf */ + imsg_compose_event(env->sc_ievs[imsg->hdr.peerid], imsg->hdr.type, + 0, imsg->hdr.pid, imsg->fd, (char *)imsg->data, + imsg->hdr.len - sizeof imsg->hdr); + return; + } + + if (iev->proc == PROC_MTA) { switch (imsg->hdr.type) { - case IMSG_BATCH_UPDATE: - update = imsg->data; - queue_update(Q_LOCAL, imsg->hdr.peerid, update->id, - update->data); + case IMSG_QUEUE_MESSAGE_FD: + b = imsg->data; + fd = queue_open_message_file(b->message_id); + imsg_compose_event(iev, IMSG_QUEUE_MESSAGE_FD, 0, 0, + fd, b, sizeof *b); return; + case IMSG_QUEUE_MESSAGE_UPDATE: case IMSG_BATCH_DONE: - queue_done(Q_LOCAL, imsg->hdr.peerid); + queue_pass_to_runner(env, iev, imsg); return; - } } - if (iev->proc == PROC_MTA) { + if (iev->proc == PROC_MDA) { switch (imsg->hdr.type) { - case IMSG_BATCH_UPDATE: - update = imsg->data; - queue_update(Q_RELAY, imsg->hdr.peerid, update->id, - update->data); - return; - - case IMSG_BATCH_DONE: - queue_done(Q_RELAY, imsg->hdr.peerid); + case IMSG_QUEUE_MESSAGE_UPDATE: + case IMSG_MDA_SESS_NEW: + queue_pass_to_runner(env, iev, imsg); return; } } @@ -356,59 +193,25 @@ queue_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_CONTROL) { switch (imsg->hdr.type) { case IMSG_QUEUE_PAUSE_LOCAL: - runqs[Q_LOCAL].max = 0; - queue_sleep(Q_LOCAL); - return; - - case IMSG_QUEUE_PAUSE_RELAY: - runqs[Q_RELAY].max = 0; - queue_sleep(Q_RELAY); - return; - + case IMSG_QUEUE_PAUSE_OUTGOING: case IMSG_QUEUE_RESUME_LOCAL: - runqs[Q_LOCAL].max = env->sc_maxconn; - queue_sleep(Q_LOCAL); - return; - - case IMSG_QUEUE_RESUME_RELAY: - runqs[Q_RELAY].max = env->sc_maxconn; - queue_sleep(Q_RELAY); - return; - + case IMSG_QUEUE_RESUME_OUTGOING: case IMSG_QUEUE_SCHEDULE: - memcpy(&content_id, imsg->data, sizeof content_id); - error = queue_control(content_id, 1); - if (error) - log_warnx("schedule request failed"); - else { - queue_sleep(Q_LOCAL); - queue_sleep(Q_RELAY); - queue_sleep(Q_BOUNCE); - } - imsg_compose_event(iev, IMSG_QUEUE_SCHEDULE, - imsg->hdr.peerid, 0, -1, &error, sizeof error); - return; - case IMSG_QUEUE_REMOVE: - memcpy(&content_id, imsg->data, sizeof content_id); - error = queue_control(content_id, 0); - if (error) - log_warnx("remove request failed"); - else { - queue_sleep(Q_LOCAL); - queue_sleep(Q_RELAY); - queue_sleep(Q_BOUNCE); - } - imsg_compose_event(iev, IMSG_QUEUE_REMOVE, - imsg->hdr.peerid, 0, -1, &error, sizeof error); + queue_pass_to_runner(env, iev, imsg); return; } } if (iev->proc == PROC_PARENT) { switch (imsg->hdr.type) { + case IMSG_PARENT_ENQUEUE_OFFLINE: + queue_pass_to_runner(env, iev, imsg); + return; + case IMSG_CTL_VERBOSE: log_verbose(*(int *)imsg->data); + queue_pass_to_runner(env, iev, imsg); return; } } @@ -416,141 +219,12 @@ queue_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) fatalx("queue_imsg: unexpected imsg"); } -int -queue_append(struct incoming *s, char *auxraw) -{ - struct batch *batch; - struct action *action; - char *copy; - struct aux aux; - u_int64_t action_id; - - 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 = incoming_batch(s, ""); - else if (aux.relay_via[0]) - batch = incoming_batch(s, aux.relay_via); - else - batch = incoming_batch(s, strchr(aux.rcpt, '@')); - - if (batch == NULL) - fatal(NULL); - - free(copy); - - if (queue_be_action_new(s->content->id, &action_id, auxraw) < 0) - return -1; - - action = action_alloc(action_id); - if (action == NULL) - fatal(NULL); - - action_insert(action, batch); - - return 0; -} - void -queue_destroy(struct incoming *s) +queue_pass_to_runner(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { - struct batch *batch; - struct action *action; - u_int rq; - int i; - - for (rq = 0; rq < nitems(s->batches); rq++) { - while ((batch = SLIST_FIRST(&s->batches[rq]))) { - SLIST_REMOVE_HEAD(&s->batches[rq], entry); - while ((action = SLIST_FIRST(&batch->actions))) { - SLIST_REMOVE_HEAD(&batch->actions, entry); - queue_be_action_delete(s->content->id, - action->id); - action_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); - action_free(action); - } - free(b); - } - } - - return (n > 0 ? 0 : -1); + imsg_compose_event(env->sc_ievs[PROC_RUNNER], imsg->hdr.type, + iev->proc, imsg->hdr.pid, imsg->fd, imsg->data, + imsg->hdr.len - sizeof imsg->hdr); } void @@ -569,16 +243,26 @@ queue_sig_handler(int sig, short event, void *p) void queue_shutdown(void) { - log_info("queue exiting"); + log_info("queue handler exiting"); _exit(0); } +void +queue_setup_events(struct smtpd *env) +{ +} + +void +queue_disable_events(struct smtpd *env) +{ +} + pid_t queue(struct smtpd *env) { pid_t pid; struct passwd *pw; - u_int rq; + struct event ev_sigint; struct event ev_sigterm; @@ -588,7 +272,8 @@ queue(struct smtpd *env) { PROC_SMTP, imsg_dispatch }, { PROC_MDA, imsg_dispatch }, { PROC_MTA, imsg_dispatch }, - { PROC_LKA, imsg_dispatch } + { PROC_LKA, imsg_dispatch }, + { PROC_RUNNER, imsg_dispatch } }; switch (pid = fork()) { @@ -617,38 +302,9 @@ queue(struct smtpd *env) setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) fatal("queue: cannot drop privileges"); - /* - * Queue opens fds for four purposes: smtp, mta, mda, and bounces. - * Therefore, use all available fd space and set the maxconn (=max - * session count for each of these tasks) to a quarter of this value. - */ - fdlimit(1.0); - if ((env->sc_maxconn = availdesc() / 4) < 1) - fatalx("queue: fd starvation"); - imsg_callback = queue_imsg; event_init(); - config_pipes(env, peers, nitems(peers)); - config_peers(env, peers, nitems(peers)); - - for (rq = 0; rq < nitems(runqs); rq++) { - SLIST_INIT(&runqs[rq].head); - runqs[rq].env = env; - runqs[rq].max = env->sc_maxconn; - } - runqs[Q_LOCAL].name = "Q_LOCAL"; - runqs[Q_RELAY].name = "Q_RELAY"; - runqs[Q_BOUNCE].name = "Q_BOUNCE"; - - /* bouncing costs 2 fds: file and socket */ - runqs[Q_BOUNCE].max /= 2; - - queue_mem_init(env); - queue_sleep(Q_LOCAL); - queue_sleep(Q_RELAY); - queue_sleep(Q_BOUNCE); - signal_set(&ev_sigint, SIGINT, queue_sig_handler, env); signal_set(&ev_sigterm, SIGTERM, queue_sig_handler, env); signal_add(&ev_sigint, NULL); @@ -656,876 +312,66 @@ queue(struct smtpd *env) signal(SIGPIPE, SIG_IGN); signal(SIGHUP, SIG_IGN); - if (event_dispatch() < 0) - fatal("event_dispatch"); - queue_shutdown(); - - return (0); -} - -void -queue_mem_init(struct smtpd *env) -{ - SLIST_HEAD(,batch) bhash[4096]; - void **btab; - struct content *content; - struct action *action; - struct batch *batch; - char *sortkey; - struct action_be a; - struct aux aux; - int btab_sz, nbtab, rq, i; - - for (i = 0; i < 4096; i++) - SLIST_INIT(&bhash[i]); - btab = NULL; - btab_sz = 0; - nbtab = 0; - /* - * Sort actions into batches. - */ - for (;;) { - if (queue_be_getnext(&a) < 0) - fatal("queue: backend error"); - if (a.action_id == 0) - break; - auxsplit(&aux, a.aux); - - /* - * Assignment to batch is based on the sortkey: - * B=<content_id> for bounced mail - * R=<domain> for relayed mail - * L=<action_id> for local mail - */ - if (a.status[0] == '5' || a.status[0] == '6') - asprintf(&sortkey, "B=%s", queue_be_decode(a.content_id)); - else if (aux.mode[0] == 'R') { - if (aux.relay_via[0]) - asprintf(&sortkey, "R=%s", aux.relay_via); - else - asprintf(&sortkey, "R=%s", strchr(aux.rcpt, '@')); - } else - asprintf(&sortkey, "L=%s", queue_be_decode(a.action_id)); - - content = NULL; - SLIST_FOREACH(batch, &bhash[a.content_id & 4095], entry) { - if (batch->content->id == a.content_id) { - content = batch->content; - if (strcmp(batch->sortkey, sortkey) == 0) - break; - } - } - - if (batch == NULL) { - if (content == NULL) { - content = content_alloc(a.content_id); - if (content == NULL) - fatal("queue_mem_init"); - env->stats->queue.length++; - } - - batch = batch_alloc(content, sortkey); - if (batch == NULL) - fatal("queue_mem_init"); - - if (*sortkey == 'B') - rq = Q_BOUNCE; - else if (*sortkey == 'R') - rq = Q_RELAY; - else - rq = Q_LOCAL; - - batch->retry = queue_retry(rq, a.birth, a.birth); - while (batch->retry < time(NULL)) - batch->retry = queue_retry(rq, a.birth, - batch->retry); - - if (batch->retry > a.birth + env->sc_qexpire) - batch->retry = NO_RETRY_EXPIRED; - - SLIST_INSERT_HEAD(&bhash[a.content_id & 4095], batch, - entry); - if (nbtab == btab_sz) { - btab_sz *= 2; - btab = realloc(btab, ++btab_sz * sizeof *btab); - if (btab == NULL) - fatal("queue_mem_init"); - } - btab[nbtab] = batch; - nbtab++; - } - - action = action_alloc(a.action_id); - if (action == NULL) - fatal("queue_mem_init"); - - action_insert(action, batch); - - free(sortkey); - } - - /* - * Add batches to schedule. + * queue opens fds for four purposes: smtp, mta, mda, and bounces. + * Therefore, use all available fd space and set the maxconn (=max + * session count for mta and mda) to a quarter of this value. */ - qsort(btab, nbtab, sizeof *btab, batchsort); - for (i = 0; i < nbtab; i++) { - batch = btab[i]; - if (batch->sortkey[0] == 'B') - rq = Q_BOUNCE; - else if (batch->sortkey[0] == 'R') - rq = Q_RELAY; - else - rq = Q_LOCAL; - queue_schedule(rq, batch); - } - - free(btab); -} - -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; - 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"); - - action = action_grow(action, a.aux); - if (action == NULL) - fatal(NULL); - - imsg_compose_event(env->sc_ievs[to], IMSG_BATCH_APPEND, - i, 0, -1, action, sizeof *action + strlen(a.aux)); - - action_free(action); - } - - imsg_compose_event(env->sc_ievs[to], IMSG_BATCH_CLOSE, i, 0, -1, - &a.birth, sizeof a.birth); - } - - /* Sanity check: were we called for no good reason? */ - if (i == -1) - fatalx("queue_send: empty run"); - - queue_sleep(rq); -} - -void -queue_expire(struct batch *batch) -{ - struct action *action, *fail; - struct action_be a; - struct aux aux; - time_t birth; - int error; - - action = SLIST_FIRST(&batch->actions); - if (queue_be_action_read(&a, batch->content->id, action->id) < 0) - fatal("queue: action read error"); - - auxsplit(&aux, a.aux); - birth = a.birth; - - if (a.status[0] == '5' || a.status[0] == '6') { - log_warnx("%s: to=%s, delay=%d, stat=Expired (no bounce due " - "to: larval bounce)", - queue_be_decode(batch->content->id), aux.mail_from, - time(NULL) - birth); - - while ((action = SLIST_FIRST(&batch->actions))) { - SLIST_REMOVE_HEAD(&batch->actions, entry); - queue_be_action_delete(batch->content->id, action->id); - queue_mem_content_unref(batch->content); - action_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); - action_free(action); - } - return; - } - - SLIST_FOREACH(action, &batch->actions, entry) - if (queue_be_action_status(batch->content->id, action->id, - "600 Message expired after too many delivery attempts") < 0) - break; - - if (action) { - fail = action; - error = errno; - } else { - fail = NULL; - error = 0; - } - - while ((action = SLIST_FIRST(&batch->actions))) { - if (action == fail) - break; - SLIST_REMOVE_HEAD(&batch->actions, entry); - - if (queue_be_action_read(&a, batch->content->id, - action->id) < 0) - fatal("queue: action read error"); - auxsplit(&aux, a.aux); - - log_info("%s: to=%s, delay=%d, stat=Expired", - queue_be_decode(batch->content->id), rcpt_pretty(&aux), - time(NULL) - birth); - - SLIST_INSERT_HEAD(&batch->content->actions, action, entry); - queue_bounce_wait(batch->content); - } - - while ((action = SLIST_FIRST(&batch->actions))) { - SLIST_REMOVE_HEAD(&batch->actions, entry); - - if (queue_be_action_read(&a, batch->content->id, - action->id) < 0) - fatal("queue: action read error"); - auxsplit(&aux, a.aux); - - log_warnx("%s: to=%s, delay=%d, stat=Expired (no bounce due " - "to: %s)", - queue_be_decode(batch->content->id), rcpt_pretty(&aux), - time(NULL) - birth, strerror(error)); - - queue_be_action_delete(batch->content->id, action->id); - queue_mem_content_unref(batch->content); - action_free(action); - } -} - -void -queue_update(int rq, int i, u_int64_t action_id, char *new_status) -{ - struct batch *batch; - struct action *action; - struct action_be a; - struct aux aux; - - batch = table_lookup(runqs[rq].session, runqs[rq].session_sz, i); - if (batch == NULL) - fatalx("queue: bogus update"); - - if (*new_status == '2') { - queue_be_action_delete(batch->content->id, action_id); - queue_mem_content_unref(batch->content); - return; - } - - action = malloc(sizeof *action); - if (action == NULL) - fatal(NULL); - action->id = action_id; - - if (*new_status == '5' || *new_status == '6') { - if (queue_be_action_read(&a, batch->content->id, action_id) < 0) - fatal("queue: queue read error"); - - auxsplit(&aux, a.aux); - - if (aux.mail_from[0] == '\0') { - log_warnx("%s: bounce recipient %s not contactable, " - "bounce dropped", - queue_be_decode(batch->content->id), aux.rcpt_to); - queue_be_action_delete(batch->content->id, action_id); - queue_mem_content_unref(batch->content); - action_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); - action_free(action); - return; - } - - SLIST_INSERT_HEAD(&batch->content->actions, action, entry); - - queue_bounce_wait(batch->content); - } else { - queue_be_action_status(batch->content->id, action_id, new_status); - SLIST_INSERT_HEAD(&batch->actions, action, entry); - } -} - -void -queue_done(int rq, int i) -{ - struct action_be a; - struct batch *batch; - struct action *action; - - /* Take batch off the session table. */ - batch = table_lookup(runqs[rq].session, runqs[rq].session_sz, i); - if (batch == NULL) - fatalx("queue: bogus batch"); - runqs[rq].session[i] = NULL; - runqs[rq].sessions--; - - log_debug("%s: %d: done", runqs[rq].name, i); - - /* All actions sent? */ - if (SLIST_EMPTY(&batch->actions)) { - if (batch->content->ref == 0) { - free(batch->content->ev); - free(batch->content); - } - free(batch); - } else { - /* Batch has actions with temporary errors. */ - action = SLIST_FIRST(&batch->actions); - if (queue_be_action_read(&a, batch->content->id, - action->id) < 0) - fatal("queue: action read error"); - batch->retry = queue_retry(rq, a.birth, batch->retry); - if (batch->retry > a.birth + runqs[rq].env->sc_qexpire) - batch->retry = NO_RETRY_EXPIRED; - queue_schedule(rq, batch); - } - - queue_sleep(rq); -} - -/* - * Insert batch into runqueue in retry time order. - */ -void -queue_schedule(int rq, struct batch *batch) -{ - 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; + fdlimit(1.0); + if ((env->sc_maxconn = availdesc() / 4) < 1) + fatalx("runner: fd starvation"); - evtimer_del(&runqs[rq].ev); + config_pipes(env, peers, nitems(peers)); + config_peers(env, peers, nitems(peers)); - if (runqs[rq].sessions >= runqs[rq].max) - return; + queue_purge(PATH_INCOMING); + queue_purge(PATH_ENQUEUE); - next = SLIST_FIRST(&runqs[rq].head); - if (next == NULL) - return; + queue_setup_events(env); - 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 == '2' || *status == '5' || *status == '6') { - 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); - action_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); -} - -struct incoming * -incoming_alloc(u_int64_t content_id) -{ - struct incoming *s; - u_int rq; - - s = calloc(1, sizeof *s); - if (s == NULL) - return NULL; - for (rq = 0; rq < nitems(s->batches); rq++) - SLIST_INIT(&s->batches[rq]); - - s->content = content_alloc(content_id); - if (s->content == NULL) { - free(s); - return NULL; - } + if (event_dispatch() < 0) + fatal("event_dispatch"); + queue_shutdown(); - return s; + return (0); } struct batch * -incoming_batch(struct incoming *s, char *sortkey) -{ - struct batch *batch; - 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 = batch_alloc(s->content, sortkey); - if (batch == NULL) - return NULL; - SLIST_INSERT_HEAD(&s->batches[rq], batch, entry); - } - - return batch; -} - -void -incoming_schedule(struct incoming *s) -{ - struct batch *batch; - u_int rq; - - for (rq = 0; rq < nitems(s->batches); rq++) { - while ((batch = SLIST_FIRST(&s->batches[rq]))) { - SLIST_REMOVE_HEAD(&s->batches[rq], entry); - batch->retry = RETRY_NOW; - queue_schedule(rq, batch); - } - } - - queue_sleep(Q_LOCAL); - queue_sleep(Q_RELAY); -} - -struct content * -content_alloc(u_int64_t content_id) +batch_by_id(struct smtpd *env, u_int64_t id) { - struct content *content; + struct batch lookup; - content = calloc(1, sizeof *content); - if (content == NULL) - return NULL; - - content->id = content_id; - - return content; + lookup.id = id; + return SPLAY_FIND(batchtree, &env->batch_queue, &lookup); } -struct batch * -batch_alloc(struct content *content, char *sortkey) -{ - struct batch *batch; - - batch = calloc(1, sizeof *batch + strlen(sortkey)); - if (batch == NULL) - return NULL; - - SLIST_INIT(&batch->actions); - batch->content = content; - strlcpy(batch->sortkey, sortkey, strlen(sortkey) + 1); - return batch; -} - -struct action * -action_alloc(u_int64_t action_id) +void +queue_purge(char *queuepath) { - struct action *action; + char path[MAXPATHLEN]; + struct qwalk *q; - action = malloc(sizeof *action); - if (action == NULL) - return NULL; + q = qwalk_new(queuepath); - action->id = action_id; + while (qwalk(q, path)) + queue_delete_layout_message(queuepath, basename(path)); - return action; + qwalk_close(q); } void -action_free(struct action *action) +queue_submit_envelope(struct smtpd *env, struct message *message) { - free(action); + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_QUEUE_SUBMIT_ENVELOPE, 0, 0, -1, + message, sizeof(struct message)); } void -action_insert(struct action *action, struct batch *batch) +queue_commit_envelopes(struct smtpd *env, struct message *message) { - SLIST_INSERT_HEAD(&batch->actions, action, entry); - batch->content->ref++; -} - -struct action * -action_grow(struct action *action, char *aux) -{ - struct action *new; - - new = realloc(action, sizeof *new + strlen(aux)); - if (new == NULL) - return NULL; - - strlcpy(new->data, aux, strlen(aux) + 1); - - return new; + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_QUEUE_COMMIT_ENVELOPES, 0, 0, -1, + message, sizeof(struct message)); } diff --git a/usr.sbin/smtpd/smtp.c b/usr.sbin/smtpd/smtp.c index d7a2e5a83fe..5728e02f839 100644 --- a/usr.sbin/smtpd/smtp.c +++ b/usr.sbin/smtpd/smtp.c @@ -1,9 +1,9 @@ -/* $OpenBSD: smtp.c,v 1.75 2010/06/02 19:16:53 chl Exp $ */ +/* $OpenBSD: smtp.c,v 1.76 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> - * Copyright (c) 2009-2010 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -40,7 +40,6 @@ #include <unistd.h> #include "smtpd.h" -#include "queue_backend.h" void smtp_imsg(struct smtpd *, struct imsgev *, struct imsg *); __dead void smtp_shutdown(void); @@ -48,22 +47,21 @@ void smtp_sig_handler(int, short, void *); void smtp_setup_events(struct smtpd *); void smtp_disable_events(struct smtpd *); void smtp_pause(struct smtpd *); -int smtp_enqueue(struct smtpd *, uid_t); +int smtp_enqueue(struct smtpd *, uid_t *); void smtp_accept(int, short, void *); struct session *smtp_new(struct listener *); -struct session *session_lookup(struct smtpd *, u_int32_t); - -u_int32_t smtp_id; +struct session *session_lookup(struct smtpd *, u_int64_t); void smtp_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { + struct session skey; + struct submit_status *ss; struct listener *l; struct session *s; struct auth *auth; struct ssl *ssl; struct dns *dns; - int status, fd; if (iev->proc == PROC_LKA) { switch (imsg->hdr.type) { @@ -84,56 +82,82 @@ smtp_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_MFA) { switch (imsg->hdr.type) { - case IMSG_MFA_MAIL: case IMSG_MFA_RCPT: - s = session_lookup(env, imsg->hdr.peerid); + case IMSG_MFA_MAIL: + log_debug("smtp: got imsg_mfa_mail/rcpt"); + ss = imsg->data; + s = session_lookup(env, ss->id); if (s == NULL) return; - memcpy(&status, imsg->data, sizeof status); - s->s_msg.status |= status; - session_pickup(s); + session_pickup(s, ss); return; } } if (iev->proc == PROC_QUEUE) { + ss = imsg->data; + switch (imsg->hdr.type) { - case IMSG_QUEUE_CREATE: - s = session_lookup(env, imsg->hdr.peerid); + case IMSG_QUEUE_CREATE_MESSAGE: + log_debug("smtp: imsg_queue_create_message returned"); + s = session_lookup(env, ss->id); if (s == NULL) return; - memcpy(&s->content_id, imsg->data, sizeof s->content_id); - memcpy(&s->queue_id, (u_int64_t *)imsg->data + 1, - sizeof s->queue_id); - if (s->content_id == INVALID_ID) - s->s_msg.status |= S_MESSAGE_TEMPFAILURE; - session_pickup(s); + strlcpy(s->s_msg.message_id, ss->u.msgid, + sizeof s->s_msg.message_id); + session_pickup(s, ss); return; - case IMSG_QUEUE_OPEN: - s = session_lookup(env, imsg->hdr.peerid); + case IMSG_QUEUE_MESSAGE_FILE: + log_debug("smtp: imsg_queue_message_file returned"); + s = session_lookup(env, ss->id); if (s == NULL) { close(imsg->fd); return; } - s->datafp = fdopen(imsg->fd, "a"); - if (s->datafp == NULL) - fatal("smtp: fdopen"); - session_pickup(s); + s->datafp = fdopen(imsg->fd, "w"); + if (s->datafp == NULL) { + /* queue may have experienced tempfail. */ + if (ss->code != 421) + fatalx("smtp: fdopen"); + close(imsg->fd); + } + session_pickup(s, ss); + return; + + case IMSG_QUEUE_TEMPFAIL: + log_debug("smtp: got imsg_queue_tempfail"); + skey.s_id = ss->id; + s = SPLAY_FIND(sessiontree, &env->sc_sessions, &skey); + if (s == NULL) + fatalx("smtp: session is gone"); + if (s->s_flags & F_WRITEONLY) + /* session is write-only, must not destroy it. */ + s->s_msg.status |= S_MESSAGE_TEMPFAILURE; + else + fatalx("smtp: corrupt session"); return; - case IMSG_QUEUE_CLOSE: - s = session_lookup(env, imsg->hdr.peerid); + case IMSG_QUEUE_COMMIT_ENVELOPES: + log_debug("smtp: got imsg_queue_commit_envelopes"); + s = session_lookup(env, ss->id); if (s == NULL) return; - memcpy(&status, imsg->data, sizeof status); - s->s_msg.status |= status; - session_pickup(s); + session_pickup(s, ss); + return; + + case IMSG_QUEUE_COMMIT_MESSAGE: + log_debug("smtp: got imsg_queue_commit_message"); + s = session_lookup(env, ss->id); + if (s == NULL) + return; + session_pickup(s, ss); return; case IMSG_SMTP_ENQUEUE: - fd = smtp_enqueue(env, getuid()); - imsg_compose_event(iev, IMSG_SMTP_ENQUEUE, imsg->hdr.peerid, 0, fd, NULL, 0); + imsg_compose_event(iev, IMSG_SMTP_ENQUEUE, 0, 0, + smtp_enqueue(env, NULL), imsg->data, + sizeof(struct message)); return; } } @@ -226,7 +250,7 @@ smtp_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) s->s_flags &= ~F_AUTHENTICATED; s->s_msg.flags &= ~F_MESSAGE_AUTHENTICATED; } - session_pickup(s); + session_pickup(s, NULL); return; case IMSG_CTL_VERBOSE: @@ -238,9 +262,9 @@ smtp_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_CONTROL) { switch (imsg->hdr.type) { case IMSG_SMTP_ENQUEUE: - fd = smtp_enqueue(env, *(uid_t *)imsg->data); imsg_compose_event(iev, IMSG_SMTP_ENQUEUE, - imsg->hdr.peerid, 0, fd, NULL, 0); + imsg->hdr.peerid, 0, smtp_enqueue(env, imsg->data), + NULL, 0); return; case IMSG_SMTP_PAUSE: @@ -414,7 +438,7 @@ smtp_resume(struct smtpd *env) } int -smtp_enqueue(struct smtpd *env, uid_t uid) +smtp_enqueue(struct smtpd *env, uid_t *euid) { static struct listener local, *l; static struct sockaddr_storage sa; @@ -454,8 +478,16 @@ smtp_enqueue(struct smtpd *env, uid_t uid) s->s_fd = fd[0]; s->s_ss = sa; + s->s_msg.flags |= F_MESSAGE_ENQUEUED; + + if (euid) + bsnprintf(s->s_hostname, sizeof(s->s_hostname), "%d@localhost", + *euid); + else { + strlcpy(s->s_hostname, "localhost", sizeof(s->s_hostname)); + s->s_msg.flags |= F_MESSAGE_BOUNCE; + } - bsnprintf(s->s_hostname, sizeof(s->s_hostname), "%d@localhost", uid); strlcpy(s->s_msg.session_hostname, s->s_hostname, sizeof(s->s_msg.session_hostname)); @@ -485,6 +517,7 @@ smtp_accept(int fd, short event, void *p) dns_query_ptr(l->env, &s->s_ss, s->s_id); } + struct session * smtp_new(struct listener *l) { @@ -504,7 +537,7 @@ smtp_new(struct listener *l) if ((s = calloc(1, sizeof(*s))) == NULL) fatal(NULL); - s->s_id = smtp_id++; + s->s_id = generate_uid(); s->s_env = env; s->s_l = l; strlcpy(s->s_msg.tag, l->tag, sizeof(s->s_msg.tag)); @@ -520,7 +553,7 @@ smtp_new(struct listener *l) * Helper function for handling IMSG replies. */ struct session * -session_lookup(struct smtpd *env, u_int32_t id) +session_lookup(struct smtpd *env, u_int64_t id) { struct session key; struct session *s; diff --git a/usr.sbin/smtpd/smtp_session.c b/usr.sbin/smtpd/smtp_session.c index 2b38e952f20..1d2ce5ef787 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.135 2010/06/01 23:06:23 jacekm Exp $ */ +/* $OpenBSD: smtp_session.c,v 1.136 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -41,7 +41,6 @@ #include <unistd.h> #include "smtpd.h" -#include "queue_backend.h" int session_rfc5321_helo_handler(struct session *, char *); int session_rfc5321_ehlo_handler(struct session *, char *); @@ -425,6 +424,8 @@ session_rfc5321_mail_handler(struct session *s, char *args) s->s_msg.session_id = s->s_id; s->s_msg.session_ss = s->s_ss; + log_debug("session_rfc5321_mail_handler: sending notification to mfa"); + session_imsg(s, PROC_MFA, IMSG_MFA_MAIL, 0, 0, -1, &s->s_msg, sizeof(s->s_msg)); return 1; @@ -449,8 +450,6 @@ session_rfc5321_rcpt_handler(struct session *s, char *args) return 1; } - s->s_msg.queue_id = s->queue_id; - s->s_state = S_RCPT_MFA; session_imsg(s, PROC_MFA, IMSG_MFA_RCPT, 0, 0, -1, &s->s_msg, @@ -488,8 +487,8 @@ session_rfc5321_data_handler(struct session *s, char *args) s->s_state = S_DATA_QUEUE; - session_imsg(s, PROC_QUEUE, IMSG_QUEUE_OPEN, s->s_id, 0, -1, - &s->queue_id, sizeof s->queue_id); + session_imsg(s, PROC_QUEUE, IMSG_QUEUE_MESSAGE_FILE, 0, 0, -1, + &s->s_msg, sizeof(s->s_msg)); return 1; } @@ -600,12 +599,13 @@ rfc5321: } void -session_pickup(struct session *s) +session_pickup(struct session *s, struct submit_status *ss) { if (s == NULL) fatal("session_pickup: desynchronized"); - if (s->s_msg.status & S_MESSAGE_TEMPFAILURE) { + if ((ss != NULL && ss->code == 421) || + (s->s_msg.status & S_MESSAGE_TEMPFAILURE)) { session_respond(s, "421 Service temporarily unavailable"); s->s_env->stats->smtp.tempfail++; s->s_flags |= F_QUIT; @@ -635,36 +635,48 @@ session_pickup(struct session *s) break; case S_MAIL_MFA: - if (s->s_msg.status & S_MESSAGE_PERMFAILURE) { + if (ss == NULL) + fatalx("bad ss at S_MAIL_MFA"); + if (ss->code != 250) { s->s_state = S_HELO; - session_respond(s, "530 Sender rejected"); + session_respond(s, "%d Sender rejected", ss->code); return; } + s->s_state = S_MAIL_QUEUE; - session_imsg(s, PROC_QUEUE, IMSG_QUEUE_CREATE, s->s_id, 0, -1, - NULL, 0); + s->s_msg.sender = ss->u.path; + + session_imsg(s, PROC_QUEUE, IMSG_QUEUE_CREATE_MESSAGE, 0, 0, -1, + &s->s_msg, sizeof(s->s_msg)); break; case S_MAIL_QUEUE: + if (ss == NULL) + fatalx("bad ss at S_MAIL_QUEUE"); s->s_state = S_MAIL; - session_respond(s, "250 2.1.0 Sender ok"); + session_respond(s, "%d 2.1.0 Sender ok", ss->code); break; case S_RCPT_MFA: + if (ss == NULL) + fatalx("bad ss at S_RCPT_MFA"); /* recipient was not accepted */ - if (s->s_msg.status & S_MESSAGE_PERMFAILURE) { + if (ss->code != 250) { /* We do not have a valid recipient, downgrade state */ if (s->rcptcount == 0) s->s_state = S_MAIL; else s->s_state = S_RCPT; - session_respond(s, "530 5.0.0 Recipient rejected: %s@%s", + session_respond(s, "%d 5.0.0 Recipient rejected: %s@%s", ss->code, s->s_msg.session_rcpt.user, s->s_msg.session_rcpt.domain); return; } - s->rcptcount++; + s->s_state = S_RCPT; - session_respond(s, "250 2.0.0 Recipient ok"); + s->rcptcount++; + s->s_msg.recipient = ss->u.path; + + session_respond(s, "%d 2.0.0 Recipient ok", ss->code); break; case S_DATA_QUEUE: @@ -676,7 +688,7 @@ session_pickup(struct session *s) s->s_msg.session_helo, s->s_hostname, ss_to_text(&s->s_ss)); fprintf(s->datafp, "\tby %s (OpenSMTPD) with %sSMTP id %s", s->s_env->sc_hostname, s->s_flags & F_EHLO ? "E" : "", - queue_be_decode(s->content_id)); + s->s_msg.message_id); if (s->s_flags & F_SECURE) { fprintf(s->datafp, "\n\t(version=%s cipher=%s bits=%d)", @@ -695,24 +707,23 @@ session_pickup(struct session *s) break; case S_DONE: - if (s->s_msg.status & S_MESSAGE_PERMFAILURE) - session_respond(s, "554 5.4.6 Routing loop detected"); - else { - session_respond(s, "250 2.0.0 %s Message accepted for delivery", - queue_be_decode(s->content_id)); - log_info("%s: from=%s%s%s, size=%ld, nrcpts=%zd, " - "relay=%s [%s]", - queue_be_decode(s->content_id), - s->s_msg.sender.user, - s->s_msg.sender.user[0] == '\0' ? "" : "@", - s->s_msg.sender.domain, - s->s_datalen, - s->rcptcount, - s->s_hostname, - ss_to_text(&s->s_ss)); - } + session_respond(s, "250 2.0.0 %s Message accepted for delivery", + s->s_msg.message_id); + log_info("%s: from=<%s%s%s>, size=%ld, nrcpts=%zd, proto=%s, " + "relay=%s [%s]", + s->s_msg.message_id, + s->s_msg.sender.user, + s->s_msg.sender.user[0] == '\0' ? "" : "@", + s->s_msg.sender.domain, + s->s_datalen, + s->rcptcount, + s->s_flags & F_EHLO ? "ESMTP" : "SMTP", + s->s_hostname, + ss_to_text(&s->s_ss)); + s->s_state = S_HELO; - s->content_id = 0; + s->s_msg.message_id[0] = '\0'; + s->s_msg.message_uid[0] = '\0'; bzero(&s->s_nresp, sizeof(s->s_nresp)); break; @@ -732,7 +743,7 @@ session_init(struct listener *l, struct session *s) } session_bufferevent_new(s); - session_pickup(s); + session_pickup(s, NULL); } void @@ -816,7 +827,7 @@ session_read_data(struct session *s, char *line) if (strcmp(line, ".") == 0) { s->s_datalen = ftell(s->datafp); - if (fclose(s->datafp) == EOF) + if (! safe_fclose(s->datafp)) s->s_msg.status |= S_MESSAGE_TEMPFAILURE; s->datafp = NULL; @@ -828,8 +839,8 @@ session_read_data(struct session *s, char *line) s->s_flags |= F_QUIT; s->s_env->stats->smtp.tempfail++; } else { - session_imsg(s, PROC_QUEUE, IMSG_QUEUE_CLOSE, - s->s_id, 0, -1, &s->queue_id, sizeof s->queue_id); + session_imsg(s, PROC_QUEUE, IMSG_QUEUE_COMMIT_MESSAGE, + 0, 0, -1, &s->s_msg, sizeof(s->s_msg)); s->s_state = S_DONE; } @@ -967,12 +978,10 @@ session_destroy(struct session *s) if (s->datafp != NULL) fclose(s->datafp); - if (s->content_id && s->s_state != S_DONE) { - log_debug("%s: deleting queue session", __func__); + if (s->s_msg.message_id[0] != '\0' && s->s_state != S_DONE) imsg_compose_event(s->s_env->sc_ievs[PROC_QUEUE], - IMSG_QUEUE_DELETE, 0, 0, -1, &s->queue_id, - sizeof s->queue_id); - } + IMSG_QUEUE_REMOVE_MESSAGE, 0, 0, -1, &s->s_msg, + sizeof(s->s_msg)); ssl_session_destroy(s); @@ -992,6 +1001,7 @@ session_destroy(struct session *s) } SPLAY_REMOVE(sessiontree, &s->s_env->sc_sessions, s); + bzero(s, sizeof(*s)); free(s); } @@ -1089,10 +1099,9 @@ session_respond(struct session *s, char *fmt, ...) switch (EVBUFFER_DATA(EVBUFFER_OUTPUT(s->s_bev))[n]) { case '5': case '4': - log_info("(none): from=<%s%s%s>, relay=%s [%s], stat=LocalError (%.*s)", - s->s_msg.sender.user, - s->s_msg.sender.user[0] == '\0' ? "" : "@", - s->s_msg.sender.domain, + log_info("%s: from=<%s@%s>, relay=%s [%s], stat=LocalError (%.*s)", + s->s_msg.message_id[0] ? s->s_msg.message_id : "(none)", + s->s_msg.sender.user, s->s_msg.sender.domain, s->s_hostname, ss_to_text(&s->s_ss), (int)EVBUFFER_LENGTH(EVBUFFER_OUTPUT(s->s_bev)) - n - 2, EVBUFFER_DATA(EVBUFFER_OUTPUT(s->s_bev))); diff --git a/usr.sbin/smtpd/smtpctl.8 b/usr.sbin/smtpd/smtpctl.8 index e0c5a41d39b..1afc397dfff 100644 --- a/usr.sbin/smtpd/smtpctl.8 +++ b/usr.sbin/smtpd/smtpctl.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: smtpctl.8,v 1.16 2010/06/01 23:06:25 jacekm Exp $ +.\" $OpenBSD: smtpctl.8,v 1.17 2010/10/09 22:05:35 gilles 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: June 1 2010 $ +.Dd $Mdocdate: October 9 2010 $ .Dt SMTPCTL 8 .Os .Sh NAME @@ -43,19 +43,20 @@ Temporarily stop deliveries to local users. .It Cm pause outgoing Temporarily stop relaying and deliveries to remote users. -.It Cm remove Ar message-id | Cm all -Removes messages with the same message ID, or all messages. +.It Cm remove Ar message-uid | message-id +Removes a single message, or all messages with the same message ID. .It Cm resume incoming Resume accepting incoming sessions. .It Cm resume local Resume deliveries to local users. .It Cm resume outgoing Resume relaying and deliveries to remote users. -.It Cm schedule Ar message-id | Cm all -Marks messages with the same message ID, or all messages, +.It Cm schedule Ar message-uid | message-id +Marks a single message, or all messages with the same message ID, as ready for immediate delivery. -.It Cm show queue Op Ic raw -Displays undelivered messages. +.It Cm show queue +Displays information concerning envelopes +that are currently in a queue. .It Cm show runqueue Displays information concerning envelopes that are scheduled for delivery. diff --git a/usr.sbin/smtpd/smtpctl.c b/usr.sbin/smtpd/smtpctl.c index f3154d73f84..3fd4619712a 100644 --- a/usr.sbin/smtpd/smtpctl.c +++ b/usr.sbin/smtpd/smtpctl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpctl.c,v 1.51 2010/06/01 23:06:25 jacekm Exp $ */ +/* $OpenBSD: smtpctl.c,v 1.52 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -43,11 +43,9 @@ #include "smtpd.h" #include "parser.h" -#include "queue_backend.h" __dead void usage(void); int show_command_output(struct imsg*); -void show_queue(int); int show_stats_output(struct imsg *); int proctype; @@ -75,7 +73,6 @@ main(int argc, char *argv[]) struct sockaddr_un sun; struct parse_result *res = NULL; struct imsg imsg; - u_int64_t content_id; int ctl_sock; int done = 0; int n, verbose = 0; @@ -86,7 +83,7 @@ main(int argc, char *argv[]) else if (strcmp(__progname, "mailq") == 0) { if (geteuid()) errx(1, "need root privileges"); - show_queue(0); + show_queue(PATH_QUEUE, 0); return 0; } else if (strcmp(__progname, "smtpctl") == 0) { /* check for root privileges */ @@ -99,12 +96,10 @@ main(int argc, char *argv[]) /* handle "disconnected" commands */ switch (res->action) { case SHOW_QUEUE: - show_queue(0); - break; - case SHOW_QUEUE_RAW: - show_queue(1); + show_queue(PATH_QUEUE, 0); break; case SHOW_RUNQUEUE: + show_queue(PATH_RUNQUEUE, 0); break; default: goto connected; @@ -151,7 +146,7 @@ connected: imsg_compose(ibuf, IMSG_QUEUE_PAUSE_LOCAL, 0, 0, -1, NULL, 0); break; case PAUSE_MTA: - imsg_compose(ibuf, IMSG_QUEUE_PAUSE_RELAY, 0, 0, -1, NULL, 0); + imsg_compose(ibuf, IMSG_QUEUE_PAUSE_OUTGOING, 0, 0, -1, NULL, 0); break; case PAUSE_SMTP: imsg_compose(ibuf, IMSG_SMTP_PAUSE, 0, 0, -1, NULL, 0); @@ -160,7 +155,7 @@ connected: imsg_compose(ibuf, IMSG_QUEUE_RESUME_LOCAL, 0, 0, -1, NULL, 0); break; case RESUME_MTA: - imsg_compose(ibuf, IMSG_QUEUE_RESUME_RELAY, 0, 0, -1, NULL, 0); + imsg_compose(ibuf, IMSG_QUEUE_RESUME_OUTGOING, 0, 0, -1, NULL, 0); break; case RESUME_SMTP: imsg_compose(ibuf, IMSG_SMTP_RESUME, 0, 0, -1, NULL, 0); @@ -168,26 +163,24 @@ connected: case SHOW_STATS: imsg_compose(ibuf, IMSG_STATS, 0, 0, -1, NULL, 0); break; - case SCHEDULE: - if (strcmp(res->data, "all") == 0) - content_id = 0; - else - content_id = queue_be_encode(res->data); - if (content_id == INVALID_ID) - errx(1, "invalid id: %s", res->data); - imsg_compose(ibuf, IMSG_QUEUE_SCHEDULE, 0, 0, -1, &content_id, - sizeof content_id); + case SCHEDULE: { + struct sched s; + + s.fd = -1; + bzero(s.mid, sizeof (s.mid)); + strlcpy(s.mid, res->data, sizeof (s.mid)); + imsg_compose(ibuf, IMSG_QUEUE_SCHEDULE, 0, 0, -1, &s, sizeof (s)); break; - case REMOVE: - if (strcmp(res->data, "all") == 0) - content_id = 0; - else - content_id = queue_be_encode(res->data); - if (content_id == INVALID_ID) - errx(1, "invalid id: %s", res->data); - imsg_compose(ibuf, IMSG_QUEUE_REMOVE, 0, 0, -1, &content_id, - sizeof content_id); + } + case REMOVE: { + struct remove s; + + s.fd = -1; + bzero(s.mid, sizeof (s.mid)); + strlcpy(s.mid, res->data, sizeof (s.mid)); + imsg_compose(ibuf, IMSG_QUEUE_REMOVE, 0, 0, -1, &s, sizeof (s)); break; + } case MONITOR: /* XXX */ break; @@ -271,76 +264,6 @@ show_command_output(struct imsg *imsg) return (1); } -void -show_queue(int raw) -{ - struct action_be a; - struct aux aux; - int ret, title; - - if (chdir(PATH_SPOOL) < 0) - err(1, "chdir"); - - title = 0; - for (;;) { - ret = queue_be_getnext(&a); - if (ret == -1) - err(1, "getnext error"); - if (ret == -2) - continue; /* unlinked during scan */ - if (a.content_id == 0) - break; /* end of queue */ - if (a.birth == 0) - continue; /* uncommitted */ - auxsplit(&aux, a.aux); - - if (raw) { - printf("%s|", queue_be_decode(a.content_id)); - printf("%d|", a.birth); - printf("%s|%s|", aux.mode, aux.mail_from); - if (aux.mode[0] == 'R') - printf("%s", aux.rcpt); - else - printf("%s", aux.rcpt_to); - printf("\n"); - continue; - } - - if (title == 0) { - printf("Message Envelope\n"); - title = 1; - } - printf("%s ", queue_be_decode(a.content_id)); - if (aux.user_from[0]) - printf("%s (", aux.user_from); - if (aux.mail_from[0]) - printf("%s", aux.mail_from); - else - printf("BOUNCE"); - if (aux.user_from[0]) - printf(")"); - printf(" -> "); - if (aux.mode[0] == 'R') { - printf("%s", aux.rcpt); - if (strcmp(aux.rcpt, aux.rcpt_to) != 0) - printf(" (%s)", aux.rcpt_to); - } else { - if (aux.user_to[0]) - printf("%s (", aux.user_to); - printf("%s", aux.rcpt_to); - if (aux.user_to[0]) - printf(")"); - } - if (a.status[0]) { - if (a.status[0] == '1' || a.status[0] == '6') - printf(" [%s]", a.status + 4); - else - printf(" [\"%s\"]", a.status + 4); - } - printf("\n"); - } -} - int show_stats_output(struct imsg *imsg) { @@ -365,8 +288,12 @@ show_stats_output(struct imsg *imsg) printf("parent.uptime=%d\n", time(NULL) - stats->parent.start); - printf("queue.inserts=%zd\n", stats->queue.inserts); - printf("queue.length=%zd\n", stats->queue.length); + printf("queue.inserts.local=%zd\n", stats->queue.inserts_local); + printf("queue.inserts.remote=%zd\n", stats->queue.inserts_remote); + + printf("runner.active=%zd\n", stats->runner.active); + printf("runner.bounces=%zd\n", stats->runner.bounces); + printf("runner.bounces.active=%zd\n", stats->runner.bounces_active); printf("smtp.errors.delays=%zd\n", stats->smtp.delays); printf("smtp.errors.linetoolong=%zd\n", stats->smtp.linetoolong); diff --git a/usr.sbin/smtpd/smtpctl/Makefile b/usr.sbin/smtpd/smtpctl/Makefile index 56324b18885..9d794d0ff24 100644 --- a/usr.sbin/smtpd/smtpctl/Makefile +++ b/usr.sbin/smtpd/smtpctl/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.15 2010/06/01 23:06:25 jacekm Exp $ +# $OpenBSD: Makefile,v 1.16 2010/10/09 22:05:35 gilles Exp $ .PATH: ${.CURDIR}/.. @@ -17,7 +17,7 @@ CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual CFLAGS+= -Wsign-compare -Wbounded CFLAGS+= -DCLIENT_NO_SSL -SRCS= smtpctl.c parser.c log.c enqueue.c queue_backend.c util.c client.c +SRCS= smtpctl.c parser.c log.c enqueue.c queue_shared.c util.c client.c LDADD+= -lutil -levent DPADD+= ${LIBUTIL} ${LIBEVENT} .include <bsd.prog.mk> diff --git a/usr.sbin/smtpd/smtpd.c b/usr.sbin/smtpd/smtpd.c index 6827d2e78e2..34d4f399d9b 100644 --- a/usr.sbin/smtpd/smtpd.c +++ b/usr.sbin/smtpd/smtpd.c @@ -1,9 +1,9 @@ -/* $OpenBSD: smtpd.c,v 1.112 2010/06/02 19:16:53 chl Exp $ */ +/* $OpenBSD: smtpd.c,v 1.113 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> - * Copyright (c) 2009-2010 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -28,7 +28,6 @@ #include <sys/uio.h> #include <sys/mman.h> -#include <dirent.h> #include <err.h> #include <errno.h> #include <event.h> @@ -46,7 +45,6 @@ #include <unistd.h> #include "smtpd.h" -#include "queue_backend.h" void parent_imsg(struct smtpd *, struct imsgev *, struct imsg *); __dead void usage(void); @@ -59,8 +57,10 @@ void parent_sig_handler(int, short, void *); void forkmda(struct smtpd *, struct imsgev *, u_int32_t, struct deliver *); -void parent_enqueue_offline(struct smtpd *); +int parent_enqueue_offline(struct smtpd *, char *); int parent_forward_open(char *); +int setup_spool(uid_t, gid_t); +int path_starts_with(char *, char *); void fork_peers(struct smtpd *); @@ -68,11 +68,11 @@ struct child *child_add(struct smtpd *, pid_t, int, int); void child_del(struct smtpd *, pid_t); struct child *child_lookup(struct smtpd *, pid_t); -void setup_spool(struct passwd *); - extern char **environ; void (*imsg_callback)(struct smtpd *, struct imsgev *, struct imsg *); +int __b64_pton(char const *, unsigned char *, size_t); + void parent_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { @@ -116,6 +116,17 @@ parent_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) } } + if (iev->proc == PROC_QUEUE) { + switch (imsg->hdr.type) { + case IMSG_PARENT_ENQUEUE_OFFLINE: + if (! parent_enqueue_offline(env, imsg->data)) + imsg_compose_event(iev, + IMSG_PARENT_ENQUEUE_OFFLINE, 0, 0, -1, + NULL, 0); + return; + } + } + if (iev->proc == PROC_MDA) { switch (imsg->hdr.type) { case IMSG_PARENT_FORK_MDA: @@ -169,7 +180,6 @@ parent_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) } } - log_warnx("parent got imsg %d from %d", imsg->hdr.type, iev->proc); fatalx("parent_imsg: unexpected imsg"); } @@ -374,10 +384,12 @@ parent_sig_handler(int sig, short event, void *p) case CHILD_ENQUEUE_OFFLINE: if (fail) log_warnx("couldn't enqueue offline " - "message; child %s", cause); + "message; smtpctl %s", cause); else log_debug("offline message enqueued"); - parent_enqueue_offline(env); + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_PARENT_ENQUEUE_OFFLINE, 0, 0, -1, + NULL, 0); break; default: @@ -471,11 +483,12 @@ main(int argc, char *argv[]) if (geteuid()) errx(1, "need root privileges"); - if ((env.sc_pw = getpwnam(SMTPD_USER)) == NULL) + if ((env.sc_pw = getpwnam(SMTPD_USER)) == NULL) errx(1, "unknown user %s", SMTPD_USER); - setup_spool(env.sc_pw); - + if (!setup_spool(env.sc_pw->pw_uid, 0)) + errx(1, "invalid directory permissions"); + log_init(debug); log_verbose(verbose); @@ -518,8 +531,6 @@ main(int argc, char *argv[]) bzero(&tv, sizeof(tv)); evtimer_add(&env.sc_ev, &tv); - parent_enqueue_offline(&env); - if (event_dispatch() < 0) fatal("event_dispatch"); @@ -553,6 +564,7 @@ fork_peers(struct smtpd *env) env->sc_instances[PROC_MTA] = 1; env->sc_instances[PROC_PARENT] = 1; env->sc_instances[PROC_QUEUE] = 1; + env->sc_instances[PROC_RUNNER] = 1; env->sc_instances[PROC_SMTP] = 1; init_pipes(env); @@ -563,6 +575,7 @@ fork_peers(struct smtpd *env) env->sc_title[PROC_MFA] = "mail filter agent"; env->sc_title[PROC_MTA] = "mail transfer agent"; env->sc_title[PROC_QUEUE] = "queue"; + env->sc_title[PROC_RUNNER] = "runner"; env->sc_title[PROC_SMTP] = "smtp server"; child_add(env, control(env), CHILD_DAEMON, PROC_CONTROL); @@ -571,6 +584,7 @@ fork_peers(struct smtpd *env) child_add(env, mfa(env), CHILD_DAEMON, PROC_MFA); child_add(env, mta(env), CHILD_DAEMON, PROC_MTA); child_add(env, queue(env), CHILD_DAEMON, PROC_QUEUE); + child_add(env, runner(env), CHILD_DAEMON, PROC_RUNNER); child_add(env, smtp(env), CHILD_DAEMON, PROC_SMTP); setproctitle("[priv]"); @@ -617,6 +631,136 @@ child_lookup(struct smtpd *env, pid_t pid) return SPLAY_FIND(childtree, &env->children, &key); } +int +setup_spool(uid_t uid, gid_t gid) +{ + unsigned int n; + char *paths[] = { PATH_INCOMING, PATH_ENQUEUE, PATH_QUEUE, + PATH_RUNQUEUE, PATH_PURGE, + PATH_OFFLINE, PATH_BOUNCE }; + char pathname[MAXPATHLEN]; + struct stat sb; + int ret; + + if (! bsnprintf(pathname, sizeof(pathname), "%s", PATH_SPOOL)) + fatal("snprintf"); + + if (stat(pathname, &sb) == -1) { + if (errno != ENOENT) { + warn("stat: %s", pathname); + return 0; + } + + if (mkdir(pathname, 0711) == -1) { + warn("mkdir: %s", pathname); + return 0; + } + + if (chown(pathname, 0, 0) == -1) { + warn("chown: %s", pathname); + return 0; + } + + if (stat(pathname, &sb) == -1) + err(1, "stat: %s", pathname); + } + + /* check if it's a directory */ + if (!S_ISDIR(sb.st_mode)) { + warnx("%s is not a directory", pathname); + return 0; + } + + /* check that it is owned by uid/gid */ + if (sb.st_uid != 0 || sb.st_gid != 0) { + warnx("%s must be owned by root:wheel", pathname); + return 0; + } + + /* check permission */ + if ((sb.st_mode & (S_IRUSR|S_IWUSR|S_IXUSR)) != (S_IRUSR|S_IWUSR|S_IXUSR) || + (sb.st_mode & (S_IRGRP|S_IWGRP|S_IXGRP)) != S_IXGRP || + (sb.st_mode & (S_IROTH|S_IWOTH|S_IXOTH)) != S_IXOTH) { + warnx("%s must be rwx--x--x (0711)", pathname); + return 0; + } + + ret = 1; + for (n = 0; n < nitems(paths); n++) { + mode_t mode; + uid_t owner; + gid_t group; + + if (!strcmp(paths[n], PATH_OFFLINE)) { + mode = 01777; + owner = 0; + group = 0; + } else { + mode = 0700; + owner = uid; + group = gid; + } + + if (! bsnprintf(pathname, sizeof(pathname), "%s%s", PATH_SPOOL, + paths[n])) + fatal("snprintf"); + + if (stat(pathname, &sb) == -1) { + if (errno != ENOENT) { + warn("stat: %s", pathname); + ret = 0; + continue; + } + + /* chmod is deffered to avoid umask effect */ + if (mkdir(pathname, 0) == -1) { + ret = 0; + warn("mkdir: %s", pathname); + } + + if (chown(pathname, owner, group) == -1) { + ret = 0; + warn("chown: %s", pathname); + } + + if (chmod(pathname, mode) == -1) { + ret = 0; + warn("chmod: %s", pathname); + } + + if (stat(pathname, &sb) == -1) + err(1, "stat: %s", pathname); + } + + /* check if it's a directory */ + if (!S_ISDIR(sb.st_mode)) { + ret = 0; + warnx("%s is not a directory", pathname); + } + + /* check that it is owned by owner/group */ + if (sb.st_uid != owner) { + ret = 0; + warnx("%s is not owned by uid %d", pathname, owner); + } + if (sb.st_gid != group) { + ret = 0; + warnx("%s is not owned by gid %d", pathname, group); + } + + /* check permission */ + if ((sb.st_mode & 07777) != mode) { + char mode_str[12]; + + ret = 0; + strmode(mode, mode_str); + mode_str[10] = '\0'; + warnx("%s must be %s (%o)", pathname, mode_str + 1, mode); + } + } + return ret; +} + void imsg_event_add(struct imsgev *iev) { @@ -640,7 +784,6 @@ imsg_compose_event(struct imsgev *iev, u_int16_t type, u_int32_t peerid, { if (imsg_compose(&iev->ibuf, type, peerid, pid, fd, data, datalen) == -1) fatal("imsg_compose_event"); - imsg_event_add(iev); } @@ -659,11 +802,8 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, errno = 0; pw = getpwnam(deliver->user); if (pw == NULL) { - if (errno) - n = snprintf(ebuf, sizeof ebuf, "getpwnam: %s", - strerror(errno)); - else - n = snprintf(ebuf, sizeof ebuf, "user not found"); + n = snprintf(ebuf, sizeof ebuf, "getpwnam: %s", + errno ? strerror(errno) : "no such user"); imsg_compose_event(iev, IMSG_MDA_DONE, id, 0, -1, ebuf, n + 1); return; } @@ -746,8 +886,7 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, /* avoid hangs by setting 5m timeout */ alarm(300); - /* external mda */ - if (deliver->mode == 'P') { + if (deliver->mode == A_EXT) { char *environ_new[2]; environ_new[0] = "PATH=" _PATH_DEFPATH; @@ -758,8 +897,7 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, error("execle"); } - /* internal mda: maildir */ - if (deliver->mode == 'D') { + if (deliver->mode == A_MAILDIR) { char tmp[PATH_MAX], new[PATH_MAX]; int ch, fd; FILE *fp; @@ -789,6 +927,10 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, break; if (ferror(stdin)) error2("read error"); + if (fflush(fp) == EOF || ferror(fp)) + error2("write error"); + if (fsync(fd) < 0) + error2("fsync"); if (fclose(fp) == EOF) error2("fclose"); snprintf(new, sizeof new, "new/%s", tmp + 4); @@ -798,8 +940,7 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, } #undef error2 - /* internal mda: file */ - if (deliver->mode == 'F') { + if (deliver->mode == A_FILENAME) { struct stat sb; time_t now; size_t len; @@ -834,6 +975,10 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, if (ferror(stdin)) error2("read error"); putc('\n', fp); + if (fflush(fp) == EOF || ferror(fp)) + error2("write error"); + if (fsync(fd) < 0) + error2("fsync"); if (fclose(fp) == EOF) error2("fclose"); _exit(0); @@ -844,103 +989,114 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, #undef error #undef error2 -void -parent_enqueue_offline(struct smtpd *env) +int +parent_enqueue_offline(struct smtpd *env, char *runner_path) { - char path[MAXPATHLEN], *line, charstr[2], *envp[2], *tmp; - struct stat sb; - struct dirent *de; + char path[MAXPATHLEN]; struct passwd *pw; - DIR *dir; + struct stat sb; pid_t pid; - arglist args; - int fd, line_sz; - fd = -1; - pw = NULL; + log_debug("parent_enqueue_offline: path %s", runner_path); - dir = opendir(PATH_SPOOL PATH_OFFLINE); - if (dir == NULL) - fatal("opendir"); + if (! bsnprintf(path, sizeof(path), "%s%s", PATH_SPOOL, runner_path)) + fatalx("parent_enqueue_offline: filename too long"); - while ((de = readdir(dir))) { - if (de->d_name[0] == '.') - continue; - snprintf(path, sizeof path, "%s%s/%s", PATH_SPOOL, - PATH_OFFLINE, de->d_name); - log_debug("%s: file %s", __func__, path); - fd = open(path, O_RDONLY); - if (fd < 0) - continue; - if (fchflags(fd, 0) < 0 || fstat(fd, &sb) < 0 || - unlink(path) < 0 || !S_ISREG(sb.st_mode) || - (pw = getpwuid(sb.st_uid)) == NULL) { - close(fd); - continue; + if (! path_starts_with(path, PATH_SPOOL PATH_OFFLINE)) + fatalx("parent_enqueue_offline: path outside offline dir"); + + if (lstat(path, &sb) == -1) { + if (errno == ENOENT) { + log_warn("parent_enqueue_offline: %s", path); + return (0); } - break; + fatal("parent_enqueue_offline: lstat"); } - closedir(dir); - - if (de == NULL) - return; + if (chflags(path, 0) == -1) { + if (errno == ENOENT) { + log_warn("parent_enqueue_offline: %s", path); + return (0); + } + fatal("parent_enqueue_offline: chflags"); + } - pid = fork(); - if (pid < 0) - fatal("fork"); + errno = 0; + if ((pw = getpwuid(sb.st_uid)) == NULL) { + log_warn("parent_enqueue_offline: getpwuid for uid %d failed", + sb.st_uid); + unlink(path); + return (0); + } - if (pid) { - child_add(env, pid, CHILD_ENQUEUE_OFFLINE, -1); - return; + if (! S_ISREG(sb.st_mode)) { + log_warnx("file %s (uid %d) not regular, removing", path, sb.st_uid); + if (S_ISDIR(sb.st_mode)) + rmdir(path); + else + unlink(path); + return (0); } - if (chdir(pw->pw_dir) < 0 && chdir("/") < 0) - _exit(1); - if (dup2(fd, STDIN_FILENO) < 0) - _exit(1); - if (closefrom(STDERR_FILENO + 1) < 0) - _exit(1); - if (setgroups(1, &pw->pw_gid) || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) - _exit(1); - if (setsid() < 0) - _exit(1); - if (signal(SIGPIPE, SIG_DFL) == SIG_ERR || - signal(SIGINT, SIG_DFL) == SIG_ERR || - signal(SIGTERM, SIG_DFL) == SIG_ERR || - signal(SIGCHLD, SIG_DFL) == SIG_ERR || - signal(SIGHUP, SIG_DFL) == SIG_ERR) - _exit(1); + if ((pid = fork()) == -1) + fatal("parent_enqueue_offline: fork"); - line = malloc(1); - if (line == NULL) - _exit(1); - line_sz = 1; - line[0] = '\0'; - do { - if (read(STDIN_FILENO, charstr, 1) <= 0) + if (pid == 0) { + char *envp[2], *p, *tmp; + FILE *fp; + size_t len; + arglist args; + + bzero(&args, sizeof(args)); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) || + closefrom(STDERR_FILENO + 1) == -1) { + unlink(path); + _exit(1); + } + + if ((fp = fopen(path, "r")) == NULL) { + unlink(path); + _exit(1); + } + unlink(path); + + if (chdir(pw->pw_dir) == -1 && chdir("/") == -1) + _exit(1); + + if (setsid() == -1 || + signal(SIGPIPE, SIG_DFL) == SIG_ERR || + dup2(fileno(fp), STDIN_FILENO) == -1) + _exit(1); + + if ((p = fgetln(fp, &len)) == NULL) + _exit(1); + + if (p[len - 1] != '\n') _exit(1); - charstr[1] = '\0'; - line = realloc(line, ++line_sz); - if (line == NULL) + p[len - 1] = '\0'; + + addargs(&args, "%s", "sendmail"); + + while ((tmp = strsep(&p, "|")) != NULL) + addargs(&args, "%s", tmp); + + if (lseek(fileno(fp), len, SEEK_SET) == -1) _exit(1); - strlcat(line, charstr, line_sz); - } while (charstr[0] != '\n'); - line[strcspn(line, "\n")] = '\0'; - - bzero(&args, sizeof args); - addargs(&args, "%s", "sendmail"); - while ((tmp = strsep(&line, "|"))) { - log_debug("%s: arg %s", __func__, tmp); - addargs(&args, "%s", tmp); + + envp[0] = "PATH=" _PATH_DEFPATH; + envp[1] = (char *)NULL; + environ = envp; + + execvp(PATH_SMTPCTL, args.list); + _exit(1); } - envp[0] = "PATH=" _PATH_DEFPATH; - envp[1] = (char *)NULL; - environ = envp; - execvp(PATH_SMTPCTL, args.list); - _exit(1); + + child_add(env, pid, CHILD_ENQUEUE_OFFLINE, -1); + + return (1); } int @@ -975,6 +1131,18 @@ parent_forward_open(char *username) } int +path_starts_with(char *file, char *prefix) +{ + char rprefix[MAXPATHLEN]; + char rfile[MAXPATHLEN]; + + if (realpath(file, rfile) == NULL || realpath(prefix, rprefix) == NULL) + return (-1); + + return (strncmp(rfile, rprefix, strlen(rprefix)) == 0); +} + +int child_cmp(struct child *c1, struct child *c2) { if (c1->pid < c2->pid) @@ -1020,25 +1188,4 @@ imsg_dispatch(int fd, short event, void *p) imsg_event_add(iev); } -void -setup_spool(struct passwd *pw) -{ - if (mkdir(PATH_SPOOL, 0711) < 0 && errno != EEXIST) - err(1, "mkdir: %s", PATH_SPOOL); - if (chmod(PATH_SPOOL, 0711) < 0) - err(1, "chmod: %s", PATH_SPOOL); - if (chown(PATH_SPOOL, 0, 0) < 0) - err(1, "chown: %s", PATH_SPOOL); - - if (queue_be_init(PATH_SPOOL, pw->pw_uid, pw->pw_gid) < 0) - err(1, "backend init failed"); - - if (mkdir(PATH_SPOOL PATH_OFFLINE, 01777) < 0 && errno != EEXIST) - err(1, "mkdir: %s", PATH_SPOOL PATH_OFFLINE); - if (chmod(PATH_SPOOL PATH_OFFLINE, 01777) < 0) - err(1, "chmod: %s", PATH_SPOOL PATH_OFFLINE); - if (chown(PATH_SPOOL PATH_OFFLINE, 0, 0) < 0) - err(1, "chmod: %s", PATH_SPOOL PATH_OFFLINE); -} - SPLAY_GENERATE(childtree, child, entry, child_cmp); diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h index ff74870100b..d5bb09e4cd0 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.194 2010/09/20 09:01:09 gilles Exp $ */ +/* $OpenBSD: smtpd.h,v 1.195 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -23,11 +23,15 @@ #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif +#define IMSG_SIZE_CHECK(p) do { \ + if (IMSG_DATA_SIZE(&imsg) != sizeof(*p)) \ + fatalx("bad length imsg received"); \ +} while (0) #define IMSG_DATA_SIZE(imsg) ((imsg)->hdr.len - IMSG_HEADER_SIZE) #define CONF_FILE "/etc/mail/smtpd.conf" #define MAX_LISTEN 16 -#define PROC_COUNT 8 +#define PROC_COUNT 9 #define MAX_NAME_SIZE 64 #define MAX_HOPS_COUNT 100 @@ -36,22 +40,41 @@ #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 MAX_RULEBUFFER_LEN 256 +#define MAX_RULEBUFFER_LEN 256 -#define SMTPD_EXPIRE (4 * 24 * 60 * 60) +#define SMTPD_QUEUE_INTERVAL (15 * 60) +#define SMTPD_QUEUE_MAXINTERVAL (4 * 60 * 60) +#define SMTPD_QUEUE_EXPIRY (4 * 24 * 60 * 60) #define SMTPD_USER "_smtpd" #define SMTPD_SOCKET "/var/run/smtpd.sock" #define SMTPD_BANNER "220 %s ESMTP OpenSMTPD" #define SMTPD_SESSION_TIMEOUT 300 #define SMTPD_BACKLOG 5 +#define PATH_MAILLOCAL "/usr/libexec/mail.local" #define PATH_SMTPCTL "/usr/sbin/smtpctl" + +#define DIRHASH_BUCKETS 4096 + #define PATH_SPOOL "/var/spool/smtpd" + +#define PATH_ENQUEUE "/enqueue" +#define PATH_INCOMING "/incoming" +#define PATH_QUEUE "/queue" +#define PATH_PURGE "/purge" + +#define PATH_MESSAGE "/message" +#define PATH_ENVELOPES "/envelopes" + +#define PATH_RUNQUEUE "/runqueue" + #define PATH_OFFLINE "/offline" +#define PATH_BOUNCE "/bounce" /* number of MX records to lookup */ #define MAX_MX_COUNT 10 @@ -113,31 +136,38 @@ enum imsg_type { IMSG_CONF_RELOAD, IMSG_LKA_MAIL, IMSG_LKA_RCPT, - IMSG_LKA_RULEMATCH, IMSG_LKA_SECRET, + IMSG_LKA_RULEMATCH, + IMSG_MDA_SESS_NEW, IMSG_MDA_DONE, - IMSG_MFA_MAIL, IMSG_MFA_RCPT, + IMSG_MFA_MAIL, - IMSG_QUEUE_CREATE, - IMSG_QUEUE_APPEND, - IMSG_QUEUE_OPEN, - IMSG_QUEUE_CLOSE, - IMSG_QUEUE_DELETE, + IMSG_QUEUE_CREATE_MESSAGE, + IMSG_QUEUE_SUBMIT_ENVELOPE, + IMSG_QUEUE_COMMIT_ENVELOPES, + IMSG_QUEUE_REMOVE_MESSAGE, + IMSG_QUEUE_COMMIT_MESSAGE, + IMSG_QUEUE_TEMPFAIL, IMSG_QUEUE_STATS, IMSG_QUEUE_PAUSE_LOCAL, - IMSG_QUEUE_PAUSE_RELAY, + IMSG_QUEUE_PAUSE_OUTGOING, IMSG_QUEUE_RESUME_LOCAL, - IMSG_QUEUE_RESUME_RELAY, + IMSG_QUEUE_RESUME_OUTGOING, + + IMSG_QUEUE_REMOVE_SUBMISSION, + IMSG_QUEUE_MESSAGE_UPDATE, + IMSG_QUEUE_MESSAGE_FD, + IMSG_QUEUE_MESSAGE_FILE, IMSG_QUEUE_SCHEDULE, IMSG_QUEUE_REMOVE, IMSG_BATCH_CREATE, IMSG_BATCH_APPEND, IMSG_BATCH_CLOSE, - IMSG_BATCH_UPDATE, IMSG_BATCH_DONE, + IMSG_PARENT_ENQUEUE_OFFLINE, IMSG_PARENT_FORWARD_OPEN, IMSG_PARENT_FORK_MDA, IMSG_PARENT_STATS, @@ -196,7 +226,8 @@ enum smtp_proc_type { PROC_QUEUE, PROC_MDA, PROC_MTA, - PROC_CONTROL + PROC_CONTROL, + PROC_RUNNER, } smtpd_process; struct peer { @@ -370,93 +401,45 @@ struct expandnode { RB_HEAD(expandtree, expandnode); -struct action { - SLIST_ENTRY(action) entry; - u_int64_t id; - char data[1]; /* actually bigger */ -}; - -struct content { - SLIST_HEAD(,action) actions; - u_int64_t id; - int ref; - struct event *ev; +enum message_type { + T_MDA_MESSAGE = 0x1, + T_MTA_MESSAGE = 0x2, + T_BOUNCE_MESSAGE = 0x4 }; -#define NO_RETRY_EXPIRED 0 -#define RETRY_NOW 1 - -struct batch { - SLIST_ENTRY(batch) entry; - SLIST_HEAD(,action) actions; - struct content *content; - time_t retry; - char sortkey[1]; /* actually bigger */ -}; - -struct incoming { - SLIST_ENTRY(incoming) entry; - SLIST_HEAD(,batch) batches[3]; - struct content *content; - char **local; - int local_sz; - int nlocal; -}; - -#define Q_LOCAL 0 -#define Q_RELAY 1 -#define Q_BOUNCE 2 - -struct queue { - SLIST_HEAD(,batch) head; - struct event ev; - struct smtpd *env; - char *name; - void **session; - int session_sz; - size_t sessions; - size_t max; -}; - -struct aux { - char *mode; - char *mail_from; - char *rcpt_to; - char *user_from; - char *user_to; - char *path; - char *rcpt; - char *relay_via; - char *port; - char *ssl; - char *cert; - char *auth; -}; - -struct bounce { - struct event ev; - struct batch *batch; - struct smtp_client *pcb; - int id; +enum message_status { + S_MESSAGE_PERMFAILURE = 0x2, + S_MESSAGE_TEMPFAILURE = 0x4, + S_MESSAGE_REJECTED = 0x8, + S_MESSAGE_ACCEPTED = 0x10, + S_MESSAGE_RETRY = 0x20, + S_MESSAGE_EDNS = 0x40, + S_MESSAGE_ECONNECT = 0x80 }; enum message_flags { - F_MESSAGE_AUTHENTICATED = 0x1 -}; - -enum message_status { - S_MESSAGE_PERMFAILURE = 0x1, - S_MESSAGE_TEMPFAILURE = 0x2 + F_MESSAGE_RESOLVED = 0x1, + F_MESSAGE_SCHEDULED = 0x2, + F_MESSAGE_PROCESSING = 0x4, + F_MESSAGE_AUTHENTICATED = 0x8, + F_MESSAGE_ENQUEUED = 0x10, + F_MESSAGE_FORCESCHEDULE = 0x20, + F_MESSAGE_BOUNCE = 0x40 }; struct message { TAILQ_ENTRY(message) entry; + enum message_type type; + + u_int64_t id; + u_int64_t session_id; + u_int64_t batch_id; + char tag[MAX_TAG_SIZE]; - u_int32_t id; /* smtp session id */ - u_int32_t session_id; /* smtp session id */ - u_int32_t queue_id; + char message_id[MAX_ID_SIZE]; + char message_uid[MAX_ID_SIZE]; char session_helo[MAXHOSTNAMELEN]; char session_hostname[MAXHOSTNAMELEN]; @@ -474,6 +457,23 @@ struct message { enum message_status status; }; +enum batch_type { + T_MDA_BATCH = 0x1, + T_MTA_BATCH = 0x2, + T_BOUNCE_BATCH = 0x4 +}; + +struct batch { + SPLAY_ENTRY(batch) b_nodes; + u_int64_t id; + enum batch_type type; + struct rule rule; + struct smtpd *env; + char message_id[MAX_ID_SIZE]; + char hostname[MAXHOSTNAMELEN]; + TAILQ_HEAD(, message) messages; +}; + enum child_type { CHILD_INVALID, CHILD_DAEMON, @@ -558,7 +558,7 @@ enum session_flags { struct session { SPLAY_ENTRY(session) s_nodes; - u_int32_t s_id; + u_int64_t s_id; enum session_flags s_flags; enum session_state s_state; @@ -584,9 +584,6 @@ struct session { FILE *datafp; int mboxfd; int messagefd; - - u_int32_t queue_id; - u_int64_t content_id; }; struct smtpd { @@ -602,8 +599,9 @@ struct smtpd { #define SMTPD_MTA_PAUSED 0x00000008 #define SMTPD_SMTP_PAUSED 0x00000010 u_int32_t sc_flags; - u_int32_t sc_maxconn; + struct timeval sc_qintval; int sc_qexpire; + u_int32_t sc_maxconn; struct event sc_ev; int *sc_pipes[PROC_COUNT] [PROC_COUNT]; @@ -620,6 +618,7 @@ struct smtpd { SPLAY_HEAD(msgtree, message) sc_messages; SPLAY_HEAD(ssltree, ssl) *sc_ssl; + SPLAY_HEAD(batchtree, batch) batch_queue; SPLAY_HEAD(childtree, child) children; SPLAY_HEAD(lkatree, lkasession) lka_sessions; SPLAY_HEAD(mtatree, mta_session) mta_sessions; @@ -633,8 +632,14 @@ struct s_parent { }; struct s_queue { - size_t length; - size_t inserts; + size_t inserts_local; + size_t inserts_remote; +}; + +struct s_runner { + size_t active; + size_t bounces_active; + size_t bounces; }; struct s_session { @@ -672,12 +677,25 @@ struct s_control { struct stats { struct s_parent parent; struct s_queue queue; + struct s_runner runner; struct s_session mta; struct s_mda mda; struct s_session smtp; struct s_control control; }; +struct sched { + int fd; + char mid[MAX_ID_SIZE]; + int ret; +}; + +struct remove { + int fd; + char mid[MAX_ID_SIZE]; + int ret; +}; + struct reload { int fd; int ret; @@ -688,6 +706,7 @@ struct submit_status { int code; union submit_path { struct path path; + char msgid[MAX_ID_SIZE]; char errormsg[MAX_LINE_SIZE]; } u; enum message_flags flags; @@ -696,7 +715,7 @@ struct submit_status { }; struct forward_req { - u_int32_t id; + u_int64_t id; u_int8_t status; char pw_name[MAXLOGNAME]; }; @@ -711,23 +730,25 @@ struct dns { struct dns *next; }; +struct secret { + u_int64_t id; + char host[MAXHOSTNAMELEN]; + char secret[MAX_LINE_SIZE]; +}; + struct mda_session { LIST_ENTRY(mda_session) entry; + struct message msg; struct msgbuf w; struct event ev; - time_t birth; - u_int64_t content_id; - u_int64_t action_id; u_int32_t id; - struct aux aux; - char *auxraw; FILE *datafp; }; struct deliver { - int mode; char to[PATH_MAX]; char user[MAXLOGNAME]; + short mode; }; struct rulematch { @@ -741,7 +762,7 @@ enum lkasession_flags { struct lkasession { SPLAY_ENTRY(lkasession) nodes; - u_int32_t id; + u_int64_t id; struct path path; struct deliverylist deliverylist; @@ -759,6 +780,7 @@ enum mta_state { MTA_INVALID_STATE, MTA_INIT, MTA_SECRET, + MTA_DATA, MTA_MX, MTA_CONNECT, MTA_PTR, @@ -766,6 +788,12 @@ enum mta_state { MTA_DONE }; +/* mta session flags */ +#define MTA_FORCE_ANYSSL 0x1 +#define MTA_FORCE_SMTPS 0x2 +#define MTA_ALLOW_PLAIN 0x4 +#define MTA_USE_AUTH 0x8 + struct mta_relay { TAILQ_ENTRY(mta_relay) entry; struct sockaddr_storage sa; @@ -773,31 +801,25 @@ struct mta_relay { int used; }; -struct recipient { - TAILQ_ENTRY(recipient) entry; - u_int64_t action_id; - char status[128]; - char address[1]; /* actually bigger */ -}; - struct mta_session { SPLAY_ENTRY(mta_session) entry; - u_int32_t id; + u_int64_t id; struct smtpd *env; enum mta_state state; - time_t birth; - u_int64_t content_id; - struct aux aux; - char *auxraw; - TAILQ_HEAD(,recipient) recipients; + char *host; + int port; + int flags; + TAILQ_HEAD(,message) recipients; TAILQ_HEAD(,mta_relay) relays; char *secret; int fd; int datafd; struct event ev; + char *cert; void *pcb; }; + /* maps return structures */ struct map_secret { char username[MAX_LINE_SIZE]; @@ -883,9 +905,53 @@ SPLAY_PROTOTYPE(lkatree, lkasession, nodes, lkasession_cmp); /* mfa.c */ pid_t mfa(struct smtpd *); +int msg_cmp(struct message *, struct message *); /* queue.c */ pid_t queue(struct smtpd *); +int queue_load_envelope(struct message *, char *); +int queue_update_envelope(struct message *); +int queue_remove_envelope(struct message *); +void queue_submit_envelope(struct smtpd *, struct message *); +void queue_commit_envelopes(struct smtpd *, struct message*); +int batch_cmp(struct batch *, struct batch *); +struct batch *batch_by_id(struct smtpd *, u_int64_t); +u_int16_t queue_hash(char *); + +/* queue_shared.c */ +int queue_create_layout_message(char *, char *); +void queue_delete_layout_message(char *, char *); +int queue_record_layout_envelope(char *, struct message *); +int queue_remove_layout_envelope(char *, struct message *); +int queue_commit_layout_message(char *, struct message *); +int queue_open_layout_messagefile(char *, struct message *); +int enqueue_create_layout(char *); +void enqueue_delete_message(char *); +int enqueue_record_envelope(struct message *); +int enqueue_remove_envelope(struct message *); +int enqueue_commit_message(struct message *); +int enqueue_open_messagefile(struct message *); +int bounce_create_layout(char *, struct message *); +void bounce_delete_message(char *); +int bounce_record_envelope(struct message *); +int bounce_remove_envelope(struct message *); +int bounce_commit_message(struct message *); +int bounce_record_message(struct message *); +int queue_create_incoming_layout(char *); +void queue_delete_incoming_message(char *); +int queue_record_incoming_envelope(struct message *); +int queue_remove_incoming_envelope(struct message *); +int queue_commit_incoming_message(struct message *); +int queue_open_incoming_message_file(struct message *); +int queue_open_message_file(char *msgid); +void queue_message_update(struct message *); +void queue_delete_message(char *); +struct qwalk *qwalk_new(char *); +int qwalk(struct qwalk *, char *); +void qwalk_close(struct qwalk *); +void show_queue(char *, int); + +u_int16_t queue_hash(char *); /* map.c */ void *map_lookup(struct smtpd *, objid_t, char *, enum map_kind); @@ -908,6 +974,11 @@ int session_socket_error(int); int enqueue(int, char **); int enqueue_offline(int, char **); +/* runner.c */ +pid_t runner(struct smtpd *); +void message_reset_flags(struct message *); +SPLAY_PROTOTYPE(batchtree, batch, b_nodes, batch_cmp); + /* smtp.c */ pid_t smtp(struct smtpd *); void smtp_resume(struct smtpd *); @@ -915,7 +986,7 @@ void smtp_resume(struct smtpd *); /* smtp_session.c */ void session_init(struct listener *, struct session *); int session_cmp(struct session *, struct session *); -void session_pickup(struct session *); +void session_pickup(struct session *, struct submit_status *); void session_destroy(struct session *); void session_respond(struct session *, char *, ...) __attribute__ ((format (printf, 2, 3))); @@ -970,20 +1041,21 @@ void addargs(arglist *, char *, ...) __attribute__((format(printf, 2, 3))); int bsnprintf(char *, size_t, const char *, ...) __attribute__ ((format (printf, 3, 4))); +int safe_fclose(FILE *); int hostname_match(char *, char *); int recipient_to_path(struct path *, char *); int valid_localpart(char *); int valid_domainpart(char *); char *ss_to_text(struct sockaddr_storage *); +int valid_message_id(char *); +int valid_message_uid(char *); char *time_to_text(time_t); int secure_file(int, char *, struct passwd *, int); void lowercase(char *, char *, size_t); -void sa_set_port(struct sockaddr *, char *); +void message_set_errormsg(struct message *, char *, ...); +char *message_get_errormsg(struct message *); +void sa_set_port(struct sockaddr *, int); struct path *path_dup(struct path *); u_int64_t generate_uid(void); void fdlimit(double); int availdesc(void); -int table_alloc(void ***, int *); -void *table_lookup(void **, int, int); -void auxsplit(struct aux *, char *); -char *rcpt_pretty(struct aux *); diff --git a/usr.sbin/smtpd/smtpd/Makefile b/usr.sbin/smtpd/smtpd/Makefile index de0a3bbc980..6037921f6df 100644 --- a/usr.sbin/smtpd/smtpd/Makefile +++ b/usr.sbin/smtpd/smtpd/Makefile @@ -1,11 +1,12 @@ -# $OpenBSD: Makefile,v 1.20 2010/06/01 23:06:25 jacekm Exp $ +# $OpenBSD: Makefile,v 1.21 2010/10/09 22:05:36 gilles Exp $ PROG= smtpd -SRCS= aliases.c authenticate.c client.c \ +SRCS= aliases.c authenticate.c bounce.c client.c \ config.c control.c dns.c expand.c forward.c \ lka.c log.c map.c map_backend.c map_parser.c mda.c \ - mfa.c mta.c parse.y queue.c queue_backend.c ruleset.c \ - smtp.c smtp_session.c smtpd.c ssl.c ssl_privsep.c util.c + mfa.c mta.c parse.y queue.c queue_shared.c ruleset.c \ + runner.c smtp.c smtp_session.c smtpd.c ssl.c \ + ssl_privsep.c util.c MAN= smtpd.8 smtpd.conf.5 BINDIR= /usr/sbin diff --git a/usr.sbin/smtpd/ssl.c b/usr.sbin/smtpd/ssl.c index 58247c8fc6a..db2e3025e7b 100644 --- a/usr.sbin/smtpd/ssl.c +++ b/usr.sbin/smtpd/ssl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssl.c,v 1.28 2010/06/01 23:06:25 jacekm Exp $ */ +/* $OpenBSD: ssl.c,v 1.29 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -550,7 +550,7 @@ ssl_session_accept(int fd, short event, void *p) session_bufferevent_new(s); event_set(&s->s_bev->ev_read, s->s_fd, EV_READ, ssl_read, s->s_bev); event_set(&s->s_bev->ev_write, s->s_fd, EV_WRITE, ssl_write, s->s_bev); - session_pickup(s); + session_pickup(s, NULL); return; retry: diff --git a/usr.sbin/smtpd/util.c b/usr.sbin/smtpd/util.c index 05be2dbfbb2..e28e1d8ab6c 100644 --- a/usr.sbin/smtpd/util.c +++ b/usr.sbin/smtpd/util.c @@ -1,9 +1,9 @@ -/* $OpenBSD: util.c,v 1.35 2010/06/01 23:06:25 jacekm Exp $ */ +/* $OpenBSD: util.c,v 1.36 2010/10/09 22:05:35 gilles Exp $ */ /* * Copyright (c) 2000,2001 Markus Friedl. All rights reserved. * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> - * Copyright (c) 2009-2010 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -58,6 +58,28 @@ bsnprintf(char *str, size_t size, const char *format, ...) return 1; } +/* Close file, signifying temporary error condition (if any) to the caller. */ +int +safe_fclose(FILE *fp) +{ + if (ferror(fp)) { + fclose(fp); + return 0; + } + if (fflush(fp)) { + fclose(fp); + if (errno == ENOSPC) + return 0; + fatal("safe_fclose: fflush"); + } + if (fsync(fileno(fp))) + fatal("safe_fclose: fsync"); + if (fclose(fp)) + fatal("safe_fclose: fclose"); + + return 1; +} + int hostname_match(char *hostname, char *pattern) { @@ -178,6 +200,53 @@ ss_to_text(struct sockaddr_storage *ss) return (buf); } +int +valid_message_id(char *mid) +{ + u_int8_t cnt; + + /* [0-9]{10}\.[a-zA-Z0-9]{16} */ + for (cnt = 0; cnt < 10; ++cnt, ++mid) + if (! isdigit((int)*mid)) + return 0; + + if (*mid++ != '.') + return 0; + + for (cnt = 0; cnt < 16; ++cnt, ++mid) + if (! isalnum((int)*mid)) + return 0; + + return (*mid == '\0'); +} + +int +valid_message_uid(char *muid) +{ + u_int8_t cnt; + + /* [0-9]{10}\.[a-zA-Z0-9]{16}\.[0-9]{0,} */ + for (cnt = 0; cnt < 10; ++cnt, ++muid) + if (! isdigit((int)*muid)) + return 0; + + if (*muid++ != '.') + return 0; + + for (cnt = 0; cnt < 16; ++cnt, ++muid) + if (! isalnum((int)*muid)) + return 0; + + if (*muid++ != '.') + return 0; + + for (cnt = 0; *muid != '\0'; ++cnt, ++muid) + if (! isdigit((int)*muid)) + return 0; + + return (cnt != 0); +} + char * time_to_text(time_t when) { @@ -302,22 +371,49 @@ lowercase(char *buf, char *s, size_t len) } void -sa_set_port(struct sockaddr *sa, char *port) +message_set_errormsg(struct message *messagep, char *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + + ret = vsnprintf(messagep->session_errorline, MAX_LINE_SIZE, fmt, ap); + if (ret >= MAX_LINE_SIZE) + strlcpy(messagep->session_errorline + (MAX_LINE_SIZE - 4), "...", 4); + + /* this should not happen */ + if (ret == -1) + err(1, "vsnprintf"); + + va_end(ap); +} + +char * +message_get_errormsg(struct message *messagep) +{ + return messagep->session_errorline; +} + +void +sa_set_port(struct sockaddr *sa, int port) { - char hbuf[NI_MAXHOST]; + char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; struct addrinfo hints, *res; int error; - error = getnameinfo(sa, sa->sa_len, hbuf, sizeof hbuf, NULL, 0, NI_NUMERICHOST); + error = getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); if (error) fatalx("sa_set_port: getnameinfo failed"); memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_NUMERICHOST; + hints.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV; + + snprintf(sbuf, sizeof(sbuf), "%d", port); - error = getaddrinfo(hbuf, port, &hints, &res); + error = getaddrinfo(hbuf, sbuf, &hints, &res); if (error) fatalx("sa_set_port: getaddrinfo failed"); @@ -330,7 +426,7 @@ path_dup(struct path *path) { struct path *pathp; - pathp = calloc(1, sizeof(struct path)); + pathp = calloc(sizeof(struct path), 1); if (pathp == NULL) fatal("calloc"); @@ -413,8 +509,7 @@ session_socket_no_linger(int fd) int session_socket_error(int fd) { - socklen_t len; - int error; + int error, len; len = sizeof(error); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) @@ -422,104 +517,3 @@ session_socket_error(int fd) return (error); } - -/* - * Find unused slot in a pointer table. - */ -int -table_alloc(void ***p, int *szp) -{ - void **array; - int array_sz, i, new; - - array = *p; - array_sz = *szp; - - for (i = 0; i < array_sz; i++) - if (array[i] == NULL) - break; - - /* array full? */ - if (i == array_sz) { - if (array_sz * 2 < array_sz) - fatalx("table_alloc: overflow"); - array_sz *= 2; - array = realloc(array, ++array_sz * sizeof *array); - if (array == NULL) - fatal("array_alloc"); - for (new = i; new < array_sz; new++) - array[new] = NULL; - *p = array; - *szp = array_sz; - } - - return i; -} - -/* - * Retrieve table entry residing at given index. - */ -void * -table_lookup(void **p, int sz, int i) -{ - if (i < 0 || i >= sz) - return (NULL); - return p[i]; -} - -void -auxsplit(struct aux *a, char *aux) -{ - int col; - char *val; - - bzero(a, sizeof *a); - col = 0; - for (;;) { - val = strsep(&aux, "|"); - if (val == NULL) - break; - col++; - if (col == 1) - a->mode = val; - else if (col == 2) - a->mail_from = val; - else if (col == 3) - a->rcpt_to = val; - else if (col == 4) - a->user_from = val; - else if (a->mode[0] == 'R') { - if (col == 5) - a->rcpt = val; - else if (col == 6) - a->relay_via = val; - else if (col == 7) - a->port = val; - else if (col == 8) - a->ssl = val; - else if (col == 9) - a->cert = val; - else if (col == 10) - a->auth = val; - } else if (col == 5) - a->user_to = val; - else if (col == 6) - a->path = val; - } -} - -char * -rcpt_pretty(struct aux *aux) -{ - switch (aux->mode[0]) { - case 'M': - case 'D': - case 'P': - return aux->user_to; - case 'F': - return aux->path; - case 'R': - return aux->rcpt; - } - return NULL; -} |