summaryrefslogtreecommitdiff
path: root/usr.sbin/smtpd
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/smtpd')
-rw-r--r--usr.sbin/smtpd/asr.c2233
-rw-r--r--usr.sbin/smtpd/asr.h82
-rw-r--r--usr.sbin/smtpd/dname.c219
-rw-r--r--usr.sbin/smtpd/dns.c723
-rw-r--r--usr.sbin/smtpd/dnsdefs.h165
-rw-r--r--usr.sbin/smtpd/dnsutil.h189
-rw-r--r--usr.sbin/smtpd/lka.c4
-rw-r--r--usr.sbin/smtpd/mta.c15
-rw-r--r--usr.sbin/smtpd/pack.c469
-rw-r--r--usr.sbin/smtpd/print.c395
-rw-r--r--usr.sbin/smtpd/res_random.c270
-rw-r--r--usr.sbin/smtpd/smtpd.h40
-rw-r--r--usr.sbin/smtpd/smtpd/Makefile5
-rw-r--r--usr.sbin/smtpd/sockaddr.c176
-rw-r--r--usr.sbin/smtpd/util.c133
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);
+}