diff options
author | Jacek Masiulaniec <jacekm@cvs.openbsd.org> | 2009-05-09 17:04:56 +0000 |
---|---|---|
committer | Jacek Masiulaniec <jacekm@cvs.openbsd.org> | 2009-05-09 17:04:56 +0000 |
commit | 6a0f971e887cf324c3d90a90f5401030ddde6b66 (patch) | |
tree | dca1622b451da6546ae20e01de668dcff536a4af /usr.sbin/smtpd | |
parent | bad8e4fca758e7b8298e71bfee6628ee4342178b (diff) |
- New API to handle all DNS query types (A, MX, PTR) asynchronously.
- Improve RFC compliance: CNAMEs are resolved, equal preference MXs
are randomized, relaying via MX that has equal/lower preference
than local server is prevented, decision on when to treat domain
name as implicit MX is better.
ok gilles@
Diffstat (limited to 'usr.sbin/smtpd')
-rw-r--r-- | usr.sbin/smtpd/dns.c | 565 | ||||
-rw-r--r-- | usr.sbin/smtpd/lka.c | 226 | ||||
-rw-r--r-- | usr.sbin/smtpd/mta.c | 119 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp.c | 20 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.h | 45 |
5 files changed, 591 insertions, 384 deletions
diff --git a/usr.sbin/smtpd/dns.c b/usr.sbin/smtpd/dns.c index 433f9396917..db9b8c3526e 100644 --- a/usr.sbin/smtpd/dns.c +++ b/usr.sbin/smtpd/dns.c @@ -1,7 +1,8 @@ -/* $OpenBSD: dns.c,v 1.10 2009/02/22 11:44:29 form Exp $ */ +/* $OpenBSD: dns.c,v 1.11 2009/05/09 17:04:55 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -28,169 +29,487 @@ #include <event.h> #include <netdb.h> #include <resolv.h> -#include <stdio.h> #include <stdlib.h> #include <string.h> +#include <unistd.h> #include "smtpd.h" -struct mxrecord { - char hostname[MAXHOSTNAMELEN]; - u_int16_t priority; +struct resdata { + struct imsgbuf ibuf; + struct imsgbuf *asker; }; -static void mxsort(struct mxrecord *, size_t); +struct mx { + char host[MAXHOSTNAMELEN]; + double prio; +}; + +void parent_dispatch_dns(int, short, void *); + +int dns(void); +void dns_dispatch_parent(int, short, void *); +void lookup_a(struct imsgbuf *, struct dns *, int); +void lookup_mx(struct imsgbuf *, struct dns *); +int get_mxlist(char *, char *, struct dns **); +void free_mxlist(struct dns *); +int mxcmp(const void *, const void *); +void lookup_ptr(struct imsgbuf *, struct dns *); + +/* + * User interface. + */ + +void +dns_query_a(struct smtpd *env, char *host, int port, u_int64_t id) +{ + struct dns query; + + bzero(&query, sizeof(query)); + strlcpy(query.host, host, sizeof(query.host)); + query.port = port; + query.id = id; + + imsg_compose(env->sc_ibufs[PROC_LKA], IMSG_DNS_A, 0, 0, -1, &query, + sizeof(query)); +} + +void +dns_query_mx(struct smtpd *env, char *host, int port, u_int64_t id) +{ + struct dns query; + + bzero(&query, sizeof(query)); + strlcpy(query.host, host, sizeof(query.host)); + query.port = port; + query.id = id; + + imsg_compose(env->sc_ibufs[PROC_LKA], IMSG_DNS_MX, 0, 0, -1, &query, + sizeof(query)); +} + +void +dns_query_ptr(struct smtpd *env, struct sockaddr_storage *ss, u_int64_t id) +{ + struct dns query; + + bzero(&query, sizeof(query)); + query.ss = *ss; + query.id = id; + + imsg_compose(env->sc_ibufs[PROC_LKA], IMSG_DNS_PTR, 0, 0, -1, &query, + sizeof(query)); +} + +/* + * Parent resolver process interface. + */ + +void +dns_async(struct smtpd *env, struct imsgbuf *asker, int type, struct dns *query) +{ + struct resdata *rd; + int fd; + + if ((rd = calloc(1, sizeof(*rd))) == NULL) + fatal(NULL); + + rd->asker = asker; + query->env = env; + + fd = dns(); + imsg_init(&rd->ibuf, fd, parent_dispatch_dns); + rd->ibuf.events = EV_READ; + rd->ibuf.data = rd; + event_set(&rd->ibuf.ev, rd->ibuf.fd, rd->ibuf.events, rd->ibuf.handler, + rd->ibuf.data); + event_add(&rd->ibuf.ev, NULL); + + imsg_compose(&rd->ibuf, type, 0, 0, -1, query, sizeof(*query)); +} -static void -mxsort(struct mxrecord *array, size_t len) +void +parent_dispatch_dns(int sig, short event, void *p) { - u_int32_t i; - int32_t j; - struct mxrecord store; - - for (i = 1; i < len; i++) { - store = array[i]; - for (j = i - 1; j >= 0 && array[j].priority > store.priority; - j--) { - array[j + 1] = array[j]; + struct resdata *rd = p; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + + ibuf = &rd->ibuf; + switch (event) { + case EV_READ: + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read_error"); + if (n == 0) + fatal("parent_dispatch_dns: pipe closed"); + break; + case EV_WRITE: + if (msgbuf_write(&ibuf->w) == -1) + fatal("parent_dispatch_dns: msgbuf_write"); + imsg_event_add(ibuf); + return; + default: + fatalx("unknown event"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatalx("parent_dispatch_dns: imsg_get error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_DNS_A: + imsg_compose(rd->asker, IMSG_DNS_A, 0, 0, -1, imsg.data, + sizeof(struct dns)); + break; + + case IMSG_DNS_A_END: + case IMSG_DNS_PTR: + imsg_compose(rd->asker, imsg.hdr.type, 0, 0, -1, + imsg.data, sizeof(struct dns)); + close(rd->ibuf.fd); + event_del(&ibuf->ev); + free(rd); + imsg_free(&imsg); + return; + + default: + log_warnx("parent_dispatch_dns: got imsg %d", + imsg.hdr.type); + fatalx("parent_dispatch_dns: unexpected imsg"); } - array[j + 1] = store; + imsg_free(&imsg); } + imsg_event_add(ibuf); } +/* + * Helper resolver process. + */ + int -getmxbyname(char *name, char ***result) +dns(void) { - union { - u_int8_t bytes[PACKETSZ]; - HEADER header; - } answer; - u_int32_t i, j; - int ret; - u_int8_t *sp; - u_int8_t *endp; - u_int8_t *ptr; - u_int16_t qdcount; - u_int8_t expbuf[PACKETSZ]; - u_int16_t type; - u_int16_t n; - u_int16_t priority; - size_t mxnb; - struct mxrecord mxarray[MXARRAYSIZE]; - size_t chunklen; - - ret = res_query(name, C_IN, T_MX, (u_int8_t *)&answer.bytes, - sizeof answer); - if (ret == -1) { + int fd[2]; + pid_t pid; + struct imsgbuf *ibuf; + + if (socketpair(AF_UNIX, SOCK_STREAM, AF_UNSPEC, fd) == -1) + fatal("socketpair"); + + session_socket_blockmode(fd[0], BM_NONBLOCK); + session_socket_blockmode(fd[1], BM_NONBLOCK); + + if ((pid = fork()) == -1) + fatal("dns: fork"); + if (pid > 0) { + close(fd[1]); + return (fd[0]); + } + close(fd[0]); + + event_base_free(NULL); + event_init(); + + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + + if ((ibuf = calloc(1, sizeof(*ibuf))) == NULL) + fatal(NULL); + imsg_init(ibuf, fd[1], dns_dispatch_parent); + ibuf->events = EV_READ; + ibuf->data = ibuf; + event_set(&ibuf->ev, ibuf->fd, ibuf->events, ibuf->handler, ibuf->data); + event_add(&ibuf->ev, NULL); + + event_dispatch(); + _exit(0); +} + +void +dns_dispatch_parent(int sig, short event, void *p) +{ + struct imsgbuf *ibuf = p; + struct imsg imsg; + ssize_t n; + + switch (event) { + case EV_READ: + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read_error"); + if (n == 0) { + /* this pipe is dead, so remove the event handler */ + event_del(&ibuf->ev); + event_loopexit(NULL); + return; + } + break; + case EV_WRITE: + if (msgbuf_write(&ibuf->w) == -1) + fatal("dns_dispatch_parent: msgbuf_write"); + imsg_event_add(ibuf); + return; + default: + fatalx("unknown event"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatalx("dns_dispatch_parent: imsg_get error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_DNS_A: + lookup_a(ibuf, imsg.data, 1); + break; + + case IMSG_DNS_MX: + lookup_mx(ibuf, imsg.data); + break; + + case IMSG_DNS_PTR: + lookup_ptr(ibuf, imsg.data); + break; + + default: + log_warnx("dns_dispatch_parent: got imsg %d", + imsg.hdr.type); + fatalx("dns_dispatch_parent: unexpected imsg"); + } + imsg_free(&imsg); + } + imsg_event_add(ibuf); +} + +void +lookup_a(struct imsgbuf *ibuf, struct dns *query, int finalize) +{ + struct addrinfo *res0, *res, hints; + char *port = NULL; + + log_debug("lookup_a %s:%d", query->host, query->port); + + if (query->port && asprintf(&port, "%u", query->port) == -1) + fatal(NULL); + + bzero(&hints, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + query->error = getaddrinfo(query->host, port, &hints, &res0); + if (query->error) + goto end; + + for (res = res0; res; res = res->ai_next) { + memcpy(&query->ss, res->ai_addr, res->ai_addr->sa_len); + imsg_compose(ibuf, IMSG_DNS_A, 0, 0, -1, query, sizeof(*query)); + } + freeaddrinfo(res0); +end: + free(port); + if (finalize) { + log_debug("lookup_a %s", query->error ? "failed" : "success"); + imsg_compose(ibuf, IMSG_DNS_A_END, 0, 0, -1, query, + sizeof(*query)); + } +} + +void +lookup_mx(struct imsgbuf *ibuf, struct dns *query) +{ + struct dns *mx0, *mx; + int success = 0; + + log_debug("lookup_mx %s", query->host); + + query->error = get_mxlist(query->host, query->env->sc_hostname, &mx0); + if (query->error) + goto end; + + if (mx0 == NULL) { + log_debug("implicit mx"); + if ((mx0 = calloc(1, sizeof(*mx0))) == NULL) + fatal(NULL); + strlcpy(mx0->host, query->host, sizeof(mx0->host)); + } + + for (mx = mx0; mx; mx = mx->next) { + mx->port = query->port; + mx->id = query->id; + lookup_a(ibuf, mx, 0); + if (!mx->error) + success++; + } + free_mxlist(mx0); + + if (success == 0) + query->error = EAI_NODATA; + +end: + log_debug("lookup_mx %s", query->error ? "failed" : "success"); + imsg_compose(ibuf, IMSG_DNS_A_END, 0, 0, -1, query, sizeof(*query)); +} + +int +get_mxlist(char *host, char *self, struct dns **res) +{ + struct mx tab[MAX_MX_COUNT]; + char buf[PACKETSZ], *p, *endp; + int ntab, i, ret, type, n, maxprio, cname_ok = 3; + int qdcount, ancount; + +again: + ntab = 0; + maxprio = 16384; + ret = res_query(host, C_IN, T_MX, buf, sizeof(buf)); + if (ret < 0) { switch (h_errno) { case TRY_AGAIN: return (EAI_AGAIN); case NO_RECOVERY: return (EAI_FAIL); case HOST_NOT_FOUND: - return (EAI_NONAME); case NO_DATA: + *res = NULL; return (0); } - fatal("getmxbyname: res_query"); + fatal("get_mxlist: res_query"); } - /* sp stores start of dns packet, - * endp stores end of dns packet, - */ - sp = (u_int8_t *)&answer.bytes; - endp = sp + ret; - - /* skip header */ - ptr = sp + HFIXEDSZ; - - for (qdcount = ntohs(answer.header.qdcount); - qdcount--; - ptr += ret + QFIXEDSZ) { - ret = dn_skipname(ptr, endp); - if (ret == -1) - return 0; + p = buf + HFIXEDSZ; + endp = buf + ret; + qdcount = ntohs(((HEADER *)buf)->qdcount); + ancount = ntohs(((HEADER *)buf)->ancount); + + if (qdcount < 1) + return (EAI_FAIL); + for (i = 0; i < qdcount; i++) { + ret = dn_skipname(p, endp); + if (ret < 0) + return (EAI_FAIL); + p += ret + QFIXEDSZ; } - mxnb = 0; - for (; ptr < endp;) { - memset(expbuf, 0, sizeof expbuf); - ret = dn_expand(sp, endp, ptr, expbuf, sizeof expbuf); - if (ret == -1) - break; - ptr += ret; + while (p < endp && ntab < ancount && ntab < MAX_MX_COUNT) { + ret = dn_skipname(p, endp); + if (ret < 0) + return (EAI_FAIL); + p += ret; - GETSHORT(type, ptr); - ptr += sizeof(u_int16_t) + sizeof(u_int32_t); - GETSHORT(n, ptr); + GETSHORT(type, p); + p += sizeof(u_int16_t) + sizeof(u_int32_t); + GETSHORT(n, p); + + if (type == T_CNAME) { + if (cname_ok-- == 0) + return (EAI_FAIL); + ret = dn_expand(buf, endp, p, tab[0].host, + sizeof(tab[0].host)); + if (ret < 0) + return (EAI_FAIL); + host = tab[0].host; + goto again; + } if (type != T_MX) { - ptr += n; + log_warnx("get_mxlist: %s: bad rr type %d", host, type); + p += n; continue; } - GETSHORT(priority, ptr); - ret = dn_expand(sp, endp, ptr, expbuf, sizeof expbuf); - if (ret == -1) - return 0; - ptr += ret; - - if (mxnb < sizeof(mxarray) / sizeof(struct mxrecord)) { - if (strlcpy(mxarray[mxnb].hostname, expbuf, - sizeof(mxarray[mxnb].hostname)) >= - sizeof(mxarray[mxnb].hostname)) - return 0; - mxarray[mxnb].priority = priority; - } - else { - int tprio = 0; - - for (i = j = 0; - i < sizeof(mxarray) / sizeof(struct mxrecord); - ++i) { - if (tprio < mxarray[i].priority) { - tprio = mxarray[i].priority; - j = i; - } - } - - if (mxarray[j].priority > priority) { - if (strlcpy(mxarray[j].hostname, expbuf, - sizeof(mxarray[j].hostname)) >= - sizeof(mxarray[j].hostname)) - return 0; - mxarray[j].priority = priority; - } - } - ++mxnb; + GETSHORT(tab[ntab].prio, p); + + ret = dn_expand(buf, endp, p, tab[ntab].host, + sizeof(tab[ntab].host)); + if (ret < 0) + return (EAI_FAIL); + p += ret; + + /* + * In case our name is listed as MX, prevent loops by excluding + * all hosts of our or greater preference number. + */ + if (strcmp(self, tab[ntab].host) == 0) + maxprio = tab[ntab].prio; + + ntab++; } - if (mxnb == 0) - return 0; + /* + * Randomize equal preference hosts using the fractional part. + */ + for (i = 0; i < ntab; i++) + tab[i].prio += (double)arc4random_uniform(ntab) / ntab; + + qsort(tab, ntab, sizeof(struct mx), mxcmp); - if (mxnb > sizeof(mxarray) / sizeof(struct mxrecord)) - mxnb = sizeof(mxarray) / sizeof(struct mxrecord); + for (i = 0; i < ntab; i++) { + log_debug("mx %s prio %f", tab[i].host, tab[i].prio); + if (tab[i].prio >= maxprio) + break; + if ((*res = calloc(1, sizeof(struct dns))) == NULL) + fatal(NULL); + strlcpy((*res)->host, tab[i].host, sizeof((*res)->host)); + res = &(*res)->next; + } - /* Rearrange MX records by priority */ - mxsort(mxarray, mxnb); + if (i == 0) + return (EAI_FAIL); - chunklen = 0; - for (i = 0; i < mxnb; ++i) - chunklen += strlen(mxarray[i].hostname) + 1; - chunklen += ((mxnb + 1) * sizeof(char *)); + return (0); +} - *result = calloc(1, chunklen); - if (*result == NULL) - fatal("getmxbyname: calloc"); +void +free_mxlist(struct dns *first) +{ + struct dns *mx, *next; - ptr = (u_int8_t *)*result + (mxnb + 1) * sizeof(char *); - for (i = 0; i < mxnb; ++i) { - strlcpy(ptr, mxarray[i].hostname, - strlen(mxarray[i].hostname) + 1); - (*result)[i] = ptr; - ptr += strlen(mxarray[i].hostname) + 1; + for (mx = first; mx; mx = next) { + next = mx->next; + free(mx); } - (*result)[i] = NULL; +} - return mxnb; +int +mxcmp(const void *va, const void *vb) +{ + const struct mx *a = va; + const struct mx *b = vb; + + if (a->prio > b->prio) + return (1); + else if (a->prio < b->prio) + return (-1); + else + return (0); +} + +void +lookup_ptr(struct imsgbuf *ibuf, struct dns *query) +{ + struct addrinfo *res, hints; + + log_debug("lookup_ptr %s", ss_to_text(&query->ss)); + + query->error = getnameinfo((struct sockaddr *)&query->ss, + query->ss.ss_len, query->host, sizeof(query->host), NULL, 0, + NI_NAMEREQD); + if (query->error) + goto end; + + bzero(&hints, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICHOST; + + if (getaddrinfo(query->host, NULL, &hints, &res) == 0) { + query->error = EAI_NODATA; + freeaddrinfo(res); + } +end: + log_debug("lookup_ptr %s", query->error ? "failed" : "success"); + imsg_compose(ibuf, IMSG_DNS_PTR, 0, 0, -1, query, sizeof(*query)); } diff --git a/usr.sbin/smtpd/lka.c b/usr.sbin/smtpd/lka.c index b9bfec2d841..2f0bc905446 100644 --- a/usr.sbin/smtpd/lka.c +++ b/usr.sbin/smtpd/lka.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka.c,v 1.44 2009/05/01 21:44:19 gilles Exp $ */ +/* $OpenBSD: lka.c,v 1.45 2009/05/09 17:04:55 jacekm Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -22,11 +22,13 @@ #include <sys/tree.h> #include <sys/param.h> #include <sys/socket.h> +#include <sys/wait.h> #include <netinet/in.h> #include <arpa/inet.h> #include <ctype.h> +#include <errno.h> #include <event.h> #include <netdb.h> #include <pwd.h> @@ -69,16 +71,23 @@ int lka_expand_rcpt_iteration(struct smtpd *, struct aliaseslist *, struct lkas void lka_rcpt_action(struct smtpd *, struct path *); void lka_clear_aliaseslist(struct aliaseslist *); int lka_encode_credentials(char *, char *); -void lka_dns_reverse(struct session *s); void lka_sig_handler(int sig, short event, void *p) { + int status; + pid_t pid; + switch (sig) { case SIGINT: case SIGTERM: lka_shutdown(); break; + case SIGCHLD: + do { + pid = waitpid(-1, &status, WNOHANG); + } while (pid > 0 || (pid == -1 && errno == EINTR)); + break; default: fatalx("lka_sig_handler: unexpected signal"); } @@ -358,173 +367,31 @@ lka_dispatch_mta(int sig, short event, void *p) break; switch (imsg.hdr.type) { - case IMSG_LKA_MX: { - struct mxreq *mxreq; - struct mxrep mxrep; - struct addrinfo hints, *res, *resp; - char **mx = NULL; - char *lmx[1]; - int len, i; - int mxcnt; - int error; - struct mxhost mxhost; - char *secret = NULL; - - mxreq = imsg.data; - mxrep.id = mxreq->id; - mxrep.getaddrinfo_error = 0; - - if (mxreq->rule.r_action == A_RELAY) { - log_debug("attempting to resolve %s", mxreq->hostname); - len = getmxbyname(mxreq->hostname, &mx); - if (len < 0) { - mxrep.getaddrinfo_error = len; - imsg_compose(ibuf, IMSG_LKA_MX_END, 0, 0, -1, - &mxrep, sizeof(struct mxrep)); - break; - } - if (len == 0) { - lmx[0] = mxreq->hostname; - mx = lmx; - len = 1; - } - } - else if (mxreq->rule.r_action == A_RELAYVIA) { - lmx[0] = mxreq->rule.r_value.relayhost.hostname; - log_debug("attempting to resolve %s:%d (forced)", lmx[0], - ntohs(mxreq->rule.r_value.relayhost.port)); - mx = lmx; - len = 1; - - } - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_protocol = IPPROTO_TCP; - - for (i = 0; i < len; ++i) { - - error = getaddrinfo(mx[i], NULL, &hints, &res); - if (error) { - mxrep.getaddrinfo_error = error; - imsg_compose(ibuf, IMSG_LKA_MX_END, 0, 0, -1, - &mxrep, sizeof(struct mxrep)); - } - - if (mxreq->rule.r_action == A_RELAYVIA) - mxhost.flags = mxreq->rule.r_value.relayhost.flags; - - if (mxhost.flags & F_AUTH) { - secret = map_dblookup(env, "secrets", mx[i]); - if (secret == NULL) { - log_warnx("no credentials for relay through \"%s\"", mx[i]); - freeaddrinfo(res); - continue; - } - } - if (secret) - lka_encode_credentials(mxhost.credentials, secret); - - for (resp = res; resp != NULL && mxcnt < MAX_MX_COUNT; resp = resp->ai_next) { - - if (resp->ai_family == PF_INET) { - struct sockaddr_in *ssin; - - mxhost.ss = *(struct sockaddr_storage *)resp->ai_addr; - - ssin = (struct sockaddr_in *)&mxhost.ss; - if (mxreq->rule.r_value.relayhost.port != 0) { - ssin->sin_port = mxreq->rule.r_value.relayhost.port; - mxrep.mxhost = mxhost; - imsg_compose(ibuf, IMSG_LKA_MX, 0, 0, -1, &mxrep, - sizeof(struct mxrep)); - continue; - } - - switch (mxhost.flags & F_SSL) { - case F_SMTPS: - ssin->sin_port = htons(465); - mxrep.mxhost = mxhost; - imsg_compose(ibuf, IMSG_LKA_MX, 0, 0, -1, &mxrep, - sizeof(struct mxrep)); - break; - case F_SSL: - ssin->sin_port = htons(465); - mxrep.mxhost = mxhost; - mxrep.mxhost.flags &= ~F_STARTTLS; - imsg_compose(ibuf, IMSG_LKA_MX, 0, 0, -1, &mxrep, - sizeof(struct mxrep)); - case F_STARTTLS: - ssin->sin_port = htons(25); - mxrep.mxhost = mxhost; - mxrep.mxhost.flags &= ~F_SMTPS; - imsg_compose(ibuf, IMSG_LKA_MX, 0, 0, -1, &mxrep, - sizeof(struct mxrep)); - break; - default: - ssin->sin_port = htons(25); - mxrep.mxhost = mxhost; - imsg_compose(ibuf, IMSG_LKA_MX, 0, 0, -1, &mxrep, - sizeof(struct mxrep)); - } - } - - if (resp->ai_family == PF_INET6) { - struct sockaddr_in6 *ssin6; - - mxhost.ss = *(struct sockaddr_storage *)resp->ai_addr; - ssin6 = (struct sockaddr_in6 *)&mxhost.ss; - if (mxreq->rule.r_value.relayhost.port != 0) { - ssin6->sin6_port = mxreq->rule.r_value.relayhost.port; - mxrep.mxhost = mxhost; - imsg_compose(ibuf, IMSG_LKA_MX, 0, 0, -1, &mxrep, - sizeof(struct mxrep)); - continue; - } - - switch (mxhost.flags & F_SSL) { - case F_SMTPS: - ssin6->sin6_port = htons(465); - mxrep.mxhost = mxhost; - imsg_compose(ibuf, IMSG_LKA_MX, 0, 0, -1, &mxrep, - sizeof(struct mxrep)); - break; - case F_SSL: - ssin6->sin6_port = htons(465); - mxrep.mxhost = mxhost; - mxrep.mxhost.flags &= ~F_STARTTLS; - imsg_compose(ibuf, IMSG_LKA_MX, 0, 0, -1, &mxrep, - sizeof(struct mxrep)); - case F_STARTTLS: - ssin6->sin6_port = htons(25); - mxrep.mxhost = mxhost; - mxrep.mxhost.flags &= ~F_SMTPS; - imsg_compose(ibuf, IMSG_LKA_MX, 0, 0, -1, &mxrep, - sizeof(struct mxrep)); - break; - default: - ssin6->sin6_port = htons(25); - mxrep.mxhost = mxhost; - imsg_compose(ibuf, IMSG_LKA_MX, 0, 0, -1, &mxrep, - sizeof(struct mxrep)); - } - } - } - freeaddrinfo(res); - free(secret); - bzero(&mxhost.credentials, MAX_LINE_SIZE); - } + case IMSG_LKA_SECRET: { + struct secret *query = imsg.data; + char *secret = NULL; - mxrep.getaddrinfo_error = error; + secret = map_dblookup(env, "secrets", query->host); - imsg_compose(ibuf, IMSG_LKA_MX_END, 0, 0, -1, - &mxrep, sizeof(struct mxrep)); + log_debug("secret for %s %s", query->host, + secret ? "found" : "not found"); - if (mx != lmx) - free(mx); + if (secret) + lka_encode_credentials(query->secret, secret); + else + query->secret[0] = '\0'; + imsg_compose(ibuf, IMSG_LKA_SECRET, 0, 0, -1, query, + sizeof(*query)); + free(secret); break; } + case IMSG_DNS_A: + case IMSG_DNS_MX: + dns_async(env, ibuf, imsg.hdr.type, imsg.data); + break; + default: log_warnx("lka_dispatch_mta: got imsg %d", imsg.hdr.type); @@ -571,10 +438,8 @@ lka_dispatch_smtp(int sig, short event, void *p) break; switch (imsg.hdr.type) { - case IMSG_LKA_HOST: - lka_dns_reverse(imsg.data); - imsg_compose(ibuf, IMSG_LKA_HOST, 0, 0, -1, imsg.data, - sizeof(struct session)); + case IMSG_DNS_PTR: + dns_async(env, ibuf, IMSG_DNS_PTR, imsg.data); break; default: log_warnx("lka_dispatch_smtp: got imsg %d", @@ -703,6 +568,7 @@ lka(struct smtpd *env) struct event ev_sigint; struct event ev_sigterm; + struct event ev_sigchld; struct peer peers[] = { { PROC_PARENT, lka_dispatch_parent }, @@ -741,8 +607,10 @@ lka(struct smtpd *env) signal_set(&ev_sigint, SIGINT, lka_sig_handler, env); signal_set(&ev_sigterm, SIGTERM, lka_sig_handler, env); + signal_set(&ev_sigchld, SIGCHLD, lka_sig_handler, env); signal_add(&ev_sigint, NULL); signal_add(&ev_sigterm, NULL); + signal_add(&ev_sigchld, NULL); signal(SIGPIPE, SIG_IGN); signal(SIGHUP, SIG_IGN); @@ -1238,30 +1106,4 @@ lka_encode_credentials(char *dest, char *src) return 1; } -void -lka_dns_reverse(struct session *s) -{ - char addr[NI_MAXHOST]; - struct addrinfo hints, *res; - struct sockaddr *sa; - - strlcpy(s->s_hostname, "<unknown>", sizeof(s->s_hostname)); - - sa = (struct sockaddr *)&s->s_ss; - if (getnameinfo(sa, sa->sa_len, addr, sizeof(addr), - NULL, 0, NI_NAMEREQD)) - return; - - memset(&hints, 0, sizeof(hints)); - hints.ai_socktype = SOCK_DGRAM; - hints.ai_flags = AI_NUMERICHOST; - - if (getaddrinfo(addr, NULL, &hints, &res)) - return; /* malicious PTR record. */ - - freeaddrinfo(res); - - strlcpy(s->s_hostname, addr, sizeof(s->s_hostname)); -} - SPLAY_GENERATE(lkatree, lkasession, nodes, lkasession_cmp); diff --git a/usr.sbin/smtpd/mta.c b/usr.sbin/smtpd/mta.c index 5ed262d8911..580ecde45aa 100644 --- a/usr.sbin/smtpd/mta.c +++ b/usr.sbin/smtpd/mta.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mta.c,v 1.44 2009/04/28 21:56:36 gilles Exp $ */ +/* $OpenBSD: mta.c,v 1.45 2009/05/09 17:04:55 jacekm Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -152,14 +152,13 @@ mta_dispatch_lka(int sig, short event, void *p) break; switch (imsg.hdr.type) { - case IMSG_LKA_MX: { + case IMSG_DNS_A: { struct session key; - struct mxrep *mxrep; + struct dns *reply = imsg.data; struct session *s; struct mxhost *mxhost; - mxrep = imsg.data; - key.s_id = mxrep->id; + key.s_id = reply->id; s = SPLAY_FIND(sessiontree, &env->sc_sessions, &key); if (s == NULL) @@ -169,19 +168,20 @@ mta_dispatch_lka(int sig, short event, void *p) if (mxhost == NULL) fatal("mta_dispatch_lka: calloc"); - *mxhost = mxrep->mxhost; + mxhost->ss = reply->ss; + TAILQ_INSERT_TAIL(&s->mxhosts, mxhost, entry); break; } - case IMSG_LKA_MX_END: { + + case IMSG_DNS_A_END: { struct session key; - struct mxrep *mxrep; + struct dns *reply = imsg.data; struct session *s; int ret; - mxrep = imsg.data; - key.s_id = mxrep->id; + key.s_id = reply->id; s = SPLAY_FIND(sessiontree, &env->sc_sessions, &key); if (s == NULL) @@ -192,7 +192,7 @@ mta_dispatch_lka(int sig, short event, void *p) do { ret = mta_connect(s); } while (ret == 0); - + if (ret < 0) { mta_batch_update_queue(s->batch); session_destroy(s); @@ -200,6 +200,25 @@ mta_dispatch_lka(int sig, short event, void *p) break; } + + case IMSG_LKA_SECRET: { + struct secret *reply = imsg.data; + struct session key, *s; + + key.s_id = reply->id; + + s = SPLAY_FIND(sessiontree, &env->sc_sessions, &key); + if (s == NULL) + fatal("smtp_dispatch_parent: session is gone"); + + strlcpy(s->credentials, reply->secret, + sizeof(s->credentials)); + + mta_mxlookup(env, s, s->batch->hostname, + &s->batch->rule); + break; + } + default: log_warnx("mta_dispatch_parent: got imsg %d", imsg.hdr.type); @@ -386,8 +405,21 @@ mta_dispatch_runner(int sig, short event, void *p) s = batchp->sessionp; - mta_mxlookup(env, s, batchp->hostname, &batchp->rule); - + if (batchp->rule.r_value.relayhost.flags & F_AUTH) { + struct secret query; + + bzero(&query, sizeof(query)); + query.id = s->s_id; + strlcpy(query.host, + batchp->rule.r_value.relayhost.hostname, + sizeof(query.host)); + + imsg_compose(env->sc_ibufs[PROC_LKA], + IMSG_LKA_SECRET, 0, 0, -1, &query, + sizeof(query)); + } else + mta_mxlookup(env, s, batchp->hostname, + &batchp->rule); break; } default: @@ -488,13 +520,28 @@ mta(struct smtpd *env) void mta_mxlookup(struct smtpd *env, struct session *sessionp, char *hostname, struct rule *rule) { - struct mxreq mxreq; + int port; - mxreq.id = sessionp->s_id; - mxreq.rule = *rule; - (void)strlcpy(mxreq.hostname, hostname, MAXHOSTNAMELEN); - imsg_compose(env->sc_ibufs[PROC_LKA], IMSG_LKA_MX, 0, 0, -1, - &mxreq, sizeof(struct mxreq)); + switch (rule->r_value.relayhost.flags & F_SSL) { + case F_SMTPS: + port = 465; + break; + case F_SSL: + port = 465; + rule->r_value.relayhost.flags &= ~F_STARTTLS; + break; + default: + port = 25; + } + + if (rule->r_value.relayhost.port) + port = ntohs(rule->r_value.relayhost.port); + + if (rule->r_action == A_RELAYVIA) + dns_query_a(env, rule->r_value.relayhost.hostname, port, + sessionp->s_id); + else + dns_query_mx(env, hostname, port, sessionp->s_id); } /* shamelessly ripped usr.sbin/relayd/check_tcp.c ;) */ @@ -504,8 +551,6 @@ mta_connect(struct session *sessionp) int s; int type; struct linger lng; - struct sockaddr_in ssin; - struct sockaddr_in6 ssin6; struct mxhost *mxhost; mxhost = TAILQ_FIRST(&sessionp->mxhosts); @@ -525,19 +570,9 @@ mta_connect(struct session *sessionp) session_socket_blockmode(s, BM_NONBLOCK); - if (mxhost->ss.ss_family == PF_INET) { - ssin = *(struct sockaddr_in *)&mxhost->ss; - if (connect(s, (struct sockaddr *)&ssin, sizeof(struct sockaddr_in)) == -1) - if (errno != EINPROGRESS) - goto bad; - } - - if (mxhost->ss.ss_family == PF_INET6) { - ssin6 = *(struct sockaddr_in6 *)&mxhost->ss; - if (connect(s, (struct sockaddr *)&ssin6, sizeof(struct sockaddr_in6)) == -1) - if (errno != EINPROGRESS) - goto bad; - } + if (connect(s, (struct sockaddr *)&mxhost->ss, mxhost->ss.ss_len) == -1) + if (errno != EINPROGRESS) + goto bad; sessionp->s_tv.tv_sec = SMTPD_CONNECT_TIMEOUT; sessionp->s_tv.tv_usec = 0; @@ -602,7 +637,8 @@ mta_write(int s, short event, void *arg) return; } - if (mxhost && mxhost->flags & F_SMTPS) { + if (sessionp->batch->rule.r_value.relayhost.flags & F_SMTPS) { + log_debug("mta_write: initializing ssl"); ssl_client_init(sessionp); return; } @@ -631,7 +667,6 @@ mta_reply_handler(struct bufferevent *bev, void *arg) char codebuf[4]; const char *errstr; int flags = 0; - struct mxhost *mxhost = TAILQ_FIRST(&sessionp->mxhosts); line = evbuffer_readline(bev->input); if (line == NULL) @@ -679,18 +714,18 @@ mta_reply_handler(struct bufferevent *bev, void *arg) if (sessionp->s_state == S_GREETED && (sessionp->s_flags & F_PEERHASAUTH) && (sessionp->s_flags & F_SECURE) && - (mxhost->flags & F_AUTH) && - (mxhost->credentials[0] != '\0')) { - log_debug("AUTH PLAIN %s", mxhost->credentials); + (sessionp->batch->rule.r_value.relayhost.flags & F_AUTH) && + (sessionp->credentials[0] != '\0')) { + log_debug("AUTH PLAIN %s", sessionp->credentials); session_respond(sessionp, "AUTH PLAIN %s", - mxhost->credentials); + sessionp->credentials); sessionp->s_state = S_AUTH_INIT; return 0; } if (sessionp->s_state == S_GREETED && !(sessionp->s_flags & F_PEERHASTLS) && - mxhost->flags & F_STARTTLS) { + (sessionp->batch->rule.r_value.relayhost.flags&F_STARTTLS)){ /* PERM - we want TLS but it is not advertised */ batchp->status = S_BATCH_PERMFAILURE; mta_batch_update_queue(batchp); @@ -700,7 +735,7 @@ mta_reply_handler(struct bufferevent *bev, void *arg) if (sessionp->s_state == S_GREETED && !(sessionp->s_flags & F_PEERHASAUTH) && - mxhost->flags & F_AUTH) { + (sessionp->batch->rule.r_value.relayhost.flags & F_AUTH)) { /* PERM - we want AUTH but it is not advertised */ batchp->status = S_BATCH_PERMFAILURE; mta_batch_update_queue(batchp); diff --git a/usr.sbin/smtpd/smtp.c b/usr.sbin/smtpd/smtp.c index 5b8e2fa52d2..dfebcc69e76 100644 --- a/usr.sbin/smtpd/smtp.c +++ b/usr.sbin/smtpd/smtp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtp.c,v 1.41 2009/04/28 22:38:22 jacekm Exp $ */ +/* $OpenBSD: smtp.c,v 1.42 2009/05/09 17:04:55 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -283,21 +283,22 @@ smtp_dispatch_lka(int sig, short event, void *p) break; switch (imsg.hdr.type) { - case IMSG_LKA_HOST: { - struct session key; + case IMSG_DNS_PTR: { + struct dns *reply = imsg.data; struct session *s; - struct session *ss; + struct session key; - ss = imsg.data; - key.s_id = ss->s_id; + key.s_id = reply->id; s = SPLAY_FIND(sessiontree, &env->sc_sessions, &key); if (s == NULL) fatal("smtp_dispatch_lka: session is gone"); - strlcpy(s->s_hostname, ss->s_hostname, + strlcpy(s->s_hostname, + reply->error ? "<unknown>" : reply->host, sizeof(s->s_hostname)); - strlcpy(s->s_msg.session_hostname, ss->s_hostname, + + strlcpy(s->s_msg.session_hostname, s->s_hostname, sizeof(s->s_msg.session_hostname)); session_init(s->s_l, s); @@ -716,8 +717,7 @@ smtp_accept(int fd, short event, void *p) if (s_smtp.sessions_active == s->s_env->sc_maxconn) event_del(&l->ev); - imsg_compose(s->s_env->sc_ibufs[PROC_LKA], IMSG_LKA_HOST, 0, 0, -1, s, - sizeof(struct session)); + dns_query_ptr(l->env, &s->s_ss, s->s_id); SPLAY_INSERT(sessiontree, &s->s_env->sc_sessions, s); } diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h index cd49ff05d72..be43006cf89 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.105 2009/04/28 23:11:25 gilles Exp $ */ +/* $OpenBSD: smtpd.h,v 1.106 2009/05/09 17:04:55 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -66,7 +66,6 @@ #define PATH_OFFLINE "/offline" /* number of MX records to lookup */ -#define MXARRAYSIZE 5 #define MAX_MX_COUNT 10 /* rfc5321 limits */ @@ -93,9 +92,7 @@ struct relayhost { struct mxhost { TAILQ_ENTRY(mxhost) entry; - u_int8_t flags; struct sockaddr_storage ss; - char credentials[MAX_LINE_SIZE]; }; /* buffer specific headers */ @@ -171,9 +168,7 @@ enum imsg_type { IMSG_CONF_RELOAD, IMSG_LKA_MAIL, IMSG_LKA_RCPT, - IMSG_LKA_MX, - IMSG_LKA_MX_END, - IMSG_LKA_HOST, + IMSG_LKA_SECRET, IMSG_MDA_MAILBOX_FILE, IMSG_MDA_MESSAGE_FILE, IMSG_MFA_RCPT, @@ -221,7 +216,12 @@ enum imsg_type { IMSG_STATS, - IMSG_SMTP_ENQUEUE + IMSG_SMTP_ENQUEUE, + + IMSG_DNS_A, + IMSG_DNS_A_END, + IMSG_DNS_MX, + IMSG_DNS_PTR }; #define IMSG_HEADER_SIZE sizeof(struct imsg_hdr) @@ -614,6 +614,8 @@ struct session { struct session_auth_req s_auth; + char credentials[MAX_LINE_SIZE]; + struct batch *batch; TAILQ_HEAD(mxhostlist, mxhost) mxhosts; @@ -717,16 +719,20 @@ struct forward_req { char pw_name[MAXLOGNAME]; }; -struct mxreq { - u_int64_t id; - char hostname[MAXHOSTNAMELEN]; - struct rule rule; +struct dns { + u_int64_t id; + char host[MAXHOSTNAMELEN]; + int port; + int error; + struct sockaddr_storage ss; + struct smtpd *env; + struct dns *next; }; -struct mxrep { - u_int64_t id; - int getaddrinfo_error; - struct mxhost mxhost; +struct secret { + u_int64_t id; + char host[MAXHOSTNAMELEN]; + char secret[MAX_LINE_SIZE]; }; enum lkasession_flags { @@ -781,7 +787,12 @@ int msgbuf_write(struct msgbuf *); /* dns.c */ -int getmxbyname(char *, char ***); +void dns_query_a(struct smtpd *, char *, int, u_int64_t); +void dns_query_mx(struct smtpd *, char *, int, u_int64_t); +void dns_query_ptr(struct smtpd *, struct sockaddr_storage *, + u_int64_t); +void dns_async(struct smtpd *, struct imsgbuf *, int, + struct dns *); /* forward.c */ |