diff options
Diffstat (limited to 'usr.sbin/smtpd')
-rw-r--r-- | usr.sbin/smtpd/asr.c | 2233 | ||||
-rw-r--r-- | usr.sbin/smtpd/asr.h | 82 | ||||
-rw-r--r-- | usr.sbin/smtpd/dname.c | 219 | ||||
-rw-r--r-- | usr.sbin/smtpd/dns.c | 723 | ||||
-rw-r--r-- | usr.sbin/smtpd/dnsdefs.h | 165 | ||||
-rw-r--r-- | usr.sbin/smtpd/dnsutil.h | 189 | ||||
-rw-r--r-- | usr.sbin/smtpd/lka.c | 4 | ||||
-rw-r--r-- | usr.sbin/smtpd/mta.c | 15 | ||||
-rw-r--r-- | usr.sbin/smtpd/pack.c | 469 | ||||
-rw-r--r-- | usr.sbin/smtpd/print.c | 395 | ||||
-rw-r--r-- | usr.sbin/smtpd/res_random.c | 270 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.h | 40 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd/Makefile | 5 | ||||
-rw-r--r-- | usr.sbin/smtpd/sockaddr.c | 176 | ||||
-rw-r--r-- | usr.sbin/smtpd/util.c | 133 |
15 files changed, 4703 insertions, 415 deletions
diff --git a/usr.sbin/smtpd/asr.c b/usr.sbin/smtpd/asr.c new file mode 100644 index 00000000000..0a878a7ec5b --- /dev/null +++ b/usr.sbin/smtpd/asr.c @@ -0,0 +1,2233 @@ +/* + * Copyright (c) 2010 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/uio.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr.h" +#include "dnsutil.h" + +#define unused __attribute__ ((unused)) + +#define DEFAULT_CONFFILE "/etc/resolv.conf" +#define DEFAULT_HOSTFILE "/etc/hosts" +#define DEFAULT_CONF "lookup bind file\nnameserver 127.0.0.1\n" +#define DEFAULT_LOOKUP "lookup bind file" + +#define ASR_MAXNS 5 +#define ASR_MAXDB 3 +#define ASR_MAXDOM 10 + +enum asr_query_type { + ASR_QUERY_DNS, + ASR_QUERY_HOST, + ASR_QUERY_ADDRINFO +}; + +enum asr_db_type { + ASR_DB_FILE, + ASR_DB_DNS, + ASR_DB_YP, +}; +struct asr_db { + int ad_type; + char *ad_path; + int ad_timeout; + int ad_retries; + int ad_count; + struct sockaddr *ad_sa[ASR_MAXNS]; +}; + +struct asr_ctx { + int ac_refcount; + int ac_ndots; + int ac_forcetcp; + char *ac_domain; + int ac_domcount; + char *ac_dom[ASR_MAXDOM]; + int ac_dbcount; + struct asr_db ac_db[ASR_MAXDB]; + int ac_family[3]; +}; + +struct asr { + char *a_path; + time_t a_mtime; + struct asr_ctx *a_ctx; +}; + +struct asr_query { + + struct asr_ctx *aq_ctx; + int aq_type; + int aq_flags; + int aq_state; + + int aq_timeout; + int aq_fd; + + int aq_dom_idx; + int aq_family_idx; + int aq_db_idx; + int aq_ns_idx; + int aq_ns_cycles; + /* for dns */ + char *aq_fqdn; /* the fqdn being looked for */ + struct query aq_query; + uint16_t aq_reqid; + char *aq_buf; + size_t aq_buflen; + size_t aq_bufsize; + size_t aq_bufoffset; /* for TCP */ + uint16_t aq_datalen; /* for TCP */ + struct packed aq_packed; + int aq_nanswer; + + /* for host */ + char *aq_host; + int aq_family; + int aq_count; + FILE *aq_file; + + /* for addrinfo */ + char *aq_hostname; + char *aq_servname; + struct addrinfo aq_hints; + struct asr_query *aq_subq; + struct addrinfo *aq_aifirst; + struct addrinfo *aq_ailast; +}; + +#define AQ_FAMILY(p) ((p)->aq_ctx->ac_family[(p)->aq_family_idx]) +#define AQ_DB(p) (&((p)->aq_ctx->ac_db[(p)->aq_db_idx])) +#define AQ_NS_SA(p) (AQ_DB(p)->ad_sa[(p)->aq_ns_idx]) +#define AQ_BUF_LEFT(p) ((p)->aq_bufsize - (p)->aq_buflen) +#define AQ_BUF_DATA(p) ((p)->aq_buf + (p)->aq_bufoffset) +#define AQ_BUF_LEN(p) ((p)->aq_buflen - (p)->aq_bufoffset) +#define AQ_BUF_WPOS(p) ((p)->aq_buf + (p)->aq_buflen) + +enum asr_state { + ASR_STATE_INIT, + ASR_STATE_NEXT_DOMAIN, + ASR_STATE_SEARCH_DOMAIN, + ASR_STATE_NEXT_DB, + ASR_STATE_QUERY_DB, + ASR_STATE_NEXT_FAMILY, + ASR_STATE_LOOKUP_FAMILY, + ASR_STATE_NEXT_NS, + ASR_STATE_QUERY_NS, + ASR_STATE_READ_RR, + ASR_STATE_QUERY_FILE, + ASR_STATE_READ_FILE, + ASR_STATE_UDP_SEND, + ASR_STATE_UDP_RECV, + ASR_STATE_TCP_WRITE, + ASR_STATE_TCP_READ, + ASR_STATE_PACKET, + ASR_STATE_NEXT_MATCH, + ASR_STATE_TRY_MATCH, + ASR_STATE_SUBQUERY, + ASR_STATE_HALT, +}; + +/* misc utility functions */ + +int asr_ndots(const char *); +int asr_is_fqdn(const char *); +int asr_cmp_fqdn_name(const char*, char*); +char *asr_make_fqdn(const char *, const char *); +int asr_parse_nameserver(struct sockaddr *, const char *); + +/* query functions */ +int asr_run_dns(struct asr_query *, struct asr_result *); +int asr_run_host(struct asr_query *, struct asr_result *); +int asr_run_addrinfo(struct asr_query *, struct asr_result *); + +/* a few helpers */ +const char * asr_error(int); + +void asr_check_reload(struct asr *); +void asr_query_free(struct asr_query *); +int asr_iter_family(struct asr_query *, int); +int asr_ensure_buf(struct asr_query *, size_t); +int asr_setup_packet(struct asr_query *); +int asr_validate_packet(struct asr_query *); +int asr_udp_send(struct asr_query *); +int asr_udp_recv(struct asr_query *); +int asr_tcp_write(struct asr_query *); +int asr_tcp_read(struct asr_query *); +int asr_parse_hosts_cb(char **, int, void*, void*); +int asr_parse_namedb_line(FILE *, char **, int); +int asr_get_port(const char *, const char *, int); +int asr_add_sockaddr(struct asr_query *, struct sockaddr *); +int asr_add_sockaddr2(struct asr_query *, struct sockaddr *, int, int); +int asr_db_add_nameserver(struct asr_db *, const char *); +void asr_db_done(struct asr_db *); + +struct asr_ctx *asr_ctx_create(void); +int asr_ctx_unref(struct asr_ctx *); +int asr_ctx_add_searchdomain(struct asr_ctx *, const char *); +int asr_ctx_from_file(struct asr_ctx *, const char *); +int asr_ctx_from_string(struct asr_ctx *, const char *); +int asr_ctx_parse_cb(const char *, + int (*)(char**, int, void*, void*), + void *, void *); +struct asr_query *asr_ctx_query(struct asr_ctx *, int); +struct asr_query *asr_ctx_query_host(struct asr_ctx *, const char *, int); + +#ifdef ASR_DEBUG + +void asr_dump(struct asr *); +void asr_dump_query(struct asr_query *); + +struct kv { int code; const char *name; }; + +static const char* kvlookup(struct kv *, int); + +int asr_debug = 0; + +void +asr_dump(struct asr *a) +{ + char buf[256]; + int i, j; + struct asr_db *ad; + struct asr_ctx *ac; + + ac = a->a_ctx; + + printf("--------- ASR CONFIG ---------------\n"); + printf("DOMAIN \"%s\"\n", ac->ac_domain); + printf("SEARCH\n"); + for(i = 0; i < ac->ac_domcount; i++) + printf(" \"%s\"\n", ac->ac_dom[i]); + printf("OPTIONS\n"); + printf(" forcetcp: %i\n", ac->ac_forcetcp); + printf(" ndots: %i\n", ac->ac_ndots); + printf(" family: "); + for(i = 0; ac->ac_family[i] != -1; i++) + printf(" %s", (ac->ac_family[i] == AF_INET) ? "inet" : "inet6"); + printf("\n"); + printf("DB\n"); + for(ad = ac->ac_db, i = 0; i < ac->ac_dbcount; i++, ad++) { + switch (ad->ad_type) { + case ASR_DB_FILE: + printf(" FILE \"%s\"\n", ad->ad_path); + break; + case ASR_DB_DNS: + printf(" DNS timeout %ims, retries %i\n", + ad->ad_timeout, + ad->ad_retries); + for(j = 0; j < ad->ad_count; j++) + printf(" NS %s\n", + print_addr(ad->ad_sa[j], buf, + sizeof buf)); + break; + case ASR_DB_YP: + printf(" YP\n"); + break; + default: + printf(" - ???? %i\n", ad->ad_type); + } + } + printf("------------------------------------\n"); +} + +static const char * +kvlookup(struct kv *kv, int code) +{ + while (kv->name) { + if (kv->code == code) + return (kv->name); + kv++; + } + return "???"; +} + +struct kv kv_query_type[] = { + { ASR_QUERY_DNS, "ASR_QUERY_DNS" }, + { ASR_QUERY_HOST, "ASR_QUERY_HOST" }, + { ASR_QUERY_ADDRINFO, "ASR_QUERY_ADDRINFO" }, + { 0, NULL } +}; + +struct kv kv_db_type[] = { + { ASR_DB_FILE, "ASR_DB_FILE" }, + { ASR_DB_DNS, "ASR_DB_DNS" }, + { ASR_DB_YP, "ASR_DB_YP" }, + { 0, NULL } +}; + +struct kv kv_state[] = { + { ASR_STATE_INIT, "ASR_STATE_INIT" }, + { ASR_STATE_NEXT_DOMAIN, "ASR_STATE_NEXT_DOMAIN" }, + { ASR_STATE_SEARCH_DOMAIN, "ASR_STATE_SEARCH_DOMAIN" }, + { ASR_STATE_NEXT_DB, "ASR_STATE_NEXT_DB" }, + { ASR_STATE_QUERY_DB, "ASR_STATE_QUERY_DB" }, + { ASR_STATE_NEXT_FAMILY, "ASR_STATE_NEXT_FAMILY" }, + { ASR_STATE_LOOKUP_FAMILY, "ASR_STATE_LOOKUP_FAMILY" }, + { ASR_STATE_NEXT_NS, "ASR_STATE_NEXT_NS" }, + { ASR_STATE_QUERY_NS, "ASR_STATE_QUERY_NS" }, + { ASR_STATE_READ_RR, "ASR_STATE_READ_RR" }, + { ASR_STATE_QUERY_FILE, "ASR_STATE_QUERY_FILE" }, + { ASR_STATE_READ_FILE, "ASR_STATE_READ_FILE" }, + { ASR_STATE_UDP_SEND, "ASR_STATE_UDP_SEND" }, + { ASR_STATE_UDP_RECV, "ASR_STATE_UDP_RECV" }, + { ASR_STATE_TCP_WRITE, "ASR_STATE_TCP_WRITE" }, + { ASR_STATE_TCP_READ, "ASR_STATE_TCP_READ" }, + { ASR_STATE_PACKET, "ASR_STATE_PACKET" }, + { ASR_STATE_NEXT_MATCH, "ASR_STATE_NEXT_MATCH" }, + { ASR_STATE_TRY_MATCH, "ASR_STATE_TRY_MATCH" }, + { ASR_STATE_SUBQUERY, "ASR_STATE_SUBQUERY" }, + { ASR_STATE_HALT, "ASR_STATE_HALT" }, + { 0, NULL } +}; + +struct kv kv_transition[] = { + { ASR_NEED_READ, "ASR_NEED_READ" }, + { ASR_NEED_WRITE, "ASR_NEED_WRITE" }, + { ASR_YIELD, "ASR_YIELD" }, + { ASR_DONE, "ASR_DONE" }, + { 0, NULL } +}; + +void +asr_dump_query(struct asr_query *aq) +{ + printf("%-25s fqdn=%s dom %-2i fam %-2i famidx %-2i db %-2i ns %-2i ns_cycles %-2i fd %-2i %ims", + kvlookup(kv_state, aq->aq_state), + aq->aq_fqdn, + aq->aq_dom_idx, + aq->aq_family, + aq->aq_family_idx, + aq->aq_db_idx, + aq->aq_ns_idx, + aq->aq_ns_cycles, + aq->aq_fd, + aq->aq_timeout); + printf("\n"); +} + +#endif /* ASR_DEBUG */ + +struct asr * +asr_resolver(const char *conf) +{ + int r; + struct asr *asr; + +#ifdef ASR_DEBUG + if (asr_debug == 0) + if(getenv("ASR_DEBUG")) { + printf("asr: %zu\n", sizeof(struct asr)); + printf("asr_ctx: %zu\n", sizeof(struct asr_ctx)); + printf("asr_db: %zu\n", sizeof(struct asr_db)); + printf("asr_query: %zu\n", sizeof(struct asr_query)); + printf("asr_result: %zu\n", sizeof(struct asr_result)); + asr_debug = 1; + } +#endif + if ((asr = calloc(1, sizeof(*asr))) == NULL) + return (NULL); + + if ((asr->a_ctx = asr_ctx_create()) == NULL) { + free(asr); + return (NULL); + } + + if (conf == NULL) + conf = DEFAULT_CONFFILE; + + if (conf[0] == '!') { + r = asr_ctx_from_string(asr->a_ctx, conf + 1); + } else { + r = 0; + asr->a_path = strdup(conf); + asr_check_reload(asr); + if (asr->a_ctx == NULL) + r = asr_ctx_from_string(asr->a_ctx, DEFAULT_CONF); + } + + if (r == -1) { + asr_ctx_unref(asr->a_ctx); + free(asr); + return (NULL); + } + +#ifdef ASR_DEBUG + if (asr_debug) + asr_dump(asr); +#endif + + return (asr); +} + +void +asr_abort(struct asr_query *aq) +{ + asr_query_free(aq); +} + +int +asr_run(struct asr_query *aq, struct asr_result *ar) +{ + int r; + +#ifdef ASR_DEBUG + if (asr_debug) { + printf("-> QUERY %p(%p) %s\n", + aq, aq->aq_ctx, + kvlookup(kv_query_type, aq->aq_type)); + } +#endif + + switch(aq->aq_type) { + case ASR_QUERY_DNS: + r = asr_run_dns(aq, ar); + break; + case ASR_QUERY_HOST: + r = asr_run_host(aq, ar); + break; + case ASR_QUERY_ADDRINFO: + r = asr_run_addrinfo(aq, ar); + break; + default: + ar->ar_err = EOPNOTSUPP; + ar->ar_errstr = "unknown query type"; + r = ASR_DONE; + } +#ifdef ASR_DEBUG + if (asr_debug) { + printf("<- "); + asr_dump_query(aq); + printf(" = %s\n", kvlookup(kv_transition, r)); + } +#endif + if (r == ASR_DONE) + asr_query_free(aq); + + return (r); +} + +int +asr_run_sync(struct asr_query *aq, struct asr_result *ar) +{ + struct pollfd fds[1]; + int r; + + for(;;) { + r = asr_run(aq, ar); + if (r == ASR_DONE || r == ASR_YIELD) + break; + fds[0].fd = ar->ar_fd; + fds[0].events = (r == ASR_NEED_READ) ? POLLIN : POLLOUT; + again: + r = poll(fds, 1, ar->ar_timeout); + if (r == -1 && errno == EINTR) + goto again; + if (r == -1) /* impossible? */ + err(1, "poll"); + } + + return r; +} + +void +asr_check_reload(struct asr *asr) +{ + struct stat st; + struct asr_ctx *ac; + + if (asr->a_path == NULL) + return; + + if (stat(asr->a_path, &st) == -1) + return; + + if (asr->a_mtime == st.st_mtime) + return; + + if ((ac = asr_ctx_create()) == NULL) + return; + + asr->a_mtime = st.st_mtime; + + if (asr_ctx_from_file(ac, asr->a_path) == -1) { + asr_ctx_unref(ac); + return; + } + + if (asr->a_ctx) + asr_ctx_unref(asr->a_ctx); + asr->a_ctx = ac; +} + +struct asr_ctx * +asr_ctx_create(void) +{ + struct asr_ctx *ac; + + if ((ac = calloc(1, sizeof(*ac))) == NULL) + return (NULL); + + ac->ac_refcount = 1; + ac->ac_ndots = 1; + ac->ac_family[0] = AF_INET; + ac->ac_family[1] = AF_INET6; + ac->ac_family[2] = -1; + + return (ac); +} + +int +asr_ctx_unref(struct asr_ctx *ac) +{ + int i; + + ac->ac_refcount--; + + if (ac->ac_refcount == 0) { + if (ac->ac_domain) + free(ac->ac_domain); + + for(i = 0; i < ac->ac_dbcount; i++) + asr_db_done(&ac->ac_db[i]); + + for(i = 0; i < ac->ac_domcount; i++) + free(ac->ac_dom[i]); + + free(ac); + return (0); + } + + return (ac->ac_refcount); +} + +int +asr_ctx_add_searchdomain(struct asr_ctx *ac, const char *domain) +{ + if (ac->ac_domcount == ASR_MAXDOM) + return (-1); + + if ((ac->ac_dom[ac->ac_domcount] = asr_make_fqdn(domain, NULL)) == NULL) + return (0); + + ac->ac_domcount += 1; + + return (1); +} + +static int +pass0(char **tok, int n, void *a0, void *a1) +{ + struct asr_ctx *ac = (struct asr_ctx*)a0; + struct asr_db *ad; + int *nscount = (int*)a1; + int i, j, d; + const char *e; + + /* search for lookup, domain, family, options, and count nameservers */ + + if (!strcmp(tok[0], "nameserver")) { + *nscount += 1; + + } else if (!strcmp(tok[0], "domain")) { + if (n != 2) + return (0); + if (ac->ac_domain) + return (0); + ac->ac_domain = strdup(tok[1]); + } else if (!strcmp(tok[0], "lookup")) { + /* ignore the line if we already set lookup */ + if (ac->ac_dbcount != 0) + return (0); + if (n - 1 > ASR_MAXDB) + return (0); + /* ensure that each lookup is only given once */ + for(i = 1; i < n; i++) + for(j = i + 1; j < n; j++) + if (!strcmp(tok[i], tok[j])) + return (0); + for(i = 1, ad = ac->ac_db; i < n; + i++, ac->ac_dbcount++, ad++) { + + if (!strcmp(tok[i], "yp")) { + ad->ad_type = ASR_DB_YP; + + } else if (!strcmp(tok[i], "bind")) { + ad->ad_type = ASR_DB_DNS; + ad->ad_count = 0; + ad->ad_timeout = 1000; + ad->ad_retries = 3; + + } else if (!strcmp(tok[i], "file")) { + ad->ad_type = ASR_DB_FILE; + ad->ad_path = strdup(DEFAULT_HOSTFILE); + } else { + /* ignore the line */ + ac->ac_dbcount = 0; + return (0); + } + } + } else if (!strcmp(tok[0], "search")) { + /* resolv.conf says the last line wins */ + for(i = 0; i < ac->ac_domcount; i++) + free(ac->ac_dom[i]); + ac->ac_domcount = 0; + for(i = 1; i < n; i++) + asr_ctx_add_searchdomain(ac, tok[i]); + } else if (!strcmp(tok[0], "family")) { + if (n == 1 || n > 3) + return (0); + for (i = 1; i < n; i++) + if (strcmp(tok[i], "inet4") && strcmp(tok[i], "inet6")) + return (0); + for (i = 1; i < n; i++) + ac->ac_family[i - 1] = strcmp(tok[i], "inet4") ? \ + AF_INET6 : AF_INET; + ac->ac_family[i - 1] = -1; + } else if (!strcmp(tok[0], "option")) { + for(i = 1; i < n; i++) { + if (!strcmp(tok[i], "tcp")) + ac->ac_forcetcp = 1; + else if ((!strncmp(tok[i], "ndots:", 6))) { + e = NULL; + d = strtonum(tok[i] + 6, 1, 16, &e); + if (e == NULL) + ac->ac_ndots = d; + } + } + } + + return (0); +} + +static int +pass1(char **tok, int n, void *a0, unused void *a1) +{ + struct asr_db *ad = (struct asr_db*) a0; + + /* fill the DNS db with the specified nameservers */ + + if (!strcmp(tok[0], "nameserver")) { + if (n != 2) + return (0); + asr_db_add_nameserver(ad, tok[1]); + } + return (0); +} + +int +asr_ctx_from_string(struct asr_ctx *ac, const char *str) +{ + char buf[512], *ch; + struct asr_db *ad; + int i; + int nscount = 0; + + asr_ctx_parse_cb(str, pass0, ac, &nscount); + + if (ac->ac_dbcount == 0) { + /* no lookup directive */ + asr_ctx_parse_cb(DEFAULT_LOOKUP, pass0, ac, &nscount); + } + + ad = NULL; + for(i = 0; i < ac->ac_dbcount; i++) + if (ac->ac_db[i].ad_type == ASR_DB_DNS) { + ad = &ac->ac_db[i]; + break; + } + + if (nscount && ad) + asr_ctx_parse_cb(str, pass1, ad, NULL); + + if (ac->ac_domain == NULL) + if (gethostname(buf, sizeof buf) == 0) { + ch = strchr(buf, '.'); + if (ch) + ac->ac_domain = strdup(ch + 1); + else /* assume root. see resolv.conf(5) */ + ac->ac_domain = strdup(""); + } + + if (ac->ac_domcount == 0) + for(ch = ac->ac_domain; ch; ) { + asr_ctx_add_searchdomain(ac, ch); + ch = strchr(ch, '.'); + if (ch && asr_ndots(++ch) == 0) + break; + } + + return (0); +} + +int +asr_ctx_from_file(struct asr_ctx *ac, const char *path) +{ + FILE *cf; + char buf[1024]; + ssize_t r; + + cf = fopen(path, "r"); + if (cf == NULL) + return (-1); + + /* XXX make sure we read the whole file */ + r = fread(buf, 1, sizeof buf - 1, cf); + fclose(cf); + if (r == -1) + return (-1); + buf[r] = '\0'; + + return asr_ctx_from_string(ac, buf); +} + +int +asr_ctx_parse_cb(const char *str, + int (*cb)(char**, int, void*, void*), + void *arg0, + void *arg1) +{ + size_t len; + const char *line; + char buf[1024]; + char *tok[10], **tp, *cp; + int ntok; + + line = str; + while (*line) { + len = strcspn(line, "\n\0"); + if (len < sizeof buf) { + memmove(buf, line, len); + buf[len] = '\0'; + } else + buf[0] = '\0'; + line += len; + if (*line == '\n') + line++; + buf[strcspn(buf, ";#")] = '\0'; + for(cp = buf, tp = tok, ntok = 0; + tp < &tok[10] && (*tp = strsep(&cp, " \t")) != NULL;) + if (**tp != '\0') { + tp++; + ntok++; + } + *tp = NULL; + + if (tok[0] == NULL) + continue; + + if (cb(tok, ntok, arg0, arg1)) + break; + } + + return (0); +} + +struct asr_query * +asr_ctx_query(struct asr_ctx *ac, int type) +{ + struct asr_query *aq; + + if ((aq = calloc(1, sizeof(*aq))) == NULL) + return (NULL); + + ac->ac_refcount += 1; + + aq->aq_ctx = ac; + aq->aq_fd = -1; + aq->aq_type = type; + aq->aq_state = ASR_STATE_INIT; + + return (aq); +} + +void +asr_done(struct asr *asr) +{ + if (asr_ctx_unref(asr->a_ctx)) + return; + if (asr->a_path) + free(asr->a_path); + free(asr); +} + +int +asr_parse_hosts_cb(char **tok, int n, void *a0, void *a1) +{ + struct asr_query *aq = (struct asr_query*) a0; + struct asr_result *ar = (struct asr_result*) a1; + int i; + + for (i = 1; i < n; i++) { + if (strcmp(tok[i], aq->aq_host)) + continue; + if (sockaddr_from_str(&ar->ar_sa.sa, aq->aq_family, tok[0]) == -1) + continue; + ar->ar_cname = strdup(tok[1]); + return (1); + } + + return (0); +} + +/* + * utility functions + */ + +int +asr_parse_nameserver(struct sockaddr *sa, const char *s) +{ + const char *estr; + char buf[256]; + char *port = NULL; + in_port_t portno = 53; + + if (*s == '[') { + strlcpy(buf, s + 1, sizeof buf); + s = buf; + port = strchr(buf, ']'); + if (port == NULL) + return (-1); + *port++ = '\0'; + if (*port != ':') + return (-1); + port++; + } + + if (port) { + portno = strtonum(port, 1, USHRT_MAX, &estr); + if (estr) + return (-1); + } + + if (sockaddr_from_str(sa, PF_UNSPEC, s) == -1) + return (-1); + + sockaddr_set_port(sa, portno); + + return (0); +} + +int +asr_db_add_nameserver(struct asr_db *ad, const char *nameserver) +{ + struct sockaddr_storage ss; + + if (ad->ad_type != ASR_DB_DNS) + return (-1); + + if (ad->ad_count == ASR_MAXNS) + return (-1); + + if (asr_parse_nameserver((struct sockaddr*)&ss, nameserver)) + return (-1); + + if ((ad->ad_sa[ad->ad_count] = calloc(1, ss.ss_len)) == NULL) + return (0); + + memmove(ad->ad_sa[ad->ad_count], &ss, ss.ss_len); + ad->ad_count += 1; + + return (1); +} + +void +asr_db_done(struct asr_db *ad) +{ + int i; + + switch(ad->ad_type) { + case ASR_DB_DNS: + for(i = 0; i < ad->ad_count; i++) + free(ad->ad_sa[i]); + break; + + case ASR_DB_YP: + break; + + case ASR_DB_FILE: + free(ad->ad_path); + break; + default: + errx(1, "asr_db_done: unknown db type"); + } +} + +int +asr_parse_namedb_line(FILE *file, char **tokens, int ntoken) +{ + size_t len; + char *buf, *cp, **tp; + int ntok; + + again: + if ((buf = fgetln(file, &len)) == NULL) + return (-1); + + if (buf[len - 1] == '\n') + len--; + + buf[len] = '\0'; + buf[strcspn(buf, "#")] = '\0'; + for(cp = buf, tp = tokens, ntok = 0; + ntok < ntoken && (*tp = strsep(&cp, " \t")) != NULL;) + if (**tp != '\0') { + tp++; + ntok++; + } + *tp = NULL; + if (tokens[0] == NULL) + goto again; + + return (ntok); +} + +const char * +asr_error(int v) +{ + switch(v) { + case ASR_OK: + return "no error"; + case EASR_MEMORY: + return "out of memory"; + case EASR_TIMEDOUT: + return "all nameservers timed out"; + case EASR_NAMESERVER: + return "no nameserver specified"; + case EASR_FAMILY: + return "invalid address family"; + case EASR_NOTFOUND: + return "not found"; + case EASR_NAME: + return "invalid domain name"; + default: + return "unknown error code"; + } +} + +int +asr_cmp_fqdn_name(const char *fqdn, char *name) +{ + int i; + + /* compare a fqdn with a name that may not end with a dot */ + + for (i = 0; fqdn[i] && name[i]; i++) + if (fqdn[i] != name[i]) + return (-1); + + if (fqdn[i] == name[i]) + return (0); + + if (fqdn[i] == 0 || fqdn[i] != '.' || fqdn[i+1] != 0) + return (-1); + + return (0); +} + +int +asr_ndots(const char *s) +{ + int n; + + for(n = 0; *s; s++) + if (*s == '.') + n += 1; + + return (n); +} + +int +asr_is_fqdn(const char *name) +{ + size_t len; + + len = strlen(name); + return (len > 0 && name[len -1] == '.'); +} + +char * +asr_make_fqdn(const char *name, const char *domain) +{ + char *fqdn; + size_t len; + + if (domain == NULL) + domain = "."; +#ifdef ASR_DEBUG + else + if (!asr_is_fqdn(domain)) + errx(1, "domain is not FQDN: %s", domain); +#endif + + len = strlen(name); + if (len == 0) { + fqdn = strdup(domain); + } else if (name[len - 1] != '.') { + if (domain[0] == '.') + domain += 1; + len += strlen(domain) + 2; + fqdn = malloc(len); + if (fqdn == NULL) + return (NULL); + strlcpy(fqdn, name, len); + strlcat(fqdn, ".", len); + strlcat(fqdn, domain, len); + } else { + fqdn = strdup(name); + } + + return (fqdn); +} + +void +asr_query_free(struct asr_query *aq) +{ + if (aq->aq_aifirst) + freeaddrinfo(aq->aq_aifirst); + if (aq->aq_subq) + asr_abort(aq->aq_subq); + if (aq->aq_host) + free(aq->aq_host); + if (aq->aq_fqdn) + free(aq->aq_fqdn); + if (aq->aq_buf) + free(aq->aq_buf); + if (aq->aq_hostname) + free(aq->aq_hostname); + if (aq->aq_servname) + free(aq->aq_servname); + if (aq->aq_fd != -1) + close(aq->aq_fd); + asr_ctx_unref(aq->aq_ctx); + free(aq); +} + +/* + * for asr_query_dns + */ + +struct asr_query * +asr_query_dns(struct asr *asr, + uint16_t type, + uint16_t class, + const char *name, + int flags) +{ + struct asr_query *aq; + + asr_check_reload(asr); + + if ((aq = asr_ctx_query(asr->a_ctx, ASR_QUERY_DNS)) == NULL) + return (NULL); + + aq->aq_flags = flags; + aq->aq_query.q_type = type; + aq->aq_query.q_class = class; + aq->aq_fqdn = asr_make_fqdn(name, NULL); + if (aq->aq_fqdn == NULL) + goto abort; + + return (aq); + abort: + asr_query_free(aq); + return (NULL); +} + +int +asr_setup_packet(struct asr_query *aq) +{ + struct packed p; + struct header h; + + if (dname_from_fqdn(aq->aq_fqdn, + aq->aq_query.q_dname, + sizeof(aq->aq_query.q_dname)) == -1) { + return (-1); + } + + aq->aq_reqid = res_randomid(); + + memset(&h, 0, sizeof h); + h.id = aq->aq_reqid; + if (!(aq->aq_flags & ASR_NOREC)) + h.flags |= RD_MASK; + h.qdcount = 1; + + if (aq->aq_buf == NULL) { + aq->aq_bufsize = PACKET_MAXLEN; + if ((aq->aq_buf = malloc(aq->aq_bufsize)) == NULL) + return (-2); + } + aq->aq_bufoffset = 0; + + packed_init(&p, aq->aq_buf, aq->aq_bufsize); + pack_header(&p, &h); + pack_query(&p, aq->aq_query.q_type, aq->aq_query.q_class, + aq->aq_query.q_dname); + aq->aq_buflen = p.offset; + + return (0); +} + +int +asr_ensure_buf(struct asr_query *aq, size_t n) +{ + char *t; + + if (aq->aq_buf == NULL) { + aq->aq_buf = malloc(n); + if (aq->aq_buf == NULL) + return (-1); + aq->aq_bufsize = n; + return (0); + } + + if (aq->aq_bufsize > n) + return (0); + + t = realloc(aq->aq_buf, n); + if (t == NULL) + return (-1); + aq->aq_buf = t; + aq->aq_bufsize = n; + + return (0); +} + +int +asr_validate_packet(struct asr_query *aq) +{ + struct packed p; + struct header h; + struct query q; + struct rr rr; + int r; + + packed_init(&p, aq->aq_buf, aq->aq_buflen); + + unpack_header(&p, &h); + if (p.err) + return (-1); + if (h.id != aq->aq_reqid) + return (-1); + if (h.qdcount != 1) + return (-1); + if ((h.flags & Z_MASK) != 0) + return (-1); /* should be zero, we could allow this */ + if (h.flags & TC_MASK) + return (-2); + if (OPCODE(h.flags) != OP_QUERY) + return (-1); /* actually, it depends on the request */ + if ((h.flags & QR_MASK) == 0) + return (-1); /* not a response */ + + unpack_query(&p, &q); + if (p.err) + return (-1); + if (q.q_type != aq->aq_query.q_type || + q.q_class != aq->aq_query.q_class || + strcasecmp(q.q_dname, aq->aq_query.q_dname)) + return (-1); + + /* validate the rest of the packet */ + for(r = h.ancount + h.nscount + h.arcount; r; r--) + unpack_rr(&p, &rr); + + if (p.err || (p.offset != aq->aq_buflen)) + return (-1); + + return (0); +} + +int +asr_udp_send(struct asr_query *aq) +{ + ssize_t n; + + aq->aq_fd = sockaddr_connect(AQ_NS_SA(aq), SOCK_DGRAM); + if (aq->aq_fd == -1) + return (-1); + + aq->aq_timeout = AQ_DB(aq)->ad_timeout; + + n = send(aq->aq_fd, aq->aq_buf, aq->aq_buflen, 0); + if (n == -1) { + if (errno == EAGAIN) + return (-2); /* timeout */ + return (-1); + } + + return (0); +} + +int +asr_udp_recv(struct asr_query *aq) +{ + ssize_t n; + + n = recv(aq->aq_fd, aq->aq_buf, aq->aq_bufsize, 0); + if (n == -1) { + if (errno == EAGAIN) + return (-2); /* timeout */ + return (-1); + } + + aq->aq_buflen = n; + + switch (asr_validate_packet(aq)) { + case -2: + return (1); /* truncated */ + case -1: + return (-1); + default: + break; + } + + return (0); +} + +int +asr_tcp_write(struct asr_query *aq) +{ + struct iovec iov[2]; + uint16_t len; + ssize_t n; + int i; + socklen_t sl; + int se; + + if (aq->aq_fd == -1) { /* connect */ + aq->aq_fd = sockaddr_connect(AQ_NS_SA(aq), SOCK_STREAM); + if (aq->aq_fd == -1) + return (-1); + aq->aq_timeout = AQ_DB(aq)->ad_timeout; + return (1); + } + + i = 0; + if (aq->aq_datalen == 0) { + /* check connection first */ + sl = sizeof(se); + if (getsockopt(aq->aq_fd, SOL_SOCKET, SO_ERROR, &se, &sl) == -1) { + warn("getsockopt"); + return (-1); + } + if (se) + return -1; + + /* need to send datalen first */ + len = htons(aq->aq_buflen); + iov[i].iov_base = &len; + iov[i].iov_len = sizeof(len); + i++; + } + + iov[i].iov_base = AQ_BUF_DATA(aq); + iov[i].iov_len = AQ_BUF_LEN(aq); + i++; + + n = writev(aq->aq_fd, iov, i); + if (n == -1) { + if (errno == EAGAIN) + return (-2); /* timeout */ + warn("writev"); + return (-1); + } + + if (aq->aq_datalen == 0 && n < 2) { + /* we want to write the data len */ + warnx("short write"); + return (-1); + } + + if (aq->aq_datalen == 0) { + aq->aq_datalen = len; + n -= 2; + } + + aq->aq_bufoffset += n; + if (aq->aq_bufoffset == aq->aq_buflen) { + aq->aq_datalen = 0; + return (0); /* all sent */ + } + + aq->aq_timeout = AQ_DB(aq)->ad_timeout; + return (1); +} + +int +asr_tcp_read(struct asr_query *aq) +{ + uint16_t len; + ssize_t n; + + if (aq->aq_datalen == 0) { + n = read(aq->aq_fd, &len, sizeof(len)); + if (n == -1) { + if (errno == EAGAIN) /* timeout */ + return (-2); + return (-1); + } + if (n < 2) { + warnx("short read"); + return (-1); + } + aq->aq_datalen = ntohs(len); + aq->aq_bufoffset = 0; + aq->aq_buflen = 0; + + if (asr_ensure_buf(aq, aq->aq_datalen) == -1) + return (-3); /* ENOMEM */ + + return (1); /* need more data */ + } + + n = read(aq->aq_fd, AQ_BUF_WPOS(aq), AQ_BUF_LEFT(aq)); + if (n == -1) { + if (errno == EAGAIN) /* timeout */ + return (-2); + warn("read"); + return (-1); + } + if (n == 0) { + warnx("closed"); + return (-1); + } + aq->aq_buflen += n; + + if (aq->aq_buflen != aq->aq_datalen) + return (1); /* need more data */ + + if (asr_validate_packet(aq) != 0) + return (-1); + + return (0); +} + +int +asr_run_dns(struct asr_query *aq, struct asr_result *ar) +{ + for(;;) { /* block not indented on purpose */ +#ifdef ASR_DEBUG + if (asr_debug) { + printf(" "); + asr_dump_query(aq); + } +#endif + switch(aq->aq_state) { + + case ASR_STATE_INIT: + aq->aq_ns_cycles = -1; + aq->aq_db_idx = 0; + aq->aq_state = ASR_STATE_QUERY_DB; + break; + + case ASR_STATE_NEXT_DB: + aq->aq_db_idx += 1; + aq->aq_state = ASR_STATE_QUERY_DB; + break; + + case ASR_STATE_QUERY_DB: + if (aq->aq_db_idx >= aq->aq_ctx->ac_dbcount) { + if (aq->aq_ns_cycles == -1) + ar->ar_err = EASR_NAMESERVER; + else + ar->ar_err = EASR_TIMEDOUT; + aq->aq_state = ASR_STATE_HALT; + break; + } + + if (AQ_DB(aq)->ad_type != ASR_DB_DNS) { + aq->aq_state = ASR_STATE_NEXT_DB; + break; + } + aq->aq_ns_cycles = 0; + aq->aq_ns_idx = 0; + aq->aq_state = ASR_STATE_QUERY_NS; + break; + + case ASR_STATE_NEXT_NS: + /* close the current fd if any */ + if (aq->aq_fd != -1) { + close(aq->aq_fd); + aq->aq_fd = -1; + } + + aq->aq_ns_idx += 1; + if (aq->aq_ns_idx >= AQ_DB(aq)->ad_count) { + aq->aq_ns_idx = 0; + aq->aq_ns_cycles++; + } + if (aq->aq_ns_cycles >= AQ_DB(aq)->ad_retries) { + aq->aq_state = ASR_STATE_NEXT_DB; + break; + } + aq->aq_state = ASR_STATE_QUERY_NS; + break; + + case ASR_STATE_QUERY_NS: + if (aq->aq_ns_idx >= AQ_DB(aq)->ad_count) { + aq->aq_state = ASR_STATE_NEXT_NS; + break; + } + switch (asr_setup_packet(aq)) { + case -2: + ar->ar_err = EASR_MEMORY; + aq->aq_state = ASR_STATE_HALT; + break; + case -1: + ar->ar_err = EASR_NAME; + aq->aq_state = ASR_STATE_HALT; + break; + default: + break; + } + if (aq->aq_ctx->ac_forcetcp) + aq->aq_state = ASR_STATE_TCP_WRITE; + else + aq->aq_state = ASR_STATE_UDP_SEND; + break; + + case ASR_STATE_UDP_SEND: + if (asr_udp_send(aq) == 0) { + aq->aq_state = ASR_STATE_UDP_RECV; + ar->ar_fd = aq->aq_fd; + ar->ar_timeout = aq->aq_timeout; + return (ASR_NEED_READ); + } + aq->aq_state = ASR_STATE_NEXT_NS; + break; + + case ASR_STATE_UDP_RECV: + switch (asr_udp_recv(aq)) { + case -2: /* timeout */ + case -1: /* fail */ + aq->aq_state = ASR_STATE_NEXT_NS; + break; + case 0: /* done */ + aq->aq_state = ASR_STATE_PACKET; + break; + case 1: /* truncated */ + close(aq->aq_fd); + aq->aq_fd = -1; + aq->aq_state = ASR_STATE_TCP_WRITE; + break; + } + break; + + case ASR_STATE_TCP_WRITE: + switch (asr_tcp_write(aq)) { + case -2: /* timeout */ + case -1: /* fail */ + aq->aq_state = ASR_STATE_NEXT_NS; + break; + case 0: + aq->aq_state = ASR_STATE_TCP_READ; + ar->ar_fd = aq->aq_fd; + ar->ar_timeout = aq->aq_timeout; + return (ASR_NEED_READ); + case 1: + ar->ar_fd = aq->aq_fd; + ar->ar_timeout = aq->aq_timeout; + return (ASR_NEED_WRITE); + } + break; + + case ASR_STATE_TCP_READ: + switch (asr_tcp_read(aq)) { + case -3: + aq->aq_state = ASR_STATE_HALT; + ar->ar_err = EASR_MEMORY; + break; + case -2: /* timeout */ + case -1: /* fail */ + aq->aq_state = ASR_STATE_NEXT_NS; + break; + case 0: + aq->aq_state = ASR_STATE_PACKET; + break; + case 1: + ar->ar_fd = aq->aq_fd; + ar->ar_timeout = aq->aq_timeout; + return (ASR_NEED_READ); + } + break; + + case ASR_STATE_PACKET: + memmove(&ar->ar_sa.sa, AQ_NS_SA(aq), AQ_NS_SA(aq)->sa_len); + ar->ar_datalen = aq->aq_buflen; + ar->ar_data = aq->aq_buf; + aq->aq_buf = NULL; + ar->ar_err = ASR_OK; + aq->aq_state = ASR_STATE_HALT; + break; + + case ASR_STATE_HALT: + ar->ar_errstr = asr_error(ar->ar_err); + if (ar->ar_err) + ar->ar_data = NULL; + return (ASR_DONE); + + default: + errx(1, "asr_run_dns: unknown state"); + }} +} + +/* + * for asr_query_host + */ + +struct asr_query * +asr_query_host(struct asr *asr, const char *host, int family) +{ + asr_check_reload(asr); + + return asr_ctx_query_host(asr->a_ctx, host, family); +} + +struct asr_query * +asr_ctx_query_host(struct asr_ctx *ac, const char *host, int family) +{ + struct asr_query *aq; + + if ((aq = asr_ctx_query(ac, ASR_QUERY_HOST)) == NULL) + return (NULL); + + aq->aq_family = family; + aq->aq_host = strdup(host); + if (aq->aq_host) + return (aq); + + asr_query_free(aq); + return (NULL); +} + +int +asr_run_host(struct asr_query *aq, struct asr_result *ar) +{ + struct header h; + struct query q; + struct rr rr; + char *tok[10]; + int ntok = 10, i, n, family; + + for(;;) { /* block not indented on purpose */ +#ifdef ASR_DEBUG + if (asr_debug) { + printf(" "); + asr_dump_query(aq); + } +#endif + switch(aq->aq_state) { + + case ASR_STATE_INIT: + if (aq->aq_family != AF_INET && + aq->aq_family != AF_INET6 && + aq->aq_family != AF_UNSPEC) { + ar->ar_err = EASR_FAMILY; + aq->aq_state = ASR_STATE_HALT; + break; + } + aq->aq_count = 0; + aq->aq_dom_idx = 0; + /* check if we need to try it as an absolute name first */ + if (asr_ndots(aq->aq_host) >= aq->aq_ctx->ac_ndots) + aq->aq_dom_idx = -1; + aq->aq_state = ASR_STATE_SEARCH_DOMAIN; + break; + + case ASR_STATE_NEXT_DOMAIN: + /* no domain search for fully qualified names */ + if (asr_is_fqdn(aq->aq_host)) { + ar->ar_err = EASR_NOTFOUND; + aq->aq_state = ASR_STATE_HALT; + break; + } + aq->aq_dom_idx += 1; + aq->aq_state = ASR_STATE_SEARCH_DOMAIN; + break; + + case ASR_STATE_SEARCH_DOMAIN: + if (aq->aq_dom_idx >= aq->aq_ctx->ac_domcount) { + ar->ar_err = EASR_NOTFOUND; + aq->aq_state = ASR_STATE_HALT; + break; + } + if (aq->aq_fqdn) + free(aq->aq_fqdn); + + if (aq->aq_dom_idx == -1) /* try as absolute first */ + aq->aq_fqdn = asr_make_fqdn(aq->aq_host, NULL); + else + aq->aq_fqdn = asr_make_fqdn(aq->aq_host, + aq->aq_ctx->ac_dom[aq->aq_dom_idx]); + + if (aq->aq_fqdn == NULL) { + ar->ar_err = EASR_MEMORY; + aq->aq_state = ASR_STATE_HALT; + break; + } + aq->aq_db_idx = 0; + aq->aq_family_idx = 0; + aq->aq_state = ASR_STATE_LOOKUP_FAMILY; + break; + + case ASR_STATE_NEXT_FAMILY: + aq->aq_family_idx += 1; + if ((aq->aq_family != AF_UNSPEC) || (AQ_FAMILY(aq) == -1)) { + /* The family was specified, or we have + * tried all families with this DB + */ + if (aq->aq_count) { + ar->ar_count = aq->aq_count; + ar->ar_err = ASR_OK; + aq->aq_state = ASR_STATE_HALT; + } else + aq->aq_state = ASR_STATE_NEXT_DB; + break; + } + aq->aq_state = ASR_STATE_LOOKUP_FAMILY; + break; + + case ASR_STATE_LOOKUP_FAMILY: + aq->aq_state = ASR_STATE_QUERY_DB; + break; + + case ASR_STATE_NEXT_DB: + aq->aq_db_idx += 1; + aq->aq_family_idx = 0; + aq->aq_state = ASR_STATE_QUERY_DB; + break; + + case ASR_STATE_QUERY_DB: + if (aq->aq_db_idx >= aq->aq_ctx->ac_dbcount) { + aq->aq_state = ASR_STATE_NEXT_DOMAIN; + break; + } + + switch(AQ_DB(aq)->ad_type) { + case ASR_DB_DNS: + family = aq->aq_family; + if (family == AF_UNSPEC) + family = AQ_FAMILY(aq); + if (family == AF_INET) + aq->aq_query.q_type = T_A; + else if (family == AF_INET6) + aq->aq_query.q_type = T_AAAA; + else + errx(1, "bad family: %i", family); + aq->aq_query.q_class = C_IN; + aq->aq_flags = 0; + aq->aq_ns_cycles = 0; + aq->aq_ns_idx = 0; + aq->aq_state = ASR_STATE_QUERY_NS; + break; + case ASR_DB_FILE: + aq->aq_state = ASR_STATE_QUERY_FILE; + break; + default: + aq->aq_state = ASR_STATE_NEXT_DB; + } + break; + + case ASR_STATE_NEXT_NS: + /* close the current fd if any */ + if (aq->aq_fd != -1) { + close(aq->aq_fd); + aq->aq_fd = -1; + } + + aq->aq_ns_idx += 1; + if (aq->aq_ns_idx >= AQ_DB(aq)->ad_count) { + aq->aq_ns_idx = 0; + aq->aq_ns_cycles++; + } + if (aq->aq_ns_cycles >= AQ_DB(aq)->ad_retries) { + aq->aq_state = ASR_STATE_NEXT_DB; + break; + } + aq->aq_state = ASR_STATE_QUERY_NS; + break; + + case ASR_STATE_QUERY_NS: + if (aq->aq_ns_idx >= AQ_DB(aq)->ad_count) { + aq->aq_state = ASR_STATE_NEXT_NS; + break; + } + switch (asr_setup_packet(aq)) { + case -2: + ar->ar_err = EASR_MEMORY; + aq->aq_state = ASR_STATE_HALT; + break; + case -1: + ar->ar_err = EASR_NAME; + aq->aq_state = ASR_STATE_HALT; + break; + default: + break; + } + if (aq->aq_ctx->ac_forcetcp) + aq->aq_state = ASR_STATE_TCP_WRITE; + else + aq->aq_state = ASR_STATE_UDP_SEND; + break; + + case ASR_STATE_UDP_SEND: + if (asr_udp_send(aq) == 0) { + aq->aq_state = ASR_STATE_UDP_RECV; + ar->ar_fd = aq->aq_fd; + ar->ar_timeout = aq->aq_timeout; + return (ASR_NEED_READ); + } + aq->aq_state = ASR_STATE_NEXT_NS; + break; + + case ASR_STATE_UDP_RECV: + switch (asr_udp_recv(aq)) { + case -2: /* timeout */ + case -1: /* fail */ + aq->aq_state = ASR_STATE_NEXT_NS; + break; + case 0: /* done */ + aq->aq_state = ASR_STATE_PACKET; + break; + case 1: /* truncated */ + close(aq->aq_fd); + aq->aq_fd = -1; + aq->aq_state = ASR_STATE_TCP_WRITE; + break; + } + break; + + case ASR_STATE_TCP_WRITE: + switch (asr_tcp_write(aq)) { + case -2: /* timeout */ + case -1: /* fail */ + aq->aq_state = ASR_STATE_NEXT_NS; + break; + case 0: + aq->aq_state = ASR_STATE_TCP_READ; + ar->ar_fd = aq->aq_fd; + ar->ar_timeout = aq->aq_timeout; + return (ASR_NEED_READ); + case 1: + ar->ar_fd = aq->aq_fd; + ar->ar_timeout = aq->aq_timeout; + return (ASR_NEED_WRITE); + } + break; + + case ASR_STATE_TCP_READ: + switch (asr_tcp_read(aq)) { + case -3: + aq->aq_state = ASR_STATE_HALT; + ar->ar_err = EASR_MEMORY; + break; + case -2: /* timeout */ + case -1: /* fail */ + aq->aq_state = ASR_STATE_NEXT_NS; + break; + case 0: + aq->aq_state = ASR_STATE_PACKET; + break; + case 1: + ar->ar_fd = aq->aq_fd; + ar->ar_timeout = aq->aq_timeout; + return (ASR_NEED_READ); + } + break; + + case ASR_STATE_PACKET: + packed_init(&aq->aq_packed, aq->aq_buf, aq->aq_buflen); + unpack_header(&aq->aq_packed, &h); + aq->aq_nanswer = h.ancount; + for(; h.qdcount; h.qdcount--) + unpack_query(&aq->aq_packed, &q); + aq->aq_state = ASR_STATE_READ_RR; + break; + + case ASR_STATE_READ_RR: + if (aq->aq_nanswer == 0) { + free(aq->aq_buf); + aq->aq_buf = NULL; + /* done with this NS, try with next family */ + aq->aq_state = ASR_STATE_NEXT_FAMILY; + break; + } + aq->aq_nanswer -= 1; + unpack_rr(&aq->aq_packed, &rr); + if (rr.rr_type == aq->aq_query.q_type && + rr.rr_class == aq->aq_query.q_class) { + aq->aq_count += 1; + ar->ar_count = aq->aq_count; + sockaddr_from_rr(&ar->ar_sa.sa, &rr); + ar->ar_cname = NULL; /* XXX */ + return (ASR_YIELD); + } + break; + + case ASR_STATE_QUERY_FILE: + aq->aq_file = fopen(AQ_DB(aq)->ad_path, "r"); + if (aq->aq_file == NULL) + aq->aq_state = ASR_STATE_NEXT_DB; + else + aq->aq_state = ASR_STATE_READ_FILE; + break; + + case ASR_STATE_READ_FILE: + n = asr_parse_namedb_line(aq->aq_file, tok, ntok); + if (n == -1) { + fclose(aq->aq_file); + aq->aq_file = NULL; + /* XXX as an optimization, the file could be parsed only once */ + aq->aq_state = ASR_STATE_NEXT_FAMILY; + break; + } + + for (i = 1; i < n; i++) { + /* for the first round, try the host as-is */ + /* XXX not nice */ + if (aq->aq_dom_idx <= 0 && !strcmp(aq->aq_host, tok[i])) { + } else if (asr_cmp_fqdn_name(aq->aq_fqdn, tok[i]) == -1) + continue; + family = aq->aq_family; + if (family == AF_UNSPEC) + family = AQ_FAMILY(aq); + if (sockaddr_from_str(&ar->ar_sa.sa, family, tok[0]) == -1) + continue; + + aq->aq_count += 1; + ar->ar_count = aq->aq_count; + ar->ar_cname = strdup(tok[1]); + return (ASR_YIELD); + } + break; + + case ASR_STATE_HALT: + ar->ar_count = aq->aq_count; + ar->ar_errstr = asr_error(ar->ar_err); + return (ASR_DONE); + + default: + errx(1, "asr_run_host: unknown state"); + }} +} + + + +/* + * for asr_query_addrinfo + */ + +struct asr_query * +asr_query_addrinfo(struct asr *asr, + const char *hostname, + const char *servname, + const struct addrinfo *hints) +{ + struct asr_query *aq; + + asr_check_reload(asr); + + if ((aq = asr_ctx_query(asr->a_ctx, ASR_QUERY_ADDRINFO)) == NULL) + return (NULL); + + if (hostname && (aq->aq_hostname = strdup(hostname)) == NULL) + goto abort; + if (servname && (aq->aq_servname = strdup(servname)) == NULL) + goto abort; + if (hints) + memmove(&aq->aq_hints, hints, sizeof *hints); + else { + memset(&aq->aq_hints, 0, sizeof aq->aq_hints); + aq->aq_hints.ai_family = PF_UNSPEC; + } + + return (aq); + abort: + asr_query_free(aq); + return (NULL); +} + +int +asr_get_port(const char *servname, const char *proto, int numonly) +{ + struct servent se; + struct servent_data sed; + int port, r; + const char* e; + + if (servname == NULL) + return (0); + + e = NULL; + port = strtonum(servname, 0, USHRT_MAX, &e); + if (e == NULL) + return htons(port); + if (errno == ERANGE) + return (-3); /* invalid */ + if (numonly) + return (-3); + + memset(&sed, 0, sizeof(sed)); + r = getservbyname_r(servname, proto, &se, &sed); + port = se.s_port; + endservent_r(&sed); + + if (r == -1) + return (-2); /* not found */ + + return (port); +} + +int +asr_add_sockaddr2(struct asr_query *aq, + struct sockaddr *sa, + int socktype, + int protocol) +{ + struct addrinfo *ai; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + const char *proto; + int port; + + switch (protocol) { + case IPPROTO_TCP: + proto = "tcp"; + break; + case IPPROTO_UDP: + proto = "udp"; + break; + default: + proto = NULL; + } + + port = -1; + if (proto) { + port = asr_get_port(aq->aq_servname, proto, + aq->aq_hints.ai_flags & AI_NUMERICSERV); + if (port < 0) + return (port); + } + + ai = calloc(1, sizeof *ai + sa->sa_len); + if (ai == NULL) + return (-1); /* no mem */ + ai->ai_family = sa->sa_family; + ai->ai_socktype = socktype; + ai->ai_protocol = protocol; + ai->ai_addrlen = sa->sa_len; + ai->ai_addr = (void*)(ai + 1); + memmove(ai->ai_addr, sa, sa->sa_len); + + if (port != -1) { + switch(ai->ai_family) { + case PF_INET: + sin = (struct sockaddr_in*)ai->ai_addr; + sin->sin_port = port; + break; + case PF_INET6: + sin6 = (struct sockaddr_in6*)ai->ai_addr; + sin6->sin6_port = port; + break; + } + } + + if (aq->aq_aifirst == NULL) + aq->aq_aifirst = ai; + if (aq->aq_ailast) + aq->aq_ailast->ai_next = ai; + aq->aq_ailast = ai; + + aq->aq_count += 1; + + return (0); +} + +struct match { + int family; + int socktype; + int protocol; +}; + +static const struct match matches[] = { + { PF_INET, SOCK_DGRAM, IPPROTO_UDP }, + { PF_INET, SOCK_STREAM, IPPROTO_TCP }, + { PF_INET, SOCK_RAW, 0 }, + { PF_INET6, SOCK_DGRAM, IPPROTO_UDP }, + { PF_INET6, SOCK_STREAM, IPPROTO_TCP }, + { PF_INET6, SOCK_RAW, 0 }, + { -1, 0, 0, }, +}; + +#define MATCH_FAMILY(a, b) ((a) == matches[(b)].family || (a) == PF_UNSPEC) +#define MATCH_PROTO(a, b) ((a) == matches[(b)].protocol || (a) == 0) +/* do not match SOCK_RAW unless explicitely specified */ +#define MATCH_SOCKTYPE(a, b) ((a) == matches[(b)].socktype || ((a) == 0 && \ + matches[(b)].socktype != SOCK_RAW)) + +int +asr_add_sockaddr(struct asr_query *aq, struct sockaddr *sa) +{ + int i, e; + + for(i = 0; matches[i].family != -1; i++) { + if (matches[i].family != sa->sa_family || + !MATCH_SOCKTYPE(aq->aq_hints.ai_socktype, i) || + !MATCH_PROTO(aq->aq_hints.ai_protocol, i)) + continue; + e = asr_add_sockaddr2(aq, sa, matches[i].socktype, matches[i].protocol); + switch(e) { + case -3: + return (EAI_NONAME); + case -2: + /* Only report bad service if the protocol was specified */ + if (aq->aq_hints.ai_protocol == 0) + break; + return (EAI_SERVICE); + case -1: + return (EAI_MEMORY); + } + } + + return (0); +} + +int +asr_iter_family(struct asr_query *aq, int first) +{ + if (first) { + aq->aq_family_idx = 0; + if (aq->aq_hints.ai_family != PF_UNSPEC) + return aq->aq_hints.ai_family; + return AQ_FAMILY(aq); + } + + if (aq->aq_hints.ai_family != PF_UNSPEC) + return (-1); + + aq->aq_family_idx++; + + return AQ_FAMILY(aq); +} + +int +asr_run_addrinfo(struct asr_query *aq, struct asr_result *ar) +{ + const char *str; + struct addrinfo *ai; + int i, family, r; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } sa; + + for(;;) { /* block not indented on purpose */ +#ifdef ASR_DEBUG + if (asr_debug) { + printf(" "); + asr_dump_query(aq); + } +#endif + switch(aq->aq_state) { + + case ASR_STATE_INIT: + aq->aq_count = 0; + aq->aq_state = ASR_STATE_HALT; + ar->ar_err = 0; + + if (aq->aq_hostname == NULL && + aq->aq_servname == NULL) { + ar->ar_err = EAI_BADHINTS; + break; + } + + ai = &aq->aq_hints; + + if (ai->ai_addrlen || + ai->ai_canonname || + ai->ai_addr || + ai->ai_next) { + ar->ar_err = EAI_BADHINTS; + break; + } + + if (ai->ai_flags & ~AI_MASK) { + ar->ar_err = EAI_BADHINTS; + break; + } + + if (ai->ai_family != PF_UNSPEC && + ai->ai_family != PF_INET && + ai->ai_family != PF_INET6) { + ar->ar_err = EAI_FAMILY; + break; + } + + if (ai->ai_socktype && + ai->ai_socktype != SOCK_DGRAM && + ai->ai_socktype != SOCK_STREAM && + ai->ai_socktype != SOCK_RAW) { + ar->ar_err = EAI_SOCKTYPE; + break; + } + + if (ai->ai_protocol && + ai->ai_protocol != IPPROTO_UDP && + ai->ai_protocol != IPPROTO_TCP) { + ar->ar_err = EAI_PROTOCOL; + break; + } + + if (ai->ai_socktype == SOCK_RAW && + aq->aq_servname != NULL) { + ar->ar_err = EAI_SERVICE; + break; + } + + /* make sure there is at least a valid combination */ + for (i = 0; matches[i].family != -1; i++) + if (MATCH_FAMILY(ai->ai_family, i) && + MATCH_SOCKTYPE(ai->ai_socktype, i) && + MATCH_PROTO(ai->ai_protocol, i)) + break; + if (matches[i].family == -1) { + ar->ar_err = EAI_BADHINTS; + break; + } + + if (aq->aq_hostname == NULL) { + for(family = asr_iter_family(aq, 1); + family != -1; + family = asr_iter_family(aq, 0)) { + if (family == PF_INET) + str = (ai->ai_flags & AI_PASSIVE) ? \ + "0.0.0.0" : "127.0.0.1"; + else /* PF_INET6 */ + str = (ai->ai_flags & AI_PASSIVE) ? \ + "::" : "::1"; + /* can't fail */ + sockaddr_from_str(&sa.sa, family, str); + if ((r = asr_add_sockaddr(aq, &sa.sa))) { + ar->ar_err = r; + aq->aq_state = ASR_STATE_HALT; + break; + } + } + if (ar->ar_err == 0 && aq->aq_count == 0) + ar->ar_err = EAI_NODATA; + break; + } + + /* try numeric addresses */ + for(family = asr_iter_family(aq, 1); + family != -1; + family = asr_iter_family(aq, 0)) { + + if (sockaddr_from_str(&sa.sa, family, + aq->aq_hostname) == -1) + continue; + + if ((r = asr_add_sockaddr(aq, &sa.sa))) { + ar->ar_err = r; + aq->aq_state = ASR_STATE_HALT; + break; + } + + aq->aq_state = ASR_STATE_HALT; + break; + } + if (ar->ar_err || aq->aq_count) + break; + + if (ai->ai_flags & AI_NUMERICHOST) { + ar->ar_err = EAI_FAIL; + aq->aq_state = ASR_STATE_HALT; + break; + } + + /* subquery for hostname */ + if ((aq->aq_subq = asr_ctx_query_host(aq->aq_ctx, + aq->aq_hostname, + ai->ai_family)) == NULL) { + ar->ar_err = EAI_MEMORY; + aq->aq_state = ASR_STATE_HALT; + } + + aq->aq_state = ASR_STATE_SUBQUERY; + break; + + case ASR_STATE_SUBQUERY: + switch ((r = asr_run(aq->aq_subq, ar))) { + case ASR_NEED_READ: + case ASR_NEED_WRITE: + return (r); + case ASR_YIELD: + if ((r = asr_add_sockaddr(aq, &ar->ar_sa.sa))) { + ar->ar_err = r; + aq->aq_state = ASR_STATE_HALT; + } + free(ar->ar_cname); + break; + case ASR_DONE: + aq->aq_subq = NULL; + if (ar->ar_count == 0) + ar->ar_err = EAI_NODATA; + else if (aq->aq_count == 0) + ar->ar_err = EAI_NONAME; + else + ar->ar_err = 0; + aq->aq_state = ASR_STATE_HALT; + break; + } + break; + + case ASR_STATE_HALT: + if (ar->ar_err == 0) { + ar->ar_errstr = NULL; + ar->ar_count = aq->aq_count; + ar->ar_ai = aq->aq_aifirst; + aq->aq_aifirst = NULL; + } else { + ar->ar_ai = NULL; + ar->ar_errstr = gai_strerror(ar->ar_err); + } + return (ASR_DONE); + + default: + errx(1, "asr_run_addrinfo: unknown state"); + }} +} diff --git a/usr.sbin/smtpd/asr.h b/usr.sbin/smtpd/asr.h new file mode 100644 index 00000000000..12adf123b67 --- /dev/null +++ b/usr.sbin/smtpd/asr.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2010 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <netdb.h> +#include <netinet/in.h> + +enum { + ASR_NEED_READ, + ASR_NEED_WRITE, + ASR_YIELD, + ASR_DONE +}; + +#define ASR_NOREC 0x01 + +enum { + ASR_OK = 0, + EASR_MEMORY, + EASR_TIMEDOUT, + EASR_NAMESERVER, + EASR_FAMILY, + EASR_NOTFOUND, + EASR_NAME, + EASR_PARAM +}; + +struct asr_result { + int ar_fd; + int ar_timeout; + int ar_err; + const char *ar_errstr; + int ar_count; + struct addrinfo *ar_ai; + char *ar_cname; + void *ar_data; + size_t ar_datalen; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } ar_sa; +}; + +struct asr_query; + +struct asr *asr_resolver(const char*); +void asr_done(struct asr*); + +int asr_run(struct asr_query*, struct asr_result*); +int asr_run_sync(struct asr_query*, struct asr_result*); +void asr_abort(struct asr_query*); + +struct asr_query *asr_query_dns(struct asr*, + uint16_t, + uint16_t, + const char*, + int); + +struct asr_query *asr_query_host(struct asr*, + const char*, + int); + +struct asr_query *asr_query_addrinfo(struct asr*, + const char*, + const char*, + const struct addrinfo*); diff --git a/usr.sbin/smtpd/dname.c b/usr.sbin/smtpd/dname.c new file mode 100644 index 00000000000..52d52c43aa4 --- /dev/null +++ b/usr.sbin/smtpd/dname.c @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2009,2010 Eric Faurot <eric@faurot.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <ctype.h> +#include <stdio.h> +#include <string.h> + +#include "dnsutil.h" + +size_t +dname_len(const char *dname) +{ + size_t l; + + l = 0; + + while(dname[l]) + l += dname[l] + 1; + + return l; +} + +size_t +dname_depth(const char *dname) +{ + size_t l; + + l = 0; + + while(*dname) { + l += 1; + dname += *dname + 1; + } + + return l; +} + +const char* +dname_up(const char *dname, unsigned int n) +{ + while(n--) { + if (dname[0] == '\0') + return (NULL); + dname += *dname + 1; + } + return (dname); +} + + +int +dname_is_in(const char *dname, const char *domain) +{ + size_t l, ld; + + l = dname_depth(dname); + ld = dname_depth(domain); + + if (ld > l) + return (0); + + dname = dname_up(dname, l - ld); + + if (strcasecmp(dname, domain) == 0) + return (1); + + return (0); +} + +int +dname_is_reverse(const char *dname) +{ + static int init = 0; + static char arpa[15]; + + if (init == 0) { + init = 1; + dname_from_fqdn("in-addr.arpa.", arpa, sizeof arpa); + } + + return (dname_is_in(dname, arpa)); +} + +int +dname_is_wildcard(const char *dname) +{ + return (dname[0] == 1 && dname[1] == '*'); +} + +int +dname_check_label(const char *s, size_t l) +{ + if (l == 0 || l > 63) + return (-1); + + for(l--; l; l--, s++) + if (!(isalnum(*s) || *s == '_' || *s == '-')) + return (-1); + + return (0); +} + +ssize_t +dname_from_fqdn(const char *str, char *dst, size_t max) +{ + ssize_t res; + size_t l, n; + char *d; + + res = 0; + for(;;) { + + d = strchr(str, '.'); + if (d == NULL) + return (-1); + + l = (d - str); + if (l > 63) + return (-1); + if ((res || l) && (dname_check_label(str, l) == -1)) + return (-1); + + res += l + 1; + + if (dst) { + *dst++ = l; + max -= 1; + n = (l > max) ? max : l; + if (n) + memmove(dst, str, n); + max -= n; + if (max == 0) + dst = NULL; + else + dst += n; + } + + str = d + 1; + if (*str == '\0') + break; + } + + return (res); +} + +ssize_t +dname_from_sockaddr(const struct sockaddr *sa, char *dst, size_t max) +{ + const struct in6_addr *in6_addr; + in_addr_t addr; + char buf[80]; + + switch (sa->sa_family) { + case AF_INET: + addr = ((const struct sockaddr_in *)sa)->sin_addr.s_addr; + snprintf(buf, sizeof (buf), + "%d.%d.%d.%d.in-addr.arpa.", + (addr >> 24) & 0xff, + (addr >> 16) & 0xff, + (addr >> 8) & 0xff, + addr & 0xff); + break; + case AF_INET6: + in6_addr = &((const struct sockaddr_in6 *)sa)->sin6_addr; + snprintf(buf, sizeof (buf), + "%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d." + "%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d." + "ip6.arpa.", + in6_addr->s6_addr[15] & 0xf, + (in6_addr->s6_addr[15] >> 4) & 0xf, + in6_addr->s6_addr[14] & 0xf, + (in6_addr->s6_addr[14] >> 4) & 0xf, + in6_addr->s6_addr[13] & 0xf, + (in6_addr->s6_addr[13] >> 4) & 0xf, + in6_addr->s6_addr[12] & 0xf, + (in6_addr->s6_addr[12] >> 4) & 0xf, + in6_addr->s6_addr[11] & 0xf, + (in6_addr->s6_addr[11] >> 4) & 0xf, + in6_addr->s6_addr[10] & 0xf, + (in6_addr->s6_addr[10] >> 4) & 0xf, + in6_addr->s6_addr[9] & 0xf, + (in6_addr->s6_addr[9] >> 4) & 0xf, + in6_addr->s6_addr[8] & 0xf, + (in6_addr->s6_addr[8] >> 4) & 0xf, + in6_addr->s6_addr[7] & 0xf, + (in6_addr->s6_addr[7] >> 4) & 0xf, + in6_addr->s6_addr[6] & 0xf, + (in6_addr->s6_addr[6] >> 4) & 0xf, + in6_addr->s6_addr[5] & 0xf, + (in6_addr->s6_addr[5] >> 4) & 0xf, + in6_addr->s6_addr[4] & 0xf, + (in6_addr->s6_addr[4] >> 4) & 0xf, + in6_addr->s6_addr[3] & 0xf, + (in6_addr->s6_addr[3] >> 4) & 0xf, + in6_addr->s6_addr[2] & 0xf, + (in6_addr->s6_addr[2] >> 4) & 0xf, + in6_addr->s6_addr[1] & 0xf, + (in6_addr->s6_addr[1] >> 4) & 0xf, + in6_addr->s6_addr[0] & 0xf, + (in6_addr->s6_addr[0] >> 4) & 0xf); + break; + default: + return (-1); + } + + return dname_from_fqdn(buf, dst, max); +} diff --git a/usr.sbin/smtpd/dns.c b/usr.sbin/smtpd/dns.c index b9b80398f04..fd3da9b0c29 100644 --- a/usr.sbin/smtpd/dns.c +++ b/usr.sbin/smtpd/dns.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dns.c,v 1.25 2010/11/28 14:02:46 gilles Exp $ */ +/* $OpenBSD: dns.c,v 1.26 2010/11/29 15:25:55 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -21,6 +21,7 @@ #include <sys/socket.h> #include <sys/queue.h> #include <sys/tree.h> +#include <sys/stat.h> #include <netinet/in.h> #include <arpa/inet.h> @@ -35,36 +36,31 @@ #include <string.h> #include <unistd.h> +#include "asr.h" +#include "dnsdefs.h" +#include "dnsutil.h" #include "smtpd.h" #include "log.h" -struct resdata { - struct imsgev iev; - struct imsgev *asker; -}; +void dns_setup(void); +int dns_resolver_updated(void); +struct dnssession *dnssession_init(struct smtpd *, struct dns *); +void dnssession_destroy(struct smtpd *, struct dnssession *); +void dnssession_mx_insert(struct dnssession *, struct mx *); +void dns_asr_handler(int, short, void *); +void dns_asr_mx_handler(int, short, void *); +void lookup_host(struct imsgev *, struct dns *, int, int); +void lookup_mx(struct imsgev *, struct dns *); +void lookup_ptr(struct imsgev *, struct dns *); -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 imsgev *, struct dns *, int, int); -void lookup_mx(struct imsgev *, struct dns *); -int get_mxlist(char *, char *, struct dns **); -void free_mxlist(struct dns *); -int mxcmp(const void *, const void *); -void lookup_ptr(struct imsgev *, struct dns *); +struct asr *asr = NULL; /* * User interface. */ void -dns_query_a(struct smtpd *env, char *host, int port, u_int64_t id) +dns_query_host(struct smtpd *env, char *host, int port, u_int64_t id) { struct dns query; @@ -73,8 +69,8 @@ dns_query_a(struct smtpd *env, char *host, int port, u_int64_t id) query.port = port; query.id = id; - imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_DNS_A, 0, 0, -1, &query, - sizeof(query)); + imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_DNS_HOST, 0, 0, -1, + &query, sizeof(query)); } void @@ -100,443 +96,384 @@ dns_query_ptr(struct smtpd *env, struct sockaddr_storage *ss, u_int64_t id) query.ss = *ss; query.id = id; + if (strlcpy(query.host, ss_to_ptr(ss), sizeof (query.host)) + >= sizeof (query.host)) + fatalx("dns_query_ptr"); + imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_DNS_PTR, 0, 0, -1, &query, sizeof(query)); } -/* - * Parent resolver process interface. - */ - -void -dns_async(struct smtpd *env, struct imsgev *asker, int type, struct dns *query) +/* LKA interface */ +int +dns_resolver_updated(void) { - struct resdata *rd; - int fd; - - if ((rd = calloc(1, sizeof(*rd))) == NULL) - fatal(NULL); + struct stat sb; + static time_t mtime = 0; - rd->asker = asker; - query->env = env; + /* first run, we need a resolver context */ + if (mtime == 0) + return 1; - /* dns() will fail if we are scarce on resources or processes */ - if ((fd = dns()) == -1) { - query->error = EAI_AGAIN; - imsg_compose_event(rd->asker, type, 0, 0, -1, query, sizeof(*query)); - return; + if (stat(_PATH_RESCONF, &sb) < 0) { + log_warnx("dns_resolver_updated: please check %s", + _PATH_RESCONF); + return 0; } - imsg_init(&rd->iev.ibuf, fd); - rd->iev.handler = parent_dispatch_dns; - rd->iev.events = EV_READ; - rd->iev.data = rd; - event_set(&rd->iev.ev, rd->iev.ibuf.fd, rd->iev.events, rd->iev.handler, - rd->iev.data); - event_add(&rd->iev.ev, NULL); + /* no change since last time */ + if (mtime == sb.st_mtime) + return 0; - imsg_compose_event(&rd->iev, type, 0, 0, -1, query, sizeof(*query)); + /* resolv.conf has been updated */ + mtime = sb.st_mtime; + return 1; } void -parent_dispatch_dns(int sig, short event, void *p) +dns_setup(void) { - struct resdata *rd = p; - struct imsgev *iev; - struct imsgbuf *ibuf; - struct imsg imsg; - ssize_t n; - - iev = &rd->iev; - ibuf = &rd->iev.ibuf; - - if (event & EV_READ) { - if ((n = imsg_read(ibuf)) == -1) - fatal("imsg_read_error"); - if (n == 0) - fatal("parent_dispatch_dns: pipe closed"); - } + if (asr) + asr_done(asr); - if (event & EV_WRITE) { - if (msgbuf_write(&ibuf->w) == -1) - fatal("parent_dispatch_dns: msgbuf_write"); - } - - for (;;) { - if ((n = imsg_get(ibuf, &imsg)) == -1) - fatal("parent_dispatch_dns: imsg_get error"); - if (n == 0) - break; - - switch (imsg.hdr.type) { - case IMSG_DNS_A: - imsg_compose_event(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_event(rd->asker, imsg.hdr.type, 0, 0, -1, - imsg.data, sizeof(struct dns)); - close(ibuf->fd); - event_del(&iev->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"); - } - imsg_free(&imsg); - } - imsg_event_add(iev); + asr = asr_resolver(NULL); + if (asr == NULL) + log_warnx("dns_setup: unable to initialize resolver, " + "please check /etc/resolv.conf"); } -/* - * Helper resolver process. - */ - -int -dns(void) +void +dns_async(struct smtpd *env, struct imsgev *asker, int type, struct dns *query) { - int fd[2]; - pid_t pid; - struct imsgev *iev; - - if (socketpair(AF_UNIX, SOCK_STREAM, AF_UNSPEC, fd) == -1) { - log_warn("socketpair"); - return -1; - } + struct dnssession *dnssession; - session_socket_blockmode(fd[0], BM_NONBLOCK); - session_socket_blockmode(fd[1], BM_NONBLOCK); + if (dns_resolver_updated()) + dns_setup(); - if ((pid = fork()) == -1) { - log_warn("fork"); - close(fd[0]); - close(fd[1]); - return -1; + if (asr == NULL) { + log_warnx("dns_async: resolver is disabled, please check %s", + _PATH_RESCONF); + goto noasr; } - 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 ((iev = calloc(1, sizeof(*iev))) == NULL) - fatal(NULL); - imsg_init(&iev->ibuf, fd[1]); - iev->handler = dns_dispatch_parent; - iev->events = EV_READ; - iev->data = iev; - event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data); - event_add(&iev->ev, NULL); - - if (event_dispatch() < 0) - fatal("event_dispatch"); - _exit(0); -} - -void -dns_dispatch_parent(int sig, short event, void *p) -{ - struct imsgev *iev = p; - struct imsgbuf *ibuf = &iev->ibuf; - struct imsg imsg; - ssize_t n; - - if (event & 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(&iev->ev); - event_loopexit(NULL); - return; - } - } - - if (event & EV_WRITE) { - if (msgbuf_write(&ibuf->w) == -1) - fatal("dns_dispatch_parent: msgbuf_write"); + query->env = env; + query->type = type; + query->asker = asker; + dnssession = dnssession_init(env, query); + + switch (type) { + case IMSG_DNS_HOST: + dnssession->aq = asr_query_host(asr, query->host, AF_UNSPEC); + break; + case IMSG_DNS_PTR: + dnssession->aq = asr_query_dns(asr, T_PTR, C_IN, query->host, 0); + break; + case IMSG_DNS_MX: + dnssession->aq = asr_query_dns(asr, T_MX, C_IN, query->host, 0); + break; + default: + goto err; } - for (;;) { - if ((n = imsg_get(ibuf, &imsg)) == -1) - fatal("dns_dispatch_parent: imsg_get error"); - if (n == 0) - break; - - switch (imsg.hdr.type) { - case IMSG_DNS_A: - lookup_a(iev, imsg.data, 0, 1); - break; - - case IMSG_DNS_MX: - lookup_mx(iev, imsg.data); - break; - - case IMSG_DNS_PTR: - lookup_ptr(iev, 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(iev); + /* query and set up event to handle answer */ + if (dnssession->aq == NULL) + goto err; + dns_asr_handler(-1, -1, dnssession); + return; + +err: + log_debug("dns_async: ASR error while attempting to resolve `%s'", + query->host); + dnssession_destroy(env, dnssession); + +noasr: + query->error = EAI_AGAIN; + if (type != IMSG_DNS_PTR) + type = IMSG_DNS_HOST_END; + imsg_compose_event(asker, type, 0, 0, -1, query, sizeof(*query)); } void -lookup_a(struct imsgev *iev, struct dns *query, int numeric, int finalize) +dns_asr_handler(int fd, short event, void *arg) { - struct addrinfo *res0, *res, hints; - char *port = NULL; - - log_debug("lookup_a %s:%d%s", query->host, query->port, - numeric ? " (numeric)" : ""); + struct dnssession *dnssession = arg; + struct dns *query = &dnssession->query; + struct smtpd *env = query->env; + struct packed pack; + struct header h; + struct query q; + struct rr rr; + struct asr_result ar; + struct timeval tv = { 0, 0 }; + char *p; + int cnt; + int ret; + + bzero(&ar, sizeof (ar)); + + switch ((ret = asr_run(dnssession->aq, &ar))) { + case ASR_NEED_READ: + tv.tv_usec = ar.ar_timeout * 1000; + event_set(&dnssession->ev, ar.ar_fd, EV_READ, + dns_asr_handler, dnssession); + event_add(&dnssession->ev, &tv); + return; - 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; - if (numeric) - hints.ai_flags = AI_NUMERICHOST; - - query->error = getaddrinfo(query->host, port, &hints, &res0); - if (query->error) - goto end; + case ASR_NEED_WRITE: + tv.tv_usec = ar.ar_timeout * 1000; + event_set(&dnssession->ev, ar.ar_fd, EV_WRITE, + dns_asr_handler, dnssession); + event_add(&dnssession->ev, &tv); + return; - for (res = res0; res; res = res->ai_next) { - memcpy(&query->ss, res->ai_addr, res->ai_addr->sa_len); - imsg_compose_event(iev, IMSG_DNS_A, 0, 0, -1, query, sizeof(*query)); + case ASR_YIELD: + case ASR_DONE: + break; } - freeaddrinfo(res0); -end: - free(port); - log_debug("lookup_a %s", query->error ? "failed" : "success"); - if (finalize) - imsg_compose_event(iev, IMSG_DNS_A_END, 0, 0, -1, query, - sizeof(*query)); -} - -void -lookup_mx(struct imsgev *iev, struct dns *query) -{ - struct dns *mx0, *mx; - int success = 0; - log_debug("lookup_mx %s", query->host); + query->error = EAI_AGAIN; - /* if ip address, skip MX lookup */ - /* XXX: maybe do it just once in parse.y? */ - lookup_a(iev, query, 1, 0); - if (!query->error) - goto end; - - query->error = get_mxlist(query->host, query->env->sc_hostname, &mx0); - if (query->error) - goto end; + if (ret == ASR_YIELD) { + free(ar.ar_cname); + query->error = 0; + query->ss = *(struct sockaddr_storage *)&ar.ar_sa.sa; + imsg_compose_event(query->asker, IMSG_DNS_HOST, 0, 0, -1, query, + sizeof(*query)); + dns_asr_handler(-1, -1, dnssession); + return; + } - if (mx0 == NULL) { - log_debug("implicit mx"); - if ((mx0 = calloc(1, sizeof(*mx0))) == NULL) - fatal(NULL); - strlcpy(mx0->host, query->host, sizeof(mx0->host)); + /* ASR_DONE */ + if (ar.ar_err) { + query->error = ar.ar_err; + goto err; } - for (mx = mx0; mx; mx = mx->next) { - mx->port = query->port; - mx->id = query->id; - lookup_a(iev, mx, 0, 0); - if (!mx->error) - success++; + if (query->type == IMSG_DNS_HOST) { + query->error = 0; + imsg_compose_event(query->asker, IMSG_DNS_HOST_END, 0, 0, -1, + query, sizeof(*query)); + dnssession_destroy(env, dnssession); + return; } - free_mxlist(mx0); - if (success == 0) - query->error = EAI_NODATA; + packed_init(&pack, ar.ar_data, ar.ar_datalen); + if (unpack_header(&pack, &h) < 0 || unpack_query(&pack, &q) < 0) + goto err; -end: - log_debug("lookup_mx %s", query->error ? "failed" : "success"); - imsg_compose_event(iev, IMSG_DNS_A_END, 0, 0, -1, query, sizeof(*query)); -} + if (h.ancount == 0) { + query->error = EAI_NONAME; + goto err; + } -int -get_mxlist(char *host, char *self, struct dns **res) -{ - struct mx tab[MAX_MX_COUNT]; - unsigned char *p, *endp; - int ntab, i, ret, type, n, maxprio, cname_ok = 3; - int qdcount, ancount; - union { - HEADER hdr; - char buf[PACKETSZ]; - } answer; -again: - ntab = 0; - maxprio = 16384; - ret = res_query(host, C_IN, T_MX, answer.buf, sizeof(answer.buf)); - if (ret < 0) { - switch (h_errno) { - case TRY_AGAIN: - return (EAI_AGAIN); - case HOST_NOT_FOUND: - return (EAI_NONAME); - case NO_RECOVERY: - return (EAI_FAIL); - case NO_DATA: - *res = NULL; - return (0); + if (query->type == IMSG_DNS_PTR) { + if (h.ancount > 1) { + log_debug("dns_asr_handler: PTR query returned several answers."); + log_debug("dns_asr_handler: keeping only first result."); } - fatal("get_mxlist: res_query"); + if (unpack_rr(&pack, &rr) < 0) + goto err; + + print_dname(rr.rr.ptr.ptrname, query->host, sizeof (query->host)); + if ((p = strrchr(query->host, '.')) != NULL) + *p = '\0'; + free(ar.ar_data); + + query->error = 0; + imsg_compose_event(query->asker, IMSG_DNS_PTR, 0, 0, -1, query, + sizeof(*query)); + dnssession_destroy(env, dnssession); + return; } - p = answer.buf + HFIXEDSZ; - endp = answer.buf + ret; - qdcount = ntohs(((HEADER *)answer.buf)->qdcount); - ancount = ntohs(((HEADER *)answer.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; + if (query->type == IMSG_DNS_MX) { + struct mx mx; + + cnt = h.ancount; + for (; cnt; cnt--) { + if (unpack_rr(&pack, &rr) < 0) + goto err; + + print_dname(rr.rr.mx.exchange, mx.host, sizeof (mx.host)); + if ((p = strrchr(mx.host, '.')) != NULL) + *p = '\0'; + mx.prio = rr.rr.mx.preference; + + /* sorted insert that will not overflow MAX_MX_COUNT */ + dnssession_mx_insert(dnssession, &mx); + } + free(ar.ar_data); + ar.ar_data = NULL; + + /* The T_MX scenario is a bit trickier than T_PTR and T_A lookups. + * Rather than forwarding the answers to the process that queried, + * we retrieve a set of MX hosts ... that need to be resolved. The + * loop above sorts them by priority, all we have left to do is to + * perform T_A lookups on all of them sequentially and provide the + * process that queried with the answers. + * + * To make it easier, we do this in another handler. + * + * -- gilles@ + */ + dnssession->mxcurrent = &dnssession->mxarray[0]; + dnssession->aq = asr_query_host(asr, + dnssession->mxcurrent->host, AF_UNSPEC); + if (dnssession->aq == NULL) + goto err; + + dns_asr_mx_handler(-1, -1, dnssession); + return; } + return; + +err: + free(ar.ar_data); + if (query->type != IMSG_DNS_PTR) + query->type = IMSG_DNS_HOST_END; + imsg_compose_event(query->asker, query->type, 0, 0, -1, query, + sizeof(*query)); + dnssession_destroy(env, dnssession); +} - while (p < endp && ntab < ancount && ntab < MAX_MX_COUNT) { - ret = dn_skipname(p, endp); - if (ret < 0) - return (EAI_FAIL); - p += ret; - - 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(answer.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) { - log_warnx("get_mxlist: %s: bad rr type %d", host, type); - p += n; - continue; - } +/* only handle MX requests */ +void +dns_asr_mx_handler(int fd, short event, void *arg) +{ + struct dnssession *dnssession = arg; + struct dns *query = &dnssession->query; + struct smtpd *env = query->env; + struct asr_result ar; + struct timeval tv = { 0, 0 }; + struct mx *lastmx; + int ret; + + switch ((ret = asr_run(dnssession->aq, &ar))) { + case ASR_NEED_READ: + tv.tv_usec = ar.ar_timeout * 1000; + event_set(&dnssession->ev, ar.ar_fd, EV_READ, + dns_asr_mx_handler, dnssession); + event_add(&dnssession->ev, &tv); + return; - GETSHORT(tab[ntab].prio, p); + case ASR_NEED_WRITE: + tv.tv_usec = ar.ar_timeout * 1000; + event_set(&dnssession->ev, ar.ar_fd, EV_WRITE, + dns_asr_mx_handler, dnssession); + event_add(&dnssession->ev, &tv); + return; - ret = dn_expand(answer.buf, endp, p, tab[ntab].host, - sizeof(tab[ntab].host)); - if (ret < 0) - return (EAI_FAIL); - p += ret; + case ASR_YIELD: + case ASR_DONE: + break; + } - /* - * 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; + query->error = EAI_AGAIN; - ntab++; + if (ret == ASR_YIELD) { + free(ar.ar_cname); + query->ss = *(struct sockaddr_storage *)&ar.ar_sa.sa; + query->error = 0; + imsg_compose_event(query->asker, IMSG_DNS_HOST, 0, 0, -1, query, + sizeof(*query)); + dns_asr_mx_handler(-1, -1, dnssession); + return; } - /* - * 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); - - 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; + /* ASR_DONE */ + if (ar.ar_err) { + query->error = ar.ar_err; + goto end; } - if (i == 0) - return (EAI_FAIL); + lastmx = &dnssession->mxarray[dnssession->mxarraysz - 1]; + if (dnssession->mxcurrent == lastmx) { + query->error = 0; + goto end; + } - return (0); + dnssession->mxcurrent++; + dnssession->aq = asr_query_host(asr, dnssession->mxcurrent->host, + AF_UNSPEC); + if (dnssession->aq == NULL) + goto end; + dns_asr_mx_handler(-1, -1, dnssession); + return; + +end: + imsg_compose_event(query->asker, IMSG_DNS_HOST_END, 0, 0, -1, query, + sizeof(*query)); + dnssession_destroy(env, dnssession); + return; } -void -free_mxlist(struct dns *first) +struct dnssession * +dnssession_init(struct smtpd *env, struct dns *query) { - struct dns *mx, *next; + struct dnssession *dnssession; - for (mx = first; mx; mx = next) { - next = mx->next; - free(mx); - } + dnssession = calloc(1, sizeof(struct dnssession)); + if (dnssession == NULL) + fatal("dnssession_init: calloc"); + + dnssession->id = query->id; + dnssession->query = *query; + SPLAY_INSERT(dnstree, &env->dns_sessions, dnssession); + return dnssession; } -int -mxcmp(const void *va, const void *vb) +void +dnssession_destroy(struct smtpd *env, struct dnssession *dnssession) { - 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); + SPLAY_REMOVE(dnstree, &env->dns_sessions, dnssession); + event_del(&dnssession->ev); + free(dnssession); } void -lookup_ptr(struct imsgev *iev, struct dns *query) +dnssession_mx_insert(struct dnssession *dnssession, struct mx *mx) { - struct addrinfo *res, hints; - - log_debug("lookup_ptr %s", ss_to_text(&query->ss)); + size_t i; + size_t j; + + if (dnssession->mxarraysz > MAX_MX_COUNT) + dnssession->mxarraysz = MAX_MX_COUNT; + + if (dnssession->mxarraysz == 0) { + dnssession->mxarray[0] = *mx; + dnssession->mxarraysz++; + return; + } + + for (i = 0; i < dnssession->mxarraysz; ++i) + if (mx->prio < dnssession->mxarray[i].prio) + goto insert; + + if (i < MAX_MX_COUNT) + dnssession->mxarray[i] = *mx; + dnssession->mxarraysz++; + return; + +insert: + for (j = dnssession->mxarraysz; j > i; --j) + dnssession->mxarray[j] = dnssession->mxarray[j - 1]; + dnssession->mxarray[i] = *mx; +} - 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; +int +dnssession_cmp(struct dnssession *s1, struct dnssession *s2) +{ + /* + * do not return u_int64_t's + */ + if (s1->id < s2->id) + return (-1); - bzero(&hints, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_NUMERICHOST; + if (s1->id > s2->id) + return (1); - 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_event(iev, IMSG_DNS_PTR, 0, 0, -1, query, sizeof(*query)); + return (0); } + +SPLAY_GENERATE(dnstree, dnssession, nodes, dnssession_cmp); diff --git a/usr.sbin/smtpd/dnsdefs.h b/usr.sbin/smtpd/dnsdefs.h new file mode 100644 index 00000000000..d93b85c9193 --- /dev/null +++ b/usr.sbin/smtpd/dnsdefs.h @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2009 Eric Faurot <eric@faurot.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define PACKET_MAXLEN 512 + +#define HEADER_LEN 12 +#define DOMAIN_MAXLEN 255 +#define LABEL_MAXLEN 63 + +#define OPCODE_SHIFT 11 +#define Z_SHIFT 4 + +#define QR_MASK (0x1 << 15) +#define OPCODE_MASK (0xf << 11) +#define AA_MASK (0x1 << 10) +#define TC_MASK (0x1 << 9) +#define RD_MASK (0x1 << 8) +#define RA_MASK (0x1 << 7) +#define Z_MASK (0x7 << 4) +#define RCODE_MASK (0xf) + +#define OPCODE(v) ((v) & OPCODE_MASK) +#define RCODE(v) ((v) & RCODE_MASK) + +#define OP_QUERY (0) +#define OP_IQUERY (0x1 << 11) /* obsolete rfc3425*/ +#define OP_STATUS (0x2 << 11) +#define OP_NOTIFY (0x4 << 11) /* rfc1996 */ +#define OP_UPDATE (0x5 << 11) /* rfc2136 */ + + +#define NOERR 0 +#define ERR_FORMAT 1 +#define ERR_SERVER 2 +#define ERR_NAME 3 +#define ERR_NOFUNC 4 +#define ERR_REFUSED 5 + +#define ERR_YXDOMAIN 6 /* rfc2136 */ +#define ERR_YXRRSET 7 /* rfc2136 */ +#define ERR_NXRRSET 8 /* rfc2136 */ +#define ERR_NOEAUTH 9 /* rfc2136 */ +#define ERR_NOTZONE 10 /* rfc2136 */ + +#define ERR_BADVERS 16 /* rfc2671 */ +#define ERR_BADSIG 16 /* rfc2845 */ +#define ERR_BADKEY 17 /* rfc2845 */ +#define ERR_BADTIME 18 /* rfc2845 */ +#define ERR_BADMODE 19 /* rfc2930 */ +#define ERR_BADNAME 20 /* rfc2930 */ +#define ERR_BADALG 21 /* rfc2930 */ +#define ERR_BADTRUNC 22 /* rfc4635 */ + + + +/* TYPE */ + + /* rfc1035 */ +#define T_A 1 /* host address */ +#define T_NS 2 /* authoritative name server */ +#define T_MD 3 /* mail destination (Obsolete - use MX) */ +#define T_MF 4 /* mail forwarder (Obsolete - use MX) */ +#define T_CNAME 5 /* canonical name for an alias */ +#define T_SOA 6 /* marks the start of a zone of authority */ +#define T_MB 7 /* mailbox domain name (EXPERIMENTAL) */ +#define T_MG 8 /* mail group member (EXPERIMENTAL) */ +#define T_MR 9 /* mail rename domain name (EXPERIMENTAL) */ +#define T_NULL 10 /* null RR (EXPERIMENTAL) */ +#define T_WKS 11 /* well known service description */ +#define T_PTR 12 /* domain name pointer */ +#define T_HINFO 13 /* host information */ +#define T_MINFO 14 /* mailbox or mail list information */ +#define T_MX 15 /* mail exchange */ +#define T_TXT 16 /* text strings */ + + /* rfc1183 */ +#define T_RP 17 /* responsible person */ +#define T_AFSDB 18 /* AFS Database location */ +#define T_X25 19 /* X25 PSDN address */ +#define T_ISDN 20 /* ISDN address */ +#define T_RT 21 /* route through */ + +#define T_NSAP 22 /* NSAP address rfc1706 */ +#define T_NSAPPTR 23 /* rfc1348 */ +#define T_SIG 24 /* security signature rfc2931, rfc4034 */ +#define T_KEY 25 /* security key rfc3445, rfc4034 */ +#define T_PX 26 /* X.400 mail mapping info rfc2163 */ +#define T_GPOS 27 /* geographical position rfc1712 */ +#define T_AAAA 28 /* IPv6 address rfc3596 */ +#define T_LOC 29 /* location information rfc1876 */ +#define T_NXT 30 /* next domain (obsolete) rfc2535 */ +#define T_EID 31 /* endpoint identifier */ +#define T_NIMLOC 32 /* nimrod locator */ +#define T_NB 32 /* NetBIOS general name service rfc1002 */ +#define T_SRV 33 /* server selection rfc2052, rfc2782, */ +#define T_NBSTAT 33 /* NetBIOS node status rfc1002 */ +#define T_ATMA 34 /* atm address */ +#define T_NAPTR 35 /* naming authority pointer rfc3403 */ +#define T_KX 36 /* key exchange rfc2230 */ +#define T_CERT 37 /* rfc2538, rfc4398 */ +#define T_A6 38 /* rfc2874, rfc3226 */ +#define T_DNAME 39 /* rfc2672 */ +#define T_SINK 40 /* */ +#define T_OPT 41 /* rfc2671 */ +#define T_APL 42 /* rfc3123 */ +#define T_DS 43 /* delegation signer rfc3658 */ +#define T_SSHFP 44 /* ssh key fingerprint rfc4255 */ +#define T_IPSECKEY 45 /* rfc4025 */ +#define T_RRSIG 46 /* rfc3755 */ +#define T_NSEC 47 /* NextSECure rfc3755, rfc3845 */ +#define T_DNSKEY 48 /* rfc3755 */ +#define T_DHCID 49 /* DHCP identifier rfc4701 */ +#define T_NSEC3 50 /* rfc5155 */ +#define T_NSEC3PARAM 51 /* rfc5155 */ + +#define T_HIP 55 /* Host Identity protocol rfc5205 */ +#define T_NINFO 56 +#define T_RKEY 57 + +#define T_SPF 99 /* sender policy framework rfc4408 */ +#define T_UINFO 100 +#define T_UID 101 +#define T_GID 102 +#define T_UNSPEC 103 + +#define T_TKEY 249 /* rfc2930 */ +#define T_TSIG 250 /* transaction signature rfc2845, rfc3645 */ +#define T_IXFR 251 /* incremental transfer rfc1995 */ + + /* request only */ +#define T_AXFR 252 /* transfer of an entire zone rfc1035 */ +#define T_MAILB 253 /* mailbox-related records (MB, MG or MR) rfc1035 */ +#define T_MAILA 254 /* mail agent RRs (Obsolete - see MX) rfc1035 */ +#define T_ALL 255 /* all records rfc1035 */ + +#define T_DNSSECTA 32768 /* DNSSEC trust authorities */ +#define T_DNSSECLV 32769 /* lookaside validation rfc4431, rfc5074 */ + + +/* CLASS */ + /* 0 reserved rfc5395 */ +#define C_IN 1 /* Internet */ +#define C_CS 2 /* CSNET (obsolete) */ +#define C_CH 3 /* Chaos */ +#define C_HS 4 /* Hesiod */ + +#define C_NONE 254 /* rfc2136 */ +#define C_ANY 255 /* */ + +#define C_PRIV0 65280 /* rfc5395 */ +#define C_PRIV1 65534 /* rfc5395 */ + /* 65535 reserved rfc5395 */ diff --git a/usr.sbin/smtpd/dnsutil.h b/usr.sbin/smtpd/dnsutil.h new file mode 100644 index 00000000000..4c77245c652 --- /dev/null +++ b/usr.sbin/smtpd/dnsutil.h @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2009,2010 Eric Faurot <eric@faurot.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> + +#include "dnsdefs.h" + +struct packed { + char *data; + size_t len; + size_t offset; + const char *err; +}; + +struct header { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +}; + +struct query { + char q_dname[DOMAIN_MAXLEN]; + uint16_t q_type; + uint16_t q_class; +}; + +struct rr { + char rr_dname[DOMAIN_MAXLEN]; + uint16_t rr_type; + uint16_t rr_class; + uint32_t rr_ttl; + union { + struct { + char cname[DOMAIN_MAXLEN]; + } cname; + struct { + uint16_t preference; + char exchange[DOMAIN_MAXLEN]; + } mx; + struct { + char nsname[DOMAIN_MAXLEN]; + } ns; + struct { + char ptrname[DOMAIN_MAXLEN]; + } ptr; + struct { + char mname[DOMAIN_MAXLEN]; + char rname[DOMAIN_MAXLEN]; + uint32_t serial; + uint32_t refresh; + uint32_t retry; + uint32_t expire; + uint32_t minimum; + } soa; + struct { + struct in_addr addr; + } in_a; + struct { + struct in6_addr addr6; + } in_aaaa; + struct { + uint16_t rdlen; + const void *rdata; + } other; + } rr; +}; + +struct rr_dynamic { + const char *rd_dname; + uint16_t rd_type; + uint16_t rd_class; + uint32_t rd_ttl; + union rr_subtype { + struct rr_cname { + char *cname; + } cname; + struct rr_mx { + uint16_t preference; + char *exchange; + } mx; + struct rr_ns { + char *nsname; + } ns; + struct rr_ptr { + char *ptrname; + } ptr; + struct rr_soa { + char *mname; + char *rname; + uint32_t serial; + uint32_t refresh; + uint32_t retry; + uint32_t expire; + uint32_t minimum; + } soa; + struct rr_in_a { + struct in_addr addr; + } in_a; + struct rr_in_aaaa { + struct in6_addr addr6; + } in_aaaa; + struct rr_other { + uint16_t rdlen; + void *rdata; + } other; + } rd; +}; + + + +/* pack.c */ +void packed_init(struct packed*, char*, size_t); + +int unpack_data(struct packed*, void*, size_t); +int unpack_u16(struct packed*, uint16_t*); +int unpack_u32(struct packed*, uint32_t*); +int unpack_inaddr(struct packed*, struct in_addr*); +int unpack_in6addr(struct packed*, struct in6_addr*); +int unpack_dname(struct packed*, char*, size_t); +int unpack_header(struct packed*, struct header*); +int unpack_query(struct packed*, struct query*); +int unpack_rr(struct packed*, struct rr*); + +int pack_data(struct packed*, const void*, size_t); +int pack_u16(struct packed*, uint16_t); +int pack_u32(struct packed*, uint32_t); +int pack_inaddr(struct packed*, struct in_addr); +int pack_in6addr(struct packed*, struct in6_addr); +int pack_header(struct packed*, const struct header*); +int pack_dname(struct packed*, const char*); +int pack_query(struct packed*, uint16_t, uint16_t, const char*); +int pack_rrdynamic(struct packed*, const struct rr_dynamic *rr); + +/* sockaddr.c */ +int sockaddr_from_rr(struct sockaddr *, struct rr *); +int sockaddr_from_str(struct sockaddr *, int, const char *); +void sockaddr_set_port(struct sockaddr *, int); +int sockaddr_connect(const struct sockaddr *, int); +int sockaddr_listen(const struct sockaddr *, int); + +/* print.c */ +const char *print_host(struct sockaddr*, char*, size_t); +const char *print_addr(struct sockaddr*, char*, size_t); +const char *print_dname(const char*, char*, size_t); +const char *print_header(struct header*, char*, size_t); +const char *print_query(struct query*, char*, size_t); +const char *print_rr(struct rr*, char*, size_t); +const char *print_rrdynamic(struct rr_dynamic*, char*, size_t); + +const char *typetostr(uint16_t); +const char *classtostr(uint16_t); +const char *rcodetostr(uint16_t); + +uint16_t strtotype(const char*); +uint16_t strtoclass(const char*); +const char *inet6_ntoa(struct in6_addr); + +/* dname.c */ +size_t dname_len(const char *); +size_t dname_depth(const char *); +ssize_t dname_from_fqdn(const char*, char*, size_t); +ssize_t dname_from_sockaddr(const struct sockaddr *, char*, size_t); +int dname_is_in(const char*, const char*); +int dname_is_wildcard(const char *); +int dname_is_reverse(const char *); +int dname_check_label(const char*, size_t); +const char* dname_up(const char*, unsigned int); + +/* res_random.c */ +unsigned int res_randomid(void); diff --git a/usr.sbin/smtpd/lka.c b/usr.sbin/smtpd/lka.c index 145be0a1100..3e8aa3ded91 100644 --- a/usr.sbin/smtpd/lka.c +++ b/usr.sbin/smtpd/lka.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka.c,v 1.122 2010/11/28 14:35:58 gilles Exp $ */ +/* $OpenBSD: lka.c,v 1.123 2010/11/29 15:25:55 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -75,7 +75,7 @@ lka_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) struct map *map; void *tmp; - if (imsg->hdr.type == IMSG_DNS_A || imsg->hdr.type == IMSG_DNS_MX || + if (imsg->hdr.type == IMSG_DNS_HOST || imsg->hdr.type == IMSG_DNS_MX || imsg->hdr.type == IMSG_DNS_PTR) { dns_async(env, iev, imsg->hdr.type, imsg->data); return; diff --git a/usr.sbin/smtpd/mta.c b/usr.sbin/smtpd/mta.c index 390e8fc8639..be30fc96a3c 100644 --- a/usr.sbin/smtpd/mta.c +++ b/usr.sbin/smtpd/mta.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mta.c,v 1.96 2010/11/28 14:35:58 gilles Exp $ */ +/* $OpenBSD: mta.c,v 1.97 2010/11/29 15:25:55 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -79,8 +79,10 @@ mta_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) s->datafd = -1; /* establish host name */ - if (b->rule.r_action == A_RELAYVIA) + if (b->rule.r_action == A_RELAYVIA) { s->host = strdup(b->rule.r_value.relayhost.hostname); + s->flags |= MTA_FORCE_MX; + } else s->host = strdup(b->hostname); if (s->host == NULL) @@ -158,7 +160,7 @@ mta_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) mta_pickup(mta_lookup(env, secret->id), secret->secret); return; - case IMSG_DNS_A: + case IMSG_DNS_HOST: dns = imsg->data; s = mta_lookup(env, dns->id); relay = calloc(1, sizeof *relay); @@ -168,7 +170,7 @@ mta_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) TAILQ_INSERT_TAIL(&s->relays, relay, entry); return; - case IMSG_DNS_A_END: + case IMSG_DNS_HOST_END: dns = imsg->data; mta_pickup(mta_lookup(env, dns->id), &dns->error); return; @@ -361,7 +363,10 @@ mta_enter_state(struct mta_session *s, int newstate, void *p) /* * Lookup MX record. */ - dns_query_mx(s->env, s->host, 0, s->id); + if (s->flags & MTA_FORCE_MX) + dns_query_host(s->env, s->host, s->port, s->id); + else + dns_query_mx(s->env, s->host, 0, s->id); break; case MTA_DATA: diff --git a/usr.sbin/smtpd/pack.c b/usr.sbin/smtpd/pack.c new file mode 100644 index 00000000000..ceb3da34ee7 --- /dev/null +++ b/usr.sbin/smtpd/pack.c @@ -0,0 +1,469 @@ +/* + * Copyright (c) 2009,2010 Eric Faurot <eric@faurot.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <string.h> + +#include "dnsutil.h" + +int dname_check_label(const char*, size_t); +const char* dname_nthlabel(int, const unsigned char*, size_t, size_t); +ssize_t dname_count_labels(const unsigned char*, size_t, size_t); +ssize_t dname_expand(const unsigned char*, size_t, size_t, + size_t*, char *, size_t); + + +void +packed_init(struct packed *pack, char *data, size_t len) +{ + pack->data = data; + pack->len = len; + pack->offset = 0; + pack->err = NULL; +} + +ssize_t +dname_expand(const unsigned char *data, size_t len, size_t offset, + size_t *newoffset, char *dst, size_t max) +{ + size_t n, count, end, ptr, start; + ssize_t res; + + if (offset >= len) + return (-1); + + res = 0; + end = start = offset; + + for(; (n = data[offset]); ) { + if ((n & 0xc0) == 0xc0) { + if (offset + 2 > len) + return (-1); + ptr = 256 * (n & ~0xc0) + data[offset + 1]; + if (ptr >= start) + return (-1); + if (end < offset + 2) + end = offset + 2; + offset = ptr; + continue; + } + if (offset + n + 1 > len) + return (-1); + + if (dname_check_label(data + offset + 1, n) == -1) + return (-1); + + /* copy n + at offset+1 */ + if (dst != NULL && max != 0) { + count = (max < n + 1) ? (max) : (n + 1); + memmove(dst, data + offset, count); + dst += count; + max -= count; + } + res += n + 1; + offset += n + 1; + if (end < offset) + end = offset; + } + if (end < offset + 1) + end = offset + 1; + + if (dst != NULL && max != 0) + dst[0] = 0; + if (newoffset) + *newoffset = end; + return (res + 1); +} + +const char * +dname_nthlabel(int n, const unsigned char *data, size_t len, size_t offset) +{ + int i; + size_t c, ptr, start; + + start = offset; + for(i = 0;;) { + c = data[offset]; + if (c == 0) + return (NULL); + if ((c & 0xc0) == 0xc0) { + if (len < offset + 2) + return (NULL); + ptr = 256 * (c & ~0xc0) + data[offset + 1]; + if (ptr >= start) + return (NULL); + offset = ptr; + continue; + } + if (i == n) + break; + offset += c + 1; + i++; + } + return (data + offset); +} + +ssize_t +dname_count_labels(const unsigned char *data, size_t len, size_t offset) +{ + size_t c, n, ptr, start; + + start = offset; + for(n = 0; (c = data[offset]); ) { + if ((c & 0xc0) == 0xc0) { + if (len < offset + 2) + return (-1); + ptr = 256 * (c & ~0xc0) + data[offset + 1]; + if (ptr >= start) + return (-1); + offset = ptr; + continue; + } + offset += c + 1; + n += 1; + } + + return (n); +} + +int +unpack_data(struct packed *p, void *data, size_t len) +{ + if (p->err) + return (-1); + + if (p->len - p->offset < len) { + p->err = "too short"; + return (-1); + } + + memmove(data, p->data + p->offset, len); + p->offset += len; + + return (0); +} + +int +unpack_u16(struct packed *p, uint16_t *u16) +{ + if (unpack_data(p, u16, 2) == -1) + return (-1); + + *u16 = ntohs(*u16); + + return (0); +} + +int +unpack_u32(struct packed *p, uint32_t *u32) +{ + if (unpack_data(p, u32, 4) == -1) + return (-1); + + *u32 = ntohl(*u32); + + return (0); +} + +int +unpack_inaddr(struct packed *p, struct in_addr *a) +{ + return (unpack_data(p, a, 4)); +} + +int +unpack_in6addr(struct packed *p, struct in6_addr *a6) +{ + return (unpack_data(p, a6, 16)); +} + +int +unpack_dname(struct packed *p, char *dst, size_t max) +{ + ssize_t e; + + if (p->err) + return (-1); + + e = dname_expand(p->data, p->len, p->offset, &p->offset, dst, max); + if (e == -1) { + p->err = "bad domain name"; + return (-1); + } + if (e < 0 || e > DOMAIN_MAXLEN) { + p->err = "domain name too long"; + return (-1); + } + + return (0); +} + +int +unpack_header(struct packed *p, struct header *h) +{ + if (unpack_data(p, h, HEADER_LEN) == -1) + return (-1); + + h->flags = ntohs(h->flags); + h->qdcount = ntohs(h->qdcount); + h->ancount = ntohs(h->ancount); + h->nscount = ntohs(h->nscount); + h->arcount = ntohs(h->arcount); + + return (0); +} + +int +unpack_query(struct packed *p, struct query *q) +{ + unpack_dname(p, q->q_dname, sizeof(q->q_dname)); + unpack_u16(p, &q->q_type); + unpack_u16(p, &q->q_class); + + return (p->err) ? (-1) : (0); +} + +int +unpack_rr(struct packed *p, struct rr *rr) +{ + uint16_t rdlen; + size_t save_offset; + + unpack_dname(p, rr->rr_dname, sizeof(rr->rr_dname)); + unpack_u16(p, &rr->rr_type); + unpack_u16(p, &rr->rr_class); + unpack_u32(p, &rr->rr_ttl); + unpack_u16(p, &rdlen); + + if (p->err) + return (-1); + + if (p->len - p->offset < rdlen) { + p->err = "too short"; + return (-1); + } + + save_offset = p->offset; + + switch(rr->rr_type) { + + case T_CNAME: + unpack_dname(p, rr->rr.cname.cname, + sizeof(rr->rr.cname.cname)); + break; + + case T_MX: + unpack_u16(p, &rr->rr.mx.preference); + unpack_dname(p, rr->rr.mx.exchange, + sizeof(rr->rr.mx.exchange)); + break; + + case T_NS: + unpack_dname(p, rr->rr.ns.nsname, + sizeof(rr->rr.ns.nsname)); + break; + + case T_PTR: + unpack_dname(p, rr->rr.ptr.ptrname, + sizeof(rr->rr.ptr.ptrname)); + break; + + case T_SOA: + unpack_dname(p, rr->rr.soa.mname, + sizeof(rr->rr.soa.mname)); + unpack_dname(p, rr->rr.soa.rname, + sizeof(rr->rr.soa.rname)); + unpack_u32(p, &rr->rr.soa.serial); + unpack_u32(p, &rr->rr.soa.refresh); + unpack_u32(p, &rr->rr.soa.retry); + unpack_u32(p, &rr->rr.soa.expire); + unpack_u32(p, &rr->rr.soa.minimum); + break; + + case T_A: + if (rr->rr_class != C_IN) + goto other; + unpack_inaddr(p, &rr->rr.in_a.addr); + break; + + case T_AAAA: + if (rr->rr_class != C_IN) + goto other; + unpack_in6addr(p, &rr->rr.in_aaaa.addr6); + break; + default: + other: + rr->rr.other.rdata = p->data + p->offset; + rr->rr.other.rdlen = rdlen; + p->offset += rdlen; + } + + if (p->err) + return (-1); + + /* make sure that the advertised rdlen is really ok */ + if (p->offset - save_offset != rdlen) + p->err = "bad dlen"; + + return (p->err) ? (-1) : (0); +} + +int +pack_data(struct packed *p, const void *data, size_t len) +{ + if (p->err) + return (-1); + + if (p->len < p->offset + len) { + p->err = "no space"; + return (-1); + } + + memmove(p->data + p->offset, data, len); + p->offset += len; + + return (0); +} + +int +pack_u16(struct packed *p, uint16_t v) +{ + v = htons(v); + + return (pack_data(p, &v, 2)); +} + +int +pack_u32(struct packed *p, uint32_t v) +{ + v = htonl(v); + + return (pack_data(p, &v, 4)); +} + +int +pack_inaddr(struct packed *p, struct in_addr a) +{ + return (pack_data(p, &a, 4)); +} + +int +pack_in6addr(struct packed *p, struct in6_addr a6) +{ + return (pack_data(p, &a6, 16)); +} + +int +pack_dname(struct packed *p, const char *dname) +{ + /* dname compression would be nice to have here. + * need additionnal context. + */ + return (pack_data(p, dname, strlen(dname) + 1)); +} + +int +pack_header(struct packed *p, const struct header *h) +{ + struct header c; + + c.id = h->id; + c.flags = htons(h->flags); + c.qdcount = htons(h->qdcount); + c.ancount = htons(h->ancount); + c.nscount = htons(h->nscount); + c.arcount = htons(h->arcount); + + return (pack_data(p, &c, HEADER_LEN)); +} + +int +pack_query(struct packed *p, uint16_t type, uint16_t class, const char *dname) +{ + pack_dname(p, dname); + pack_u16(p, type); + pack_u16(p, class); + + return (p->err) ? (-1) : (0); +} + +int +pack_rrdynamic(struct packed *p, const struct rr_dynamic *rd) +{ + const union rr_subtype *rr; + struct packed save; + + pack_dname(p, rd->rd_dname); + pack_u16(p, rd->rd_type); + pack_u16(p, rd->rd_class); + pack_u32(p, rd->rd_ttl); + + save = *p; + pack_u16(p, 0); /* rdlen */ + + rr = &rd->rd; + switch(rd->rd_type) { + case T_CNAME: + pack_dname(p, rr->cname.cname); + break; + + case T_MX: + pack_u16(p, rr->mx.preference); + pack_dname(p, rr->mx.exchange); + break; + + case T_NS: + pack_dname(p, rr->ns.nsname); + break; + + case T_PTR: + pack_dname(p, rr->ptr.ptrname); + break; + + case T_SOA: + pack_dname(p, rr->soa.mname); + pack_dname(p, rr->soa.rname); + pack_u32(p, rr->soa.serial); + pack_u32(p, rr->soa.refresh); + pack_u32(p, rr->soa.retry); + pack_u32(p, rr->soa.expire); + pack_u32(p, rr->soa.minimum); + break; + + case T_A: + if (rd->rd_class != C_IN) + goto other; + pack_inaddr(p, rr->in_a.addr); + break; + + case T_AAAA: + if (rd->rd_class != C_IN) + goto other; + pack_in6addr(p, rr->in_aaaa.addr6); + break; + default: + other: + pack_data(p, rr->other.rdata, rr->other.rdlen); + } + + if (p->err) + return (-1); + + /* rewrite rdlen */ + pack_u16(&save, p->offset - save.offset - 2); + p->err = save.err; + + return (p->err) ? (-1) : (0); +} diff --git a/usr.sbin/smtpd/print.c b/usr.sbin/smtpd/print.c new file mode 100644 index 00000000000..c310422bf99 --- /dev/null +++ b/usr.sbin/smtpd/print.c @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2009,2010 Eric Faurot <eric@faurot.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <inttypes.h> +#include <netdb.h> +#include <stdio.h> +#include <string.h> + +#include "dnsutil.h" + +struct keyval { + const char *key; + uint16_t value; +}; + +struct keyval kv_class[] = { + { "IN", C_IN }, + { "CS", C_CS }, + { "CH", C_CH }, + { "HS", C_HS }, +}; + +struct keyval kv_type[] = { + { "A", T_A }, + { "NS", T_NS }, + { "MD", T_MD }, + { "MF", T_MF }, + { "CNAME", T_CNAME }, + { "SOA", T_SOA }, + { "MB", T_MB }, + { "MG", T_MG }, + { "MR", T_MR }, + { "NULL", T_NULL }, + { "WKS", T_WKS }, + { "PTR", T_PTR }, + { "HINFO", T_HINFO }, + { "MINFO", T_MINFO }, + { "MX", T_MX }, + { "TXT", T_TXT }, + + { "AXFR", T_AXFR }, + { "MAILB", T_MAILB }, + { "MAILA", T_MAILA }, + { "ALL", T_ALL }, + + { "AAAA", T_AAAA }, +}; + +struct keyval kv_rcode[] = { + { "NOERROR", NOERR }, + { "ERR_FORMAT", ERR_FORMAT }, + { "ERR_SERVER", ERR_SERVER }, + { "ERR_NAME", ERR_NAME }, + { "ERR_NOFUNC", ERR_NOFUNC }, + { "ERR_REFUSED",ERR_REFUSED }, +}; + +const char * +typetostr(uint16_t v) +{ + static char buf[16]; + size_t i; + + for(i = 0; i < sizeof(kv_type)/sizeof(kv_type[0]); i++) + if (kv_type[i].value == v) + return (kv_type[i].key); + + snprintf(buf, sizeof buf, "%"PRIu16"?", v); + + return (buf); +} + +const char * +classtostr(uint16_t v) +{ + static char buf[16]; + size_t i; + + for(i = 0; i < sizeof(kv_class)/sizeof(kv_class[0]); i++) + if (kv_class[i].value == v) + return (kv_class[i].key); + + snprintf(buf, sizeof buf, "%"PRIu16"?", v); + + return (buf); +} + +const char * +rcodetostr(uint16_t v) +{ + static char buf[16]; + size_t i; + + for(i = 0; i < sizeof(kv_rcode)/sizeof(kv_rcode[0]); i++) + if (kv_rcode[i].value == v) + return (kv_rcode[i].key); + + snprintf(buf, sizeof buf, "%"PRIu16"?", v); + + return (buf); +} + +uint16_t +strtotype(const char *name) +{ + size_t i; + + for(i = 0; i < sizeof(kv_type)/sizeof(kv_type[0]); i++) + if (!strcmp(kv_type[i].key, name)) + return (kv_type[i].value); + + return (0); +} + +uint16_t +strtoclass(const char *name) +{ + size_t i; + + for(i = 0; i < sizeof(kv_class)/sizeof(kv_class[0]); i++) + if (!strcmp(kv_class[i].key, name)) + return (kv_class[i].value); + + return (0); +} + +const char * +inet6_ntoa(struct in6_addr a) +{ + static char buf[256]; + struct sockaddr_in6 si; + + si.sin6_len = sizeof(si); + si.sin6_family = PF_INET6; + si.sin6_addr = a; + + return print_host((struct sockaddr*)&si, buf, sizeof buf); +} + +const char* +print_rr(struct rr *rr, char *buf, size_t max) +{ + char *res; + char tmp[256]; + char tmp2[256]; + int r; + + res = buf; + + r = snprintf(buf, max, "%s %u %s %s ", + print_dname(rr->rr_dname, tmp, sizeof tmp), + rr->rr_ttl, + classtostr(rr->rr_class), + typetostr(rr->rr_type)); + if (r == -1) { + buf[0] = '\0'; + return buf; + } + + if ((size_t)r >= max) + return buf; + + max -= r; + buf += r; + + switch(rr->rr_type) { + case T_CNAME: + print_dname(rr->rr.cname.cname, buf, max); + break; + case T_MX: + snprintf(buf, max, "%"PRIu32" %s", + rr->rr.mx.preference, + print_dname(rr->rr.mx.exchange, tmp, sizeof tmp)); + break; + case T_NS: + print_dname(rr->rr.ns.nsname, buf, max); + break; + case T_PTR: + print_dname(rr->rr.ptr.ptrname, buf, max); + break; + case T_SOA: + snprintf(buf, max, + "%s %s %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32, + print_dname(rr->rr.soa.rname, tmp, sizeof tmp), + print_dname(rr->rr.soa.mname, tmp2, sizeof tmp2), + rr->rr.soa.serial, + rr->rr.soa.refresh, + rr->rr.soa.retry, + rr->rr.soa.expire, + rr->rr.soa.minimum); + break; + case T_A: + if (rr->rr_class != C_IN) + goto other; + snprintf(buf, max, "%s", inet_ntoa(rr->rr.in_a.addr)); + break; + case T_AAAA: + if (rr->rr_class != C_IN) + goto other; + snprintf(buf, max, inet6_ntoa(rr->rr.in_aaaa.addr6)); + break; + default: + other: + snprintf(buf, max, "(rdlen=%"PRIu16 ")", rr->rr.other.rdlen); + break; + } + + return (res); +} + +const char* +print_rrdynamic(struct rr_dynamic *rd, char *buf, size_t max) +{ + char *res; + char tmp[256]; + char tmp2[256]; + int r; + + res = buf; + + r = snprintf(buf, max, "%s %u %s %s ", + print_dname(rd->rd_dname, tmp, sizeof tmp), + rd->rd_ttl, + classtostr(rd->rd_class), + typetostr(rd->rd_type)); + if (r == -1) { + buf[0] = '\0'; + return buf; + } + + if ((size_t)r >= max) + return buf; + + max -= r; + buf += r; + + switch(rd->rd_type) { + case T_CNAME: + print_dname(rd->rd.cname.cname, buf, max); + break; + case T_MX: + snprintf(buf, max, "%"PRIu32" %s", + rd->rd.mx.preference, + print_dname(rd->rd.mx.exchange, tmp, sizeof tmp)); + break; + case T_NS: + print_dname(rd->rd.ns.nsname, buf, max); + break; + case T_PTR: + print_dname(rd->rd.ptr.ptrname, buf, max); + break; + case T_SOA: + snprintf(buf, max, + "%s %s %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32, + print_dname(rd->rd.soa.rname, tmp, sizeof tmp), + print_dname(rd->rd.soa.mname, tmp2, sizeof tmp2), + rd->rd.soa.serial, + rd->rd.soa.refresh, + rd->rd.soa.retry, + rd->rd.soa.expire, + rd->rd.soa.minimum); + break; + case T_A: + if (rd->rd_class != C_IN) + goto other; + snprintf(buf, max, "%s", inet_ntoa(rd->rd.in_a.addr)); + break; + case T_AAAA: + if (rd->rd_class != C_IN) + goto other; + snprintf(buf, max, inet6_ntoa(rd->rd.in_aaaa.addr6)); + break; + default: + other: + snprintf(buf, max, "(rdlen=%"PRIu16 ")", rd->rd.other.rdlen); + break; + } + + return (res); +} + +const char* +print_query(struct query *q, char *buf, size_t max) +{ + char b[256]; + + snprintf(buf, max, "%s %s %s", + print_dname(q->q_dname, b, sizeof b), + classtostr(q->q_class), typetostr(q->q_type)); + + return (buf); +} + +const char* +print_dname(const char *_dname, char *buf, size_t max) +{ + const unsigned char *dname = _dname; + char *res; + size_t left, n, count; + + if (_dname[0] == 0) { + strlcpy(buf, ".", max); + return buf; + } + + res = buf; + left = max - 1; + for (n = 0; dname[0] && left; n += dname[0]) { + count = (dname[0] < (left - 1)) ? dname[0] : (left - 1); + memmove(buf, dname + 1, count); + dname += dname[0] + 1; + left -= count; + buf += count; + if (left) { + left -= 1; + *buf++ = '.'; + } + } + buf[0] = 0; + + return (res); +} + +const char* +print_header(struct header *h, char *buf, size_t max) +{ + snprintf(buf, max, + "id:0x%04x %s op:%i %s %s %s %s z:%i r:%s qd:%i an:%i ns:%i ar:%i", + (int)h->id, + (h->flags & QR_MASK) ? "QR":" ", + (int)(OPCODE(h->flags) >> OPCODE_SHIFT), + (h->flags & AA_MASK) ? "AA":" ", + (h->flags & TC_MASK) ? "TC":" ", + (h->flags & RD_MASK) ? "RD":" ", + (h->flags & RA_MASK) ? "RA":" ", + ((h->flags & Z_MASK) >> Z_SHIFT), + rcodetostr(RCODE(h->flags)), + h->qdcount, h->ancount, h->nscount, h->arcount); + + return buf; +} + +const char * +print_host(struct sockaddr *sa, char *buf, size_t len) +{ + int e; + + if ((e = getnameinfo(sa, sa->sa_len, + buf, len, NULL, 0, NI_NUMERICHOST)) != 0) { + buf[0] = '\0'; + return (NULL); + } + return (buf); +} + +const char * +print_addr(struct sockaddr *sa, char *buf, size_t len) +{ + char h[256]; + + print_host(sa, h, sizeof h); + + switch (sa->sa_family) { + case AF_INET: + snprintf(buf, len, "%s:%i", h, + ntohs(((struct sockaddr_in*)(sa))->sin_port)); + break; + case AF_INET6: + snprintf(buf, len, "[%s]:%i", h, + ntohs(((struct sockaddr_in6*)(sa))->sin6_port)); + break; + default: + snprintf(buf, len, "?"); + break; + } + + return (buf); +} diff --git a/usr.sbin/smtpd/res_random.c b/usr.sbin/smtpd/res_random.c new file mode 100644 index 00000000000..18c777dce7d --- /dev/null +++ b/usr.sbin/smtpd/res_random.c @@ -0,0 +1,270 @@ +/* + * Copied from: lib/libc/net/res_random.c + * + * -- eric@ + */ +/* $OpenBSD: res_random.c,v 1.1 2010/11/29 15:25:56 gilles Exp $ */ + +/* + * Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de> + * Copyright 2008 Damien Miller <djm@openbsd.org> + * All rights reserved. + * + * Theo de Raadt <deraadt@openbsd.org> came up with the idea of using + * such a mathematical system to generate more random (yet non-repeating) + * ids to solve the resolver/named problem. But Niels designed the + * actual system based on the constraints. + * + * Later modified by Damien Miller to wrap the LCG output in a 15-bit + * permutation generator based on a Luby-Rackoff block cipher. This + * ensures the output is non-repeating and preserves the MSB twiddle + * trick, but makes it more resistant to LCG prediction. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * seed = random 15bit + * n = prime, g0 = generator to n, + * j = random so that gcd(j,n-1) == 1 + * g = g0^j mod n will be a generator again. + * + * X[0] = random seed. + * X[n] = a*X[n-1]+b mod m is a Linear Congruential Generator + * with a = 7^(even random) mod m, + * b = random with gcd(b,m) == 1 + * m = 31104 and a maximal period of m-1. + * + * The transaction id is determined by: + * id[n] = seed xor (g^X[n] mod n) + * + * Effectivly the id is restricted to the lower 15 bits, thus + * yielding two different cycles by toggling the msb on and off. + * This avoids reuse issues caused by reseeding. + * + * The output of this generator is then randomly permuted though a + * custom 15 bit Luby-Rackoff block cipher. + */ + +#include <sys/types.h> +#include <netinet/in.h> +#include <sys/time.h> + +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +#include "dnsutil.h" + +#define RU_OUT 180 /* Time after wich will be reseeded */ +#define RU_MAX 30000 /* Uniq cycle, avoid blackjack prediction */ +#define RU_GEN 2 /* Starting generator */ +#define RU_N 32749 /* RU_N-1 = 2*2*3*2729 */ +#define RU_AGEN 7 /* determine ru_a as RU_AGEN^(2*rand) */ +#define RU_M 31104 /* RU_M = 2^7*3^5 - don't change */ +#define RU_ROUNDS 11 /* Number of rounds for permute (odd) */ + +struct prf_ctx { + /* PRF lookup table for odd rounds (7 bits input to 8 bits output) */ + u_char prf7[(RU_ROUNDS / 2) * (1 << 7)]; + + /* PRF lookup table for even rounds (8 bits input to 7 bits output) */ + u_char prf8[((RU_ROUNDS + 1) / 2) * (1 << 8)]; +}; + +#define PFAC_N 3 +static const u_int16_t pfacts[PFAC_N] = { + 2, + 3, + 2729 +}; + +static u_int16_t ru_x; +static u_int16_t ru_seed, ru_seed2; +static u_int16_t ru_a, ru_b; +static u_int16_t ru_g; +static u_int16_t ru_counter = 0; +static u_int16_t ru_msb = 0; +static struct prf_ctx *ru_prf = NULL; +static long ru_reseed; + +static u_int16_t pmod(u_int16_t, u_int16_t, u_int16_t); +static void res_initid(void); + +/* + * Do a fast modular exponation, returned value will be in the range + * of 0 - (mod-1) + */ +static u_int16_t +pmod(u_int16_t gen, u_int16_t exp, u_int16_t mod) +{ + u_int16_t s, t, u; + + s = 1; + t = gen; + u = exp; + + while (u) { + if (u & 1) + s = (s * t) % mod; + u >>= 1; + t = (t * t) % mod; + } + return (s); +} + +/* + * 15-bit permutation based on Luby-Rackoff block cipher + */ +static u_int +permute15(u_int in) +{ + int i; + u_int left, right, tmp; + + if (ru_prf == NULL) + return in; + + left = (in >> 8) & 0x7f; + right = in & 0xff; + + /* + * Each round swaps the width of left and right. Even rounds have + * a 7-bit left, odd rounds have an 8-bit left. Since this uses an + * odd number of rounds, left is always 8 bits wide at the end. + */ + for (i = 0; i < RU_ROUNDS; i++) { + if ((i & 1) == 0) + tmp = ru_prf->prf8[(i << (8 - 1)) | right] & 0x7f; + else + tmp = ru_prf->prf7[((i - 1) << (7 - 1)) | right]; + tmp ^= left; + left = right; + right = tmp; + } + + return (right << 8) | left; +} + +/* + * Initializes the seed and chooses a suitable generator. Also toggles + * the msb flag. The msb flag is used to generate two distinct + * cycles of random numbers and thus avoiding reuse of ids. + * + * This function is called from res_randomid() when needed, an + * application does not have to worry about it. + */ +static void +res_initid(void) +{ + u_int16_t j, i; + u_int32_t tmp; + int noprime = 1; + struct timeval tv; + + ru_x = arc4random_uniform(RU_M); + + /* 15 bits of random seed */ + tmp = arc4random(); + ru_seed = (tmp >> 16) & 0x7FFF; + ru_seed2 = tmp & 0x7FFF; + + /* Determine the LCG we use */ + tmp = arc4random(); + ru_b = (tmp & 0xfffe) | 1; + ru_a = pmod(RU_AGEN, (tmp >> 16) & 0xfffe, RU_M); + while (ru_b % 3 == 0) + ru_b += 2; + + j = arc4random_uniform(RU_N); + + /* + * Do a fast gcd(j,RU_N-1), so we can find a j with + * gcd(j, RU_N-1) == 1, giving a new generator for + * RU_GEN^j mod RU_N + */ + + while (noprime) { + for (i = 0; i < PFAC_N; i++) + if (j % pfacts[i] == 0) + break; + + if (i >= PFAC_N) + noprime = 0; + else + j = (j + 1) % RU_N; + } + + ru_g = pmod(RU_GEN, j, RU_N); + ru_counter = 0; + + /* Initialise PRF for Luby-Rackoff permutation */ + if (ru_prf == NULL) + ru_prf = malloc(sizeof(*ru_prf)); + if (ru_prf != NULL) + arc4random_buf(ru_prf, sizeof(*ru_prf)); + + gettimeofday(&tv, NULL); + ru_reseed = tv.tv_sec + RU_OUT; + ru_msb = ru_msb == 0x8000 ? 0 : 0x8000; +} + +u_int +res_randomid(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + if (ru_counter >= RU_MAX || tv.tv_sec > ru_reseed) + res_initid(); + + /* Linear Congruential Generator */ + ru_x = (ru_a * ru_x + ru_b) % RU_M; + ru_counter++; + + return permute15(ru_seed ^ pmod(ru_g, ru_seed2 + ru_x, RU_N)) | ru_msb; +} + +#if 0 +int +main(int argc, char **argv) +{ + int i, n; + u_int16_t wert; + + res_initid(); + + printf("Generator: %u\n", ru_g); + printf("Seed: %u\n", ru_seed); + printf("Reseed at %ld\n", ru_reseed); + printf("Ru_X: %u\n", ru_x); + printf("Ru_A: %u\n", ru_a); + printf("Ru_B: %u\n", ru_b); + + n = argc > 1 ? atoi(argv[1]) : 60001; + for (i=0;i<n;i++) { + wert = res_randomid(); + printf("%u\n", wert); + } + return 0; +} +#endif + diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h index 60d05c998bb..68546616b91 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.199 2010/11/28 13:56:43 gilles Exp $ */ +/* $OpenBSD: smtpd.h,v 1.200 2010/11/29 15:25:56 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -188,8 +188,8 @@ enum imsg_type { IMSG_SMTP_PAUSE, IMSG_SMTP_RESUME, - IMSG_DNS_A, - IMSG_DNS_A_END, + IMSG_DNS_HOST, + IMSG_DNS_HOST_END, IMSG_DNS_MX, IMSG_DNS_PTR }; @@ -627,6 +627,7 @@ struct smtpd { SPLAY_HEAD(batchtree, batch) batch_queue; SPLAY_HEAD(childtree, child) children; SPLAY_HEAD(lkatree, lkasession) lka_sessions; + SPLAY_HEAD(dnstree, dnssession) dns_sessions; SPLAY_HEAD(mtatree, mta_session) mta_sessions; LIST_HEAD(mdalist, mda_session) mda_sessions; @@ -738,6 +739,8 @@ struct dns { char host[MAXHOSTNAMELEN]; int port; int error; + int type; + struct imsgev *asker; struct sockaddr_storage ss; struct smtpd *env; struct dns *next; @@ -790,6 +793,23 @@ struct lkasession { struct submit_status ss; }; +struct mx { + char host[MAXHOSTNAMELEN]; + int prio; + struct mx *next; +}; + +struct dnssession { + SPLAY_ENTRY(dnssession) nodes; + u_int64_t id; + struct dns query; + struct event ev; + struct asr_query *aq; + struct mx mxarray[MAX_MX_COUNT]; + size_t mxarraysz; + struct mx *mxcurrent; +}; + enum mta_state { MTA_INVALID_STATE, MTA_INIT, @@ -803,10 +823,11 @@ enum mta_state { }; /* mta session flags */ -#define MTA_FORCE_ANYSSL 0x1 -#define MTA_FORCE_SMTPS 0x2 -#define MTA_ALLOW_PLAIN 0x4 -#define MTA_USE_AUTH 0x8 +#define MTA_FORCE_ANYSSL 0x01 +#define MTA_FORCE_SMTPS 0x02 +#define MTA_ALLOW_PLAIN 0x04 +#define MTA_USE_AUTH 0x08 +#define MTA_FORCE_MX 0x10 struct mta_relay { TAILQ_ENTRY(mta_relay) entry; @@ -898,7 +919,7 @@ int session_socket_error(int); /* dns.c */ -void dns_query_a(struct smtpd *, char *, int, u_int64_t); +void dns_query_host(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); @@ -927,7 +948,9 @@ int forwards_get(int, struct expandtree *); /* lka.c */ pid_t lka(struct smtpd *); int lkasession_cmp(struct lkasession *, struct lkasession *); +int dnssession_cmp(struct dnssession *, struct dnssession *); SPLAY_PROTOTYPE(lkatree, lkasession, nodes, lkasession_cmp); +SPLAY_PROTOTYPE(dnstree, dnssession, nodes, dnssession_cmp); /* map.c */ @@ -1069,6 +1092,7 @@ int recipient_to_path(struct path *, char *); int valid_localpart(char *); int valid_domainpart(char *); char *ss_to_text(struct sockaddr_storage *); +char *ss_to_ptr(struct sockaddr_storage *); int valid_message_id(char *); int valid_message_uid(char *); char *time_to_text(time_t); diff --git a/usr.sbin/smtpd/smtpd/Makefile b/usr.sbin/smtpd/smtpd/Makefile index 6037921f6df..e78cef4315d 100644 --- a/usr.sbin/smtpd/smtpd/Makefile +++ b/usr.sbin/smtpd/smtpd/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.21 2010/10/09 22:05:36 gilles Exp $ +# $OpenBSD: Makefile,v 1.22 2010/11/29 15:25:56 gilles Exp $ PROG= smtpd SRCS= aliases.c authenticate.c bounce.c client.c \ @@ -6,7 +6,8 @@ SRCS= aliases.c authenticate.c bounce.c client.c \ lka.c log.c map.c map_backend.c map_parser.c mda.c \ mfa.c mta.c parse.y queue.c queue_shared.c ruleset.c \ runner.c smtp.c smtp_session.c smtpd.c ssl.c \ - ssl_privsep.c util.c + ssl_privsep.c util.c asr.c print.c pack.c dname.c \ + res_random.c sockaddr.c MAN= smtpd.8 smtpd.conf.5 BINDIR= /usr/sbin diff --git a/usr.sbin/smtpd/sockaddr.c b/usr.sbin/smtpd/sockaddr.c new file mode 100644 index 00000000000..a7c5761b9b0 --- /dev/null +++ b/usr.sbin/smtpd/sockaddr.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2010 Eric Faurot <eric@faurot.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> + +#include "dnsutil.h" + +int +sockaddr_from_rr(struct sockaddr *sa, struct rr *rr) +{ + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + if (rr->rr_class != C_IN) + return (-1); + + switch (rr->rr_type) { + case T_A: + sin = (struct sockaddr_in*)sa; + memset(sin, 0, sizeof *sin); + sin->sin_len = sizeof *sin; + sin->sin_family = PF_INET; + sin->sin_addr = rr->rr.in_a.addr; + sin->sin_port = 0; + return (0); + case T_AAAA: + sin6 = (struct sockaddr_in6*)sa; + memset(sin6, 0, sizeof *sin6); + sin6->sin6_len = sizeof *sin6; + sin6->sin6_family = PF_INET6; + sin6->sin6_addr = rr->rr.in_aaaa.addr6; + sin6->sin6_port = 0; + return (0); + + default: + break; + } + + return (-1); +} + +int +sockaddr_from_str(struct sockaddr *sa, int family, const char *str) +{ + struct in_addr ina; + struct in6_addr in6a; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + switch (family) { + case PF_UNSPEC: + if (sockaddr_from_str(sa, PF_INET, str) == 0) + return (0); + return sockaddr_from_str(sa, PF_INET6, str); + + case PF_INET: + if (inet_pton(PF_INET, str, &ina) != 1) + return (-1); + + sin = (struct sockaddr_in *)sa; + memset(sin, 0, sizeof *sin); + sin->sin_len = sizeof(struct sockaddr_in); + sin->sin_family = PF_INET; + sin->sin_addr.s_addr = ina.s_addr; + return (0); + + case PF_INET6: + if (inet_pton(PF_INET6, str, &in6a) != 1) + return (-1); + + sin6 = (struct sockaddr_in6 *)sa; + memset(sin6, 0, sizeof *sin6); + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_family = PF_INET6; + sin6->sin6_addr = in6a; + return (0); + + default: + break; + } + + return (-1); +} + +void +sockaddr_set_port(struct sockaddr *sa, int portno) +{ + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + switch (sa->sa_family) { + case PF_INET: + sin = (struct sockaddr_in *)sa; + sin->sin_port = htons(portno); + break; + case PF_INET6: + sin6 = (struct sockaddr_in6 *)sa; + sin6->sin6_port = htons(portno); + break; + } +} + +int +sockaddr_connect(const struct sockaddr *sa, int socktype) +{ + int errno_save, flags, sock; + + if ((sock = socket(sa->sa_family, socktype, 0)) == -1) + goto fail; + + if ((flags = fcntl(sock, F_GETFL, 0)) == -1) + goto fail; + + flags |= O_NONBLOCK; + + if ((flags = fcntl(sock, F_SETFL, flags)) == -1) + goto fail; + + if (connect(sock, sa, sa->sa_len) == -1) { + if (errno == EINPROGRESS) + return (sock); + goto fail; + } + + return (sock); + + fail: + + if (sock != -1) { + errno_save = errno; + close(sock); + errno = errno_save; + } + + return (-1); +} + +int +sockaddr_listen(const struct sockaddr *sa, int socktype) +{ + int errno_save, sock; + + if ((sock = socket(sa->sa_family, socktype, 0)) == -1) + return (-1); + + if (bind(sock, sa, sa->sa_len) == -1) { + errno_save = errno; + close(sock); + errno = errno_save; + return (-1); + } + + return (sock); +} diff --git a/usr.sbin/smtpd/util.c b/usr.sbin/smtpd/util.c index 1021848f1b5..4d06eb3d5cc 100644 --- a/usr.sbin/smtpd/util.c +++ b/usr.sbin/smtpd/util.c @@ -1,4 +1,4 @@ -/* $OpenBSD: util.c,v 1.37 2010/11/28 13:56:43 gilles Exp $ */ +/* $OpenBSD: util.c,v 1.38 2010/11/29 15:25:56 gilles Exp $ */ /* * Copyright (c) 2000,2001 Markus Friedl. All rights reserved. @@ -26,6 +26,8 @@ #include <sys/stat.h> #include <sys/resource.h> +#include <netinet/in.h> + #include <ctype.h> #include <err.h> #include <errno.h> @@ -45,6 +47,9 @@ #include "smtpd.h" #include "log.h" +const char *log_in6addr(const struct in6_addr *); +const char *log_sockaddr(struct sockaddr *); + int bsnprintf(char *str, size_t size, const char *format, ...) { @@ -190,18 +195,101 @@ ss_to_text(struct sockaddr_storage *ss) buf[0] = '\0'; p = buf; + if (ss->ss_family == PF_INET) { + in_addr_t addr; + + addr = ((struct sockaddr_in *)ss)->sin_addr.s_addr; + bsnprintf(p, NI_MAXHOST, + "%d.%d.%d.%d", + addr & 0xff, + (addr >> 8) & 0xff, + (addr >> 16) & 0xff, + (addr >> 24) & 0xff); + } + if (ss->ss_family == PF_INET6) { + struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)ss; + struct in6_addr *in6_addr; + strlcpy(buf, "IPv6:", sizeof(buf)); p = buf + 5; + in6_addr = &in6->sin6_addr; + bsnprintf(p, NI_MAXHOST, "%s", log_in6addr(in6_addr)); } - if (getnameinfo((struct sockaddr *)ss, ss->ss_len, p, - NI_MAXHOST, NULL, 0, NI_NUMERICHOST)) - fatalx("ss_to_text: getnameinfo"); - return (buf); } +char * +ss_to_ptr(struct sockaddr_storage *ss) +{ + static char buffer[1024]; + + /* we need to construct a PTR query */ + switch (ss->ss_family) { + case AF_INET: { + in_addr_t addr; + + addr = ((struct sockaddr_in *)ss)->sin_addr.s_addr; + + bsnprintf(buffer, sizeof (buffer), + "%d.%d.%d.%d.in-addr.arpa", + (addr >> 24) & 0xff, + (addr >> 16) & 0xff, + (addr >> 8) & 0xff, + addr & 0xff); + break; + } + case AF_INET6: { + struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)ss; + struct in6_addr *in6_addr; + + in6_addr = &in6->sin6_addr; + bsnprintf(buffer, sizeof (buffer), + "%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d." + "%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d.%d." + "ip6.arpa", + in6_addr->s6_addr[15] & 0xf, + (in6_addr->s6_addr[15] >> 4) & 0xf, + in6_addr->s6_addr[14] & 0xf, + (in6_addr->s6_addr[14] >> 4) & 0xf, + in6_addr->s6_addr[13] & 0xf, + (in6_addr->s6_addr[13] >> 4) & 0xf, + in6_addr->s6_addr[12] & 0xf, + (in6_addr->s6_addr[12] >> 4) & 0xf, + in6_addr->s6_addr[11] & 0xf, + (in6_addr->s6_addr[11] >> 4) & 0xf, + in6_addr->s6_addr[10] & 0xf, + (in6_addr->s6_addr[10] >> 4) & 0xf, + in6_addr->s6_addr[9] & 0xf, + (in6_addr->s6_addr[9] >> 4) & 0xf, + in6_addr->s6_addr[8] & 0xf, + (in6_addr->s6_addr[8] >> 4) & 0xf, + in6_addr->s6_addr[7] & 0xf, + (in6_addr->s6_addr[7] >> 4) & 0xf, + in6_addr->s6_addr[6] & 0xf, + (in6_addr->s6_addr[6] >> 4) & 0xf, + in6_addr->s6_addr[5] & 0xf, + (in6_addr->s6_addr[5] >> 4) & 0xf, + in6_addr->s6_addr[4] & 0xf, + (in6_addr->s6_addr[4] >> 4) & 0xf, + in6_addr->s6_addr[3] & 0xf, + (in6_addr->s6_addr[3] >> 4) & 0xf, + in6_addr->s6_addr[2] & 0xf, + (in6_addr->s6_addr[2] >> 4) & 0xf, + in6_addr->s6_addr[1] & 0xf, + (in6_addr->s6_addr[1] >> 4) & 0xf, + in6_addr->s6_addr[0] & 0xf, + (in6_addr->s6_addr[0] >> 4) & 0xf); + break; + } + default: + fatalx("dns_query_ptr"); + } + + return buffer; +} + int valid_message_id(char *mid) { @@ -519,3 +607,38 @@ session_socket_error(int fd) return (error); } + +const char * +log_in6addr(const struct in6_addr *addr) +{ + struct sockaddr_in6 sa_in6; + u_int16_t tmp16; + + bzero(&sa_in6, sizeof(sa_in6)); + sa_in6.sin6_len = sizeof(sa_in6); + sa_in6.sin6_family = AF_INET6; + memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr)); + + /* XXX thanks, KAME, for this ugliness... adopted from route/show.c */ + if (IN6_IS_ADDR_LINKLOCAL(&sa_in6.sin6_addr) || + IN6_IS_ADDR_MC_LINKLOCAL(&sa_in6.sin6_addr)) { + memcpy(&tmp16, &sa_in6.sin6_addr.s6_addr[2], sizeof(tmp16)); + sa_in6.sin6_scope_id = ntohs(tmp16); + sa_in6.sin6_addr.s6_addr[2] = 0; + sa_in6.sin6_addr.s6_addr[3] = 0; + } + + return (log_sockaddr((struct sockaddr *)&sa_in6)); +} + +const char * +log_sockaddr(struct sockaddr *sa) +{ + static char buf[NI_MAXHOST]; + + if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0, + NI_NUMERICHOST)) + return ("(unknown)"); + else + return (buf); +} |