summaryrefslogtreecommitdiffstats
path: root/src/dns
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:46:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:46:30 +0000
commitb5896ba9f6047e7031e2bdee0622d543e11a6734 (patch)
treefd7b460593a2fee1be579bec5697e6d887ea3421 /src/dns
parentInitial commit. (diff)
downloadpostfix-upstream.tar.xz
postfix-upstream.zip
Adding upstream version 3.4.23.upstream/3.4.23upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/dns')
l---------src/dns/.indent.pro1
-rw-r--r--src/dns/.printfck25
-rw-r--r--src/dns/Makefile.in416
-rw-r--r--src/dns/dns.h345
-rw-r--r--src/dns/dns_lookup.c1193
-rw-r--r--src/dns/dns_rr.c347
-rw-r--r--src/dns/dns_rr_eq_sa.c157
-rw-r--r--src/dns/dns_rr_eq_sa.in4
-rw-r--r--src/dns/dns_rr_eq_sa.ref24
-rw-r--r--src/dns/dns_rr_filter.c150
-rw-r--r--src/dns/dns_rr_to_pa.c113
-rw-r--r--src/dns/dns_rr_to_pa.in2
-rw-r--r--src/dns/dns_rr_to_pa.ref2
-rw-r--r--src/dns/dns_rr_to_sa.c163
-rw-r--r--src/dns/dns_rr_to_sa.in2
-rw-r--r--src/dns/dns_rr_to_sa.ref2
-rw-r--r--src/dns/dns_sa_to_rr.c138
-rw-r--r--src/dns/dns_sa_to_rr.in1
-rw-r--r--src/dns/dns_sa_to_rr.ref2
-rw-r--r--src/dns/dns_sec.c144
-rw-r--r--src/dns/dns_str_resflags.c124
-rw-r--r--src/dns/dns_strerror.c69
-rw-r--r--src/dns/dns_strrecord.c117
-rw-r--r--src/dns/dns_strtype.c211
-rw-r--r--src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref15
-rw-r--r--src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref9
-rw-r--r--src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref15
-rw-r--r--src/dns/error.ref13
-rw-r--r--src/dns/error.reg1
-rw-r--r--src/dns/mxonly_test.ref11
-rw-r--r--src/dns/no-a.ref13
-rw-r--r--src/dns/no-a.reg1
-rw-r--r--src/dns/no-aaaa.ref13
-rw-r--r--src/dns/no-aaaa.reg1
-rw-r--r--src/dns/no-mx.ref15
-rw-r--r--src/dns/no-mx.reg1
-rw-r--r--src/dns/no-txt.reg1
-rw-r--r--src/dns/nullmx_test.ref8
-rw-r--r--src/dns/nxdomain_test.ref5
-rw-r--r--src/dns/test_dns_lookup.c129
40 files changed, 4003 insertions, 0 deletions
diff --git a/src/dns/.indent.pro b/src/dns/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/dns/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/dns/.printfck b/src/dns/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/dns/.printfck
@@ -0,0 +1,25 @@
+been_here_xt 2 0
+bounce_append 5 0
+cleanup_out_format 1 0
+defer_append 5 0
+mail_command 1 0
+mail_print 1 0
+msg_error 0 0
+msg_fatal 0 0
+msg_info 0 0
+msg_panic 0 0
+msg_warn 0 0
+opened 4 0
+post_mail_fprintf 1 0
+qmgr_message_bounce 2 0
+rec_fprintf 2 0
+sent 4 0
+smtp_cmd 1 0
+smtp_mesg_fail 2 0
+smtp_printf 1 0
+smtp_rcpt_fail 3 0
+smtp_site_fail 2 0
+udp_syslog 1 0
+vstream_fprintf 1 0
+vstream_printf 0 0
+vstring_sprintf 1 0
diff --git a/src/dns/Makefile.in b/src/dns/Makefile.in
new file mode 100644
index 0000000..0af33f4
--- /dev/null
+++ b/src/dns/Makefile.in
@@ -0,0 +1,416 @@
+SHELL = /bin/sh
+SRCS = dns_lookup.c dns_rr.c dns_strerror.c dns_strtype.c dns_rr_to_pa.c \
+ dns_sa_to_rr.c dns_rr_eq_sa.c dns_rr_to_sa.c dns_strrecord.c \
+ dns_rr_filter.c dns_str_resflags.c dns_sec.c
+OBJS = dns_lookup.o dns_rr.o dns_strerror.o dns_strtype.o dns_rr_to_pa.o \
+ dns_sa_to_rr.o dns_rr_eq_sa.o dns_rr_to_sa.o dns_strrecord.o \
+ dns_rr_filter.o dns_str_resflags.o dns_sec.o
+HDRS = dns.h
+TESTSRC = test_dns_lookup.c test_alias_token.c
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+INCL =
+LIB = lib$(LIB_PREFIX)dns$(LIB_SUFFIX)
+TESTPROG= test_dns_lookup dns_rr_to_pa dns_rr_to_sa dns_sa_to_rr dns_rr_eq_sa
+LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+LIB_DIR = ../../lib
+INC_DIR = ../../include
+
+.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
+
+all: $(LIB)
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+tests: test dns_rr_to_pa_test dns_rr_to_sa_test dns_sa_to_rr_test \
+ dns_rr_eq_sa_test no-a-test no-aaaa-test no-mx-test \
+ error-filter-test nullmx_test nxdomain_test mxonly_test \
+ dnsbl_tests
+
+dnsbl_tests: \
+ dnsbl_ttl_127.0.0.2_bind_plain_test \
+ dnsbl_ttl_127.0.0.2_bind_ncache_test \
+ dnsbl_ttl_127.0.0.2_priv_plain_test \
+ dnsbl_ttl_127.0.0.2_priv_ncache_test \
+ dnsbl_ttl_127.0.0.1_bind_plain_test \
+ dnsbl_ttl_127.0.0.1_bind_ncache_test \
+ dnsbl_ttl_127.0.0.1_priv_plain_test \
+ dnsbl_ttl_127.0.0.1_priv_ncache_test
+
+DNSBL_NEXIST_REPLY_FIX = \
+ sed -e 's/ [0-9][0-9]* IN SOA / TTL IN SOA /' \
+ -e 's/len=[0-9][0-9]* /len=LEN /' \
+ -e 's/nscount=[1-9][0-9]*/nscount=N/' \
+ -e 's/ [0-9]* [0-9]* [0-9]* [0-9]* [0-9]*/ D D D D D/'
+
+DNSBL_EXIST_REPLY_FIX = \
+ sed -e 's/ [0-9][0-9]* IN A / TTL IN A /' \
+ -e 's/len=[0-9][0-9]* /len=LEN /' \
+ -e 's/ancount=[1-9][0-9]*/ancount=N/' \
+ -e 's/nscount=[1-9][0-9]*/nscount=N/' \
+ -e 's/ [0-9]* [0-9]* [0-9]* [0-9]* [0-9]*/ D D D D D/' \
+ -e 's/127.0.0.[0-9]*$$/127.0.0.D/' \
+ | uniq
+
+root_tests:
+
+$(LIB): $(OBJS)
+ $(AR) $(ARFL) $(LIB) $?
+ $(RANLIB) $(LIB)
+ $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(OBJS) $(SHLIB_SYSLIBS)
+
+$(LIB_DIR)/$(LIB): $(LIB)
+ cp $(LIB) $(LIB_DIR)
+ $(RANLIB) $(LIB_DIR)/$(LIB)
+
+update: $(LIB_DIR)/$(LIB) $(HDRS)
+ -for i in $(HDRS); \
+ do \
+ cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \
+ done
+ cd $(INC_DIR); chmod 644 $(HDRS)
+
+test_dns_lookup: test_dns_lookup.c all $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+dns_rr_to_pa: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+dns_rr_to_sa: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+dns_sa_to_rr: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+dns_rr_eq_sa: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+dns_rr_to_pa_test: dns_rr_to_pa dns_rr_to_pa.in dns_rr_to_pa.ref
+ $(SHLIB_ENV) ./dns_rr_to_pa `cat dns_rr_to_pa.in` >dns_rr_to_pa.tmp
+ diff dns_rr_to_pa.ref dns_rr_to_pa.tmp
+ rm -f dns_rr_to_pa.tmp
+
+dns_rr_to_sa_test: dns_rr_to_sa dns_rr_to_sa.in dns_rr_to_sa.ref
+ $(SHLIB_ENV) ./dns_rr_to_sa `cat dns_rr_to_sa.in` >dns_rr_to_sa.tmp
+ diff dns_rr_to_sa.ref dns_rr_to_sa.tmp
+ rm -f dns_rr_to_sa.tmp
+
+dns_sa_to_rr_test: dns_sa_to_rr dns_sa_to_rr.in dns_sa_to_rr.ref
+ $(SHLIB_ENV) ./dns_sa_to_rr `cat dns_sa_to_rr.in` >dns_sa_to_rr.tmp
+ diff dns_sa_to_rr.ref dns_sa_to_rr.tmp
+ rm -f dns_sa_to_rr.tmp
+
+dns_rr_eq_sa_test: dns_rr_eq_sa dns_rr_eq_sa.in dns_rr_eq_sa.ref
+ $(SHLIB_ENV) ./dns_rr_eq_sa `cat dns_rr_eq_sa.in` >dns_rr_eq_sa.tmp
+ diff dns_rr_eq_sa.ref dns_rr_eq_sa.tmp
+ rm -f dns_rr_eq_sa.tmp
+
+no-a-test: no-a.reg test_dns_lookup no-a.ref
+ $(SHLIB_ENV) ./test_dns_lookup -f regexp:no-a.reg a,aaaa spike.porcupine.org >test_dns_lookup.tmp 2>&1
+ diff no-a.ref test_dns_lookup.tmp
+ rm -f test_dns_lookup.tmp
+
+no-aaaa-test: no-aaaa.reg test_dns_lookup no-aaaa.ref
+ $(SHLIB_ENV) ./test_dns_lookup -f regexp:no-aaaa.reg a,aaaa spike.porcupine.org >test_dns_lookup.tmp 2>&1
+ diff no-aaaa.ref test_dns_lookup.tmp
+ rm -f test_dns_lookup.tmp
+
+no-mx-test: no-mx.reg test_dns_lookup no-mx.ref
+ set -e; $(SHLIB_ENV) ./test_dns_lookup -f regexp:no-mx.reg mx porcupine.org 2>&1 | sort >test_dns_lookup.tmp || true
+ diff no-mx.ref test_dns_lookup.tmp
+ rm -f test_dns_lookup.tmp
+
+error-filter-test: error.reg test_dns_lookup error.ref
+ set -e; $(SHLIB_ENV) ./test_dns_lookup -f regexp:error.reg a,aaaa spike.porcupine.org >test_dns_lookup.tmp 2>&1 || true
+ diff error.ref test_dns_lookup.tmp
+ rm -f test_dns_lookup.tmp
+
+nullmx_test: test_dns_lookup nullmx_test.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup mx,a nullmx.porcupine.org; \
+ ) >nullmx_test.tmp 2>&1 || exit 0
+ diff nullmx_test.ref nullmx_test.tmp
+ rm -f nullmx_test.tmp
+
+nxdomain_test: test_dns_lookup nxdomain_test.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup mx,a nxdomain.porcupine.org; \
+ ) >nxdomain_test.tmp 2>&1 || exit 0
+ diff nxdomain_test.ref nxdomain_test.tmp
+ rm -f nxdomain_test.tmp
+
+mxonly_test: test_dns_lookup mxonly_test.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup mx,a porcupine.org | sort; \
+ ) >mxonly_test.tmp 2>&1 || exit 0
+ diff mxonly_test.ref mxonly_test.tmp
+ rm -f mxonly_test.tmp
+
+# Non-existent record, libbind API, RFC 2308 disabled.
+
+dnsbl_ttl_127.0.0.1_bind_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_plain.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup a 1.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup a 1.0.0.127.b.barracudacentral.org; \
+ $(SHLIB_ENV) ./test_dns_lookup a 1.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_bind_plain.tmp
+ diff dnsbl_ttl_127.0.0.1_bind_plain.ref dnsbl_ttl_127.0.0.1_bind_plain.tmp
+ rm -f dnsbl_ttl_127.0.0.1_bind_plain.tmp
+
+# Non-existent record, private API, RFC 2308 disabled.
+
+dnsbl_ttl_127.0.0.1_priv_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_plain.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup -p a 1.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -p a 1.0.0.127.b.barracudacentral.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -p a 1.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_priv_plain.tmp
+ diff dnsbl_ttl_127.0.0.1_bind_plain.ref dnsbl_ttl_127.0.0.1_priv_plain.tmp
+ rm -f dnsbl_ttl_127.0.0.1_priv_plain.tmp
+
+# Non-existent record, libbind API, RFC 2308 enabled.
+
+dnsbl_ttl_127.0.0.1_bind_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_ncache.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup -n a 1.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -n a 1.0.0.127.b.barracudacentral.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -n a 1.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_bind_ncache.tmp
+ diff dnsbl_ttl_127.0.0.1_bind_ncache.ref dnsbl_ttl_127.0.0.1_bind_ncache.tmp
+ rm -f dnsbl_ttl_127.0.0.1_bind_ncache.tmp
+
+# Non-existent record, private API, RFC 2308 enabled.
+
+dnsbl_ttl_127.0.0.1_priv_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_ncache.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup -n -p a 1.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -n -p a 1.0.0.127.b.barracudacentral.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -n -p a 1.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_priv_ncache.tmp
+ diff dnsbl_ttl_127.0.0.1_bind_ncache.ref dnsbl_ttl_127.0.0.1_priv_ncache.tmp
+ rm -f dnsbl_ttl_127.0.0.1_priv_ncache.tmp
+
+# Existing record, libbind API, RFC 2308 disabled.
+
+dnsbl_ttl_127.0.0.2_bind_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup a 2.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup a 2.0.0.127.b.barracudacentral.org; \
+ $(SHLIB_ENV) ./test_dns_lookup a 2.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_bind_plain.tmp
+ diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_bind_plain.tmp
+ rm -f dnsbl_ttl_127.0.0.2_bind_plain.tmp
+
+# Existing record, private API, RFC 2308 disabled.
+
+dnsbl_ttl_127.0.0.2_priv_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup -p a 2.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -p a 2.0.0.127.b.barracudacentral.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -p a 2.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_priv_plain.tmp
+ diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_priv_plain.tmp
+ rm -f dnsbl_ttl_127.0.0.2_priv_plain.tmp
+
+# Existing record, libbind API, RFC 2308 enabled.
+
+dnsbl_ttl_127.0.0.2_bind_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup -n a 2.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -n a 2.0.0.127.b.barracudacentral.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -n a 2.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_bind_ncache.tmp
+ diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_bind_ncache.tmp
+ rm -f dnsbl_ttl_127.0.0.2_bind_ncache.tmp
+
+# Existing record, private API, RFC 2308 enabled.
+
+dnsbl_ttl_127.0.0.2_priv_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup -n -p a 2.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -n -p a 2.0.0.127.b.barracudacentral.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -n -p a 2.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_priv_ncache.tmp
+ diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_priv_ncache.tmp
+ rm -f dnsbl_ttl_127.0.0.2_priv_ncache.tmp
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir printfck
+ cp *.h printfck
+ sed '1,/^# do not edit/!d' Makefile >printfck/Makefile
+ set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done
+ cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o`
+
+lint:
+ lint $(DEFS) $(SRCS) $(LINTFIX)
+
+clean:
+ rm -f *.o $(LIB) *core $(TESTPROG) junk
+ rm -rf printfck
+
+tidy: clean
+
+depend: $(MAKES)
+ (sed '1,/^# do not edit/!d' Makefile.in; \
+ set -e; for i in [a-z][a-z0-9]*.c; do \
+ $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \
+ -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \
+ -e 's/o: \.\//o: /' -e p -e '}' ; \
+ done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in
+ @$(EXPORT) make -f Makefile.in Makefile 1>&2
+
+# do not edit below this line - it is generated by 'make depend'
+dns_lookup.o: ../../include/argv.h
+dns_lookup.o: ../../include/check_arg.h
+dns_lookup.o: ../../include/dict.h
+dns_lookup.o: ../../include/mail_params.h
+dns_lookup.o: ../../include/maps.h
+dns_lookup.o: ../../include/msg.h
+dns_lookup.o: ../../include/myaddrinfo.h
+dns_lookup.o: ../../include/myflock.h
+dns_lookup.o: ../../include/mymalloc.h
+dns_lookup.o: ../../include/sock_addr.h
+dns_lookup.o: ../../include/stringops.h
+dns_lookup.o: ../../include/sys_defs.h
+dns_lookup.o: ../../include/valid_hostname.h
+dns_lookup.o: ../../include/vbuf.h
+dns_lookup.o: ../../include/vstream.h
+dns_lookup.o: ../../include/vstring.h
+dns_lookup.o: dns.h
+dns_lookup.o: dns_lookup.c
+dns_rr.o: ../../include/check_arg.h
+dns_rr.o: ../../include/msg.h
+dns_rr.o: ../../include/myaddrinfo.h
+dns_rr.o: ../../include/mymalloc.h
+dns_rr.o: ../../include/myrand.h
+dns_rr.o: ../../include/sock_addr.h
+dns_rr.o: ../../include/sys_defs.h
+dns_rr.o: ../../include/vbuf.h
+dns_rr.o: ../../include/vstring.h
+dns_rr.o: dns.h
+dns_rr.o: dns_rr.c
+dns_rr_eq_sa.o: ../../include/check_arg.h
+dns_rr_eq_sa.o: ../../include/msg.h
+dns_rr_eq_sa.o: ../../include/myaddrinfo.h
+dns_rr_eq_sa.o: ../../include/sock_addr.h
+dns_rr_eq_sa.o: ../../include/sys_defs.h
+dns_rr_eq_sa.o: ../../include/vbuf.h
+dns_rr_eq_sa.o: ../../include/vstring.h
+dns_rr_eq_sa.o: dns.h
+dns_rr_eq_sa.o: dns_rr_eq_sa.c
+dns_rr_filter.o: ../../include/argv.h
+dns_rr_filter.o: ../../include/check_arg.h
+dns_rr_filter.o: ../../include/dict.h
+dns_rr_filter.o: ../../include/maps.h
+dns_rr_filter.o: ../../include/msg.h
+dns_rr_filter.o: ../../include/myaddrinfo.h
+dns_rr_filter.o: ../../include/myflock.h
+dns_rr_filter.o: ../../include/sock_addr.h
+dns_rr_filter.o: ../../include/sys_defs.h
+dns_rr_filter.o: ../../include/vbuf.h
+dns_rr_filter.o: ../../include/vstream.h
+dns_rr_filter.o: ../../include/vstring.h
+dns_rr_filter.o: dns.h
+dns_rr_filter.o: dns_rr_filter.c
+dns_rr_to_pa.o: ../../include/check_arg.h
+dns_rr_to_pa.o: ../../include/msg.h
+dns_rr_to_pa.o: ../../include/myaddrinfo.h
+dns_rr_to_pa.o: ../../include/sock_addr.h
+dns_rr_to_pa.o: ../../include/sys_defs.h
+dns_rr_to_pa.o: ../../include/vbuf.h
+dns_rr_to_pa.o: ../../include/vstring.h
+dns_rr_to_pa.o: dns.h
+dns_rr_to_pa.o: dns_rr_to_pa.c
+dns_rr_to_sa.o: ../../include/check_arg.h
+dns_rr_to_sa.o: ../../include/msg.h
+dns_rr_to_sa.o: ../../include/myaddrinfo.h
+dns_rr_to_sa.o: ../../include/sock_addr.h
+dns_rr_to_sa.o: ../../include/sys_defs.h
+dns_rr_to_sa.o: ../../include/vbuf.h
+dns_rr_to_sa.o: ../../include/vstring.h
+dns_rr_to_sa.o: dns.h
+dns_rr_to_sa.o: dns_rr_to_sa.c
+dns_sa_to_rr.o: ../../include/check_arg.h
+dns_sa_to_rr.o: ../../include/msg.h
+dns_sa_to_rr.o: ../../include/myaddrinfo.h
+dns_sa_to_rr.o: ../../include/sock_addr.h
+dns_sa_to_rr.o: ../../include/sys_defs.h
+dns_sa_to_rr.o: ../../include/vbuf.h
+dns_sa_to_rr.o: ../../include/vstring.h
+dns_sa_to_rr.o: dns.h
+dns_sa_to_rr.o: dns_sa_to_rr.c
+dns_sec.o: ../../include/check_arg.h
+dns_sec.o: ../../include/mail_params.h
+dns_sec.o: ../../include/msg.h
+dns_sec.o: ../../include/myaddrinfo.h
+dns_sec.o: ../../include/mymalloc.h
+dns_sec.o: ../../include/sock_addr.h
+dns_sec.o: ../../include/split_at.h
+dns_sec.o: ../../include/sys_defs.h
+dns_sec.o: ../../include/vbuf.h
+dns_sec.o: ../../include/vstring.h
+dns_sec.o: dns.h
+dns_sec.o: dns_sec.c
+dns_str_resflags.o: ../../include/check_arg.h
+dns_str_resflags.o: ../../include/myaddrinfo.h
+dns_str_resflags.o: ../../include/name_mask.h
+dns_str_resflags.o: ../../include/sock_addr.h
+dns_str_resflags.o: ../../include/sys_defs.h
+dns_str_resflags.o: ../../include/vbuf.h
+dns_str_resflags.o: ../../include/vstring.h
+dns_str_resflags.o: dns.h
+dns_str_resflags.o: dns_str_resflags.c
+dns_strerror.o: ../../include/check_arg.h
+dns_strerror.o: ../../include/myaddrinfo.h
+dns_strerror.o: ../../include/sock_addr.h
+dns_strerror.o: ../../include/sys_defs.h
+dns_strerror.o: ../../include/vbuf.h
+dns_strerror.o: ../../include/vstring.h
+dns_strerror.o: dns.h
+dns_strerror.o: dns_strerror.c
+dns_strrecord.o: ../../include/check_arg.h
+dns_strrecord.o: ../../include/msg.h
+dns_strrecord.o: ../../include/myaddrinfo.h
+dns_strrecord.o: ../../include/sock_addr.h
+dns_strrecord.o: ../../include/sys_defs.h
+dns_strrecord.o: ../../include/vbuf.h
+dns_strrecord.o: ../../include/vstring.h
+dns_strrecord.o: dns.h
+dns_strrecord.o: dns_strrecord.c
+dns_strtype.o: ../../include/check_arg.h
+dns_strtype.o: ../../include/myaddrinfo.h
+dns_strtype.o: ../../include/sock_addr.h
+dns_strtype.o: ../../include/sys_defs.h
+dns_strtype.o: ../../include/vbuf.h
+dns_strtype.o: ../../include/vstring.h
+dns_strtype.o: dns.h
+dns_strtype.o: dns_strtype.c
+test_dns_lookup.o: ../../include/argv.h
+test_dns_lookup.o: ../../include/check_arg.h
+test_dns_lookup.o: ../../include/mail_params.h
+test_dns_lookup.o: ../../include/msg.h
+test_dns_lookup.o: ../../include/msg_vstream.h
+test_dns_lookup.o: ../../include/myaddrinfo.h
+test_dns_lookup.o: ../../include/mymalloc.h
+test_dns_lookup.o: ../../include/sock_addr.h
+test_dns_lookup.o: ../../include/sys_defs.h
+test_dns_lookup.o: ../../include/vbuf.h
+test_dns_lookup.o: ../../include/vstream.h
+test_dns_lookup.o: ../../include/vstring.h
+test_dns_lookup.o: dns.h
+test_dns_lookup.o: test_dns_lookup.c
diff --git a/src/dns/dns.h b/src/dns/dns.h
new file mode 100644
index 0000000..3631d23
--- /dev/null
+++ b/src/dns/dns.h
@@ -0,0 +1,345 @@
+#ifndef _DNS_H_INCLUDED_
+#define _DNS_H_INCLUDED_
+
+/*++
+/* NAME
+/* dns 3h
+/* SUMMARY
+/* domain name service lookup
+/* SYNOPSIS
+/* #include <dns.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#ifdef RESOLVE_H_NEEDS_STDIO_H
+#include <stdio.h>
+#endif
+#ifdef RESOLVE_H_NEEDS_NAMESER8_COMPAT_H
+#include <nameser8_compat.h>
+#endif
+#ifdef RESOLVE_H_NEEDS_ARPA_NAMESER_COMPAT_H
+#include <arpa/nameser_compat.h>
+#endif
+#include <resolv.h>
+
+ /*
+ * Name server compatibility. These undocumented macros appear in the file
+ * <arpa/nameser.h>, but since they are undocumented we should not count on
+ * their presence, and so they are included here just in case.
+ */
+#ifndef GETSHORT
+
+#define GETSHORT(s, cp) { \
+ unsigned char *t_cp = (u_char *)(cp); \
+ (s) = ((unsigned)t_cp[0] << 8) \
+ | ((unsigned)t_cp[1]) \
+ ; \
+ (cp) += 2; \
+}
+
+#define GETLONG(l, cp) { \
+ unsigned char *t_cp = (u_char *)(cp); \
+ (l) = ((unsigned)t_cp[0] << 24) \
+ | ((unsigned)t_cp[1] << 16) \
+ | ((unsigned)t_cp[2] << 8) \
+ | ((unsigned)t_cp[3]) \
+ ; \
+ (cp) += 4; \
+}
+
+#endif
+
+/*
+ * Disable DNSSEC at compile-time even if RES_USE_DNSSEC is available
+ */
+#ifdef NO_DNSSEC
+#undef RES_USE_DNSSEC
+#undef RES_TRUSTAD
+#endif
+
+ /*
+ * Compatibility with systems that lack RES_USE_DNSSEC and RES_USE_EDNS0
+ */
+#ifndef RES_USE_DNSSEC
+#define RES_USE_DNSSEC 0
+#endif
+#ifndef RES_USE_EDNS0
+#define RES_USE_EDNS0 0
+#endif
+#ifndef RES_TRUSTAD
+#define RES_TRUSTAD 0
+#endif
+
+ /*-
+ * TLSA: https://tools.ietf.org/html/rfc6698#section-7.1
+ * RRSIG: http://tools.ietf.org/html/rfc4034#section-3
+ *
+ * We don't request RRSIG, but we get it "for free" when we send the DO-bit.
+ */
+#ifndef T_TLSA
+#define T_TLSA 52
+#endif
+#ifndef T_RRSIG
+#define T_RRSIG 46 /* Avoid unknown RR in logs */
+#endif
+#ifndef T_DNAME
+#define T_DNAME 39 /* [RFC6672] */
+#endif
+
+ /*
+ * https://tools.ietf.org/html/rfc6698#section-7.2
+ */
+#define DNS_TLSA_USAGE_CA_CONSTRAINT 0
+#define DNS_TLSA_USAGE_SERVICE_CERTIFICATE_CONSTRAINT 1
+#define DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION 2
+#define DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE 3
+
+ /*
+ * https://tools.ietf.org/html/rfc6698#section-7.3
+ */
+#define DNS_TLSA_SELECTOR_FULL_CERTIFICATE 0
+#define DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO 1
+
+ /*
+ * https://tools.ietf.org/html/rfc6698#section-7.4
+ */
+#define DNS_TLSA_MATCHING_TYPE_NO_HASH_USED 0
+#define DNS_TLSA_MATCHING_TYPE_SHA256 1
+#define DNS_TLSA_MATCHING_TYPE_SHA512 2
+
+ /*
+ * SunOS 4 needs this.
+ */
+#ifndef T_TXT
+#define T_TXT 16
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <sock_addr.h>
+#include <myaddrinfo.h>
+
+ /*
+ * Structure for fixed resource record data.
+ */
+typedef struct DNS_FIXED {
+ unsigned short type; /* T_A, T_CNAME, etc. */
+ unsigned short class; /* C_IN, etc. */
+ unsigned int ttl; /* always */
+ unsigned length; /* record length */
+} DNS_FIXED;
+
+ /*
+ * Structure of a DNS resource record after expansion. The components are
+ * named after the things one can expect to find in a DNS resource record.
+ */
+typedef struct DNS_RR {
+ char *qname; /* query name, mystrdup()ed */
+ char *rname; /* reply name, mystrdup()ed */
+ unsigned short type; /* T_A, T_CNAME, etc. */
+ unsigned short class; /* C_IN, etc. */
+ unsigned int ttl; /* always */
+ unsigned int dnssec_valid; /* DNSSEC validated */
+ unsigned short pref; /* T_MX only */
+ struct DNS_RR *next; /* linkage */
+ size_t data_len; /* actual data size */
+ char data[1]; /* actually a bunch of data */
+} DNS_RR;
+
+ /*
+ * dns_strerror.c
+ */
+extern const char *dns_strerror(unsigned);
+
+ /*
+ * dns_strtype.c
+ */
+extern const char *dns_strtype(unsigned);
+extern unsigned dns_type(const char *);
+
+ /*
+ * dns_strrecord.c
+ */
+extern char *dns_strrecord(VSTRING *, DNS_RR *);
+
+ /*
+ * dns_rr.c
+ */
+extern DNS_RR *dns_rr_create(const char *, const char *,
+ ushort, ushort,
+ unsigned, unsigned,
+ const char *, size_t);
+extern void dns_rr_free(DNS_RR *);
+extern DNS_RR *dns_rr_copy(DNS_RR *);
+extern DNS_RR *dns_rr_append(DNS_RR *, DNS_RR *);
+extern DNS_RR *dns_rr_sort(DNS_RR *, int (*) (DNS_RR *, DNS_RR *));
+extern int dns_rr_compare_pref_ipv6(DNS_RR *, DNS_RR *);
+extern int dns_rr_compare_pref_ipv4(DNS_RR *, DNS_RR *);
+extern int dns_rr_compare_pref_any(DNS_RR *, DNS_RR *);
+extern int dns_rr_compare_pref(DNS_RR *, DNS_RR *);
+extern DNS_RR *dns_rr_shuffle(DNS_RR *);
+extern DNS_RR *dns_rr_remove(DNS_RR *, DNS_RR *);
+
+ /*
+ * dns_rr_to_pa.c
+ */
+extern const char *dns_rr_to_pa(DNS_RR *, MAI_HOSTADDR_STR *);
+
+ /*
+ * dns_sa_to_rr.c
+ */
+extern DNS_RR *dns_sa_to_rr(const char *, unsigned, struct sockaddr *);
+
+ /*
+ * dns_rr_to_sa.c
+ */
+extern int dns_rr_to_sa(DNS_RR *, unsigned, struct sockaddr *, SOCKADDR_SIZE *);
+
+ /*
+ * dns_rr_eq_sa.c
+ */
+extern int dns_rr_eq_sa(DNS_RR *, struct sockaddr *);
+
+#ifdef HAS_IPV6
+#define DNS_RR_EQ_SA(rr, sa) \
+ ((SOCK_ADDR_IN_FAMILY(sa) == AF_INET && (rr)->type == T_A \
+ && SOCK_ADDR_IN_ADDR(sa).s_addr == IN_ADDR((rr)->data).s_addr) \
+ || (SOCK_ADDR_IN_FAMILY(sa) == AF_INET6 && (rr)->type == T_AAAA \
+ && memcmp((char *) &(SOCK_ADDR_IN6_ADDR(sa)), \
+ (rr)->data, (rr)->data_len) == 0))
+#else
+#define DNS_RR_EQ_SA(rr, sa) \
+ (SOCK_ADDR_IN_FAMILY(sa) == AF_INET && (rr)->type == T_A \
+ && SOCK_ADDR_IN_ADDR(sa).s_addr == IN_ADDR((rr)->data).s_addr)
+#endif
+
+ /*
+ * dns_lookup.c
+ */
+extern int dns_lookup_x(const char *, unsigned, unsigned, DNS_RR **,
+ VSTRING *, VSTRING *, int *, unsigned);
+extern int dns_lookup_rl(const char *, unsigned, DNS_RR **, VSTRING *,
+ VSTRING *, int *, int,...);
+extern int dns_lookup_rv(const char *, unsigned, DNS_RR **, VSTRING *,
+ VSTRING *, int *, int, unsigned *);
+
+#define dns_lookup(name, type, rflags, list, fqdn, why) \
+ dns_lookup_x((name), (type), (rflags), (list), (fqdn), (why), (int *) 0, \
+ (unsigned) 0)
+#define dns_lookup_r(name, type, rflags, list, fqdn, why, rcode) \
+ dns_lookup_x((name), (type), (rflags), (list), (fqdn), (why), (rcode), \
+ (unsigned) 0)
+#define dns_lookup_l(name, rflags, list, fqdn, why, lflags, ...) \
+ dns_lookup_rl((name), (rflags), (list), (fqdn), (why), (int *) 0, \
+ (lflags), __VA_ARGS__)
+#define dns_lookup_v(name, rflags, list, fqdn, why, lflags, ltype) \
+ dns_lookup_rv((name), (rflags), (list), (fqdn), (why), (int *) 0, \
+ (lflags), (ltype))
+
+ /*
+ * The dns_lookup() rflag that requests DNSSEC validation.
+ */
+#define DNS_WANT_DNSSEC_VALIDATION(rflags) ((rflags) & RES_USE_DNSSEC)
+
+ /*
+ * lflags.
+ */
+#define DNS_REQ_FLAG_STOP_OK (1<<0)
+#define DNS_REQ_FLAG_STOP_INVAL (1<<1)
+#define DNS_REQ_FLAG_STOP_NULLMX (1<<2)
+#define DNS_REQ_FLAG_STOP_MX_POLICY (1<<3)
+#define DNS_REQ_FLAG_NCACHE_TTL (1<<4)
+#define DNS_REQ_FLAG_NONE (0)
+
+ /*
+ * Status codes. Failures must have negative codes so they will not collide
+ * with valid counts of answer records etc.
+ *
+ * When a function queries multiple record types for one name, it issues one
+ * query for each query record type. Each query returns a (status, rcode,
+ * text). Only one of these (status, rcode, text) will be returned to the
+ * caller. The selection is based on the status code precedence.
+ *
+ * - Return DNS_OK (and the corresponding rcode) as long as any query returned
+ * DNS_OK. If this is changed, then code needs to be added to prevent memory
+ * leaks.
+ *
+ * - Return DNS_RETRY (and the corresponding rcode and text) instead of any
+ * hard negative result.
+ *
+ * - Return DNS_NOTFOUND (and the corresponding rcode and text) only when all
+ * queries returned DNS_NOTFOUND.
+ *
+ * DNS_POLICY ranks higher than DNS_RETRY because there was a DNS_OK result,
+ * but the reply filter dropped it. This is a very soft error.
+ *
+ * Below is the precedence order. The order between DNS_RETRY and DNS_NOTFOUND
+ * is arbitrary.
+ */
+#define DNS_RECURSE (-7) /* internal only: recursion needed */
+#define DNS_NOTFOUND (-6) /* query ok, data not found */
+#define DNS_NULLMX (-5) /* query ok, service unavailable */
+#define DNS_FAIL (-4) /* query failed, don't retry */
+#define DNS_INVAL (-3) /* query ok, malformed reply */
+#define DNS_RETRY (-2) /* query failed, try again */
+#define DNS_POLICY (-1) /* query ok, all records dropped */
+#define DNS_OK 0 /* query succeeded */
+
+ /*
+ * How long can a DNS name or single text value be?
+ */
+#define DNS_NAME_LEN 1024
+
+ /*
+ * dns_rr_filter.c.
+ */
+extern void dns_rr_filter_compile(const char *, const char *);
+
+#ifdef LIBDNS_INTERNAL
+#include <maps.h>
+extern MAPS *dns_rr_filter_maps;
+extern int dns_rr_filter_execute(DNS_RR **);
+
+#endif
+
+ /*
+ * dns_str_resflags.c
+ */
+const char *dns_str_resflags(unsigned long);
+
+ /*
+ * dns_sec.c.
+ */
+#define DNS_SEC_FLAG_AVAILABLE (1<<0) /* got some DNSSEC validated reply */
+#define DNS_SEC_FLAG_DONT_PROBE (1<<1) /* probe already sent, or disabled */
+
+#define DNS_SEC_STATS_SET(flags) (dns_sec_stats |= (flags))
+#define DNS_SEC_STATS_TEST(flags) (dns_sec_stats & (flags))
+
+extern int dns_sec_stats; /* See DNS_SEC_FLAG_XXX above */
+extern void dns_sec_probe(int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/dns/dns_lookup.c b/src/dns/dns_lookup.c
new file mode 100644
index 0000000..68e243b
--- /dev/null
+++ b/src/dns/dns_lookup.c
@@ -0,0 +1,1193 @@
+/*++
+/* NAME
+/* dns_lookup 3
+/* SUMMARY
+/* domain name service lookup
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* int dns_lookup(name, type, rflags, list, fqdn, why)
+/* const char *name;
+/* unsigned type;
+/* unsigned rflags;
+/* DNS_RR **list;
+/* VSTRING *fqdn;
+/* VSTRING *why;
+/*
+/* int dns_lookup_l(name, rflags, list, fqdn, why, lflags, ltype, ...)
+/* const char *name;
+/* unsigned rflags;
+/* DNS_RR **list;
+/* VSTRING *fqdn;
+/* VSTRING *why;
+/* int lflags;
+/* unsigned ltype;
+/*
+/* int dns_lookup_v(name, rflags, list, fqdn, why, lflags, ltype)
+/* const char *name;
+/* unsigned rflags;
+/* DNS_RR **list;
+/* VSTRING *fqdn;
+/* VSTRING *why;
+/* int lflags;
+/* unsigned *ltype;
+/* AUXILIARY FUNCTIONS
+/* extern int var_dns_ncache_ttl_fix;
+/*
+/* int dns_lookup_r(name, type, rflags, list, fqdn, why, rcode)
+/* const char *name;
+/* unsigned type;
+/* unsigned rflags;
+/* DNS_RR **list;
+/* VSTRING *fqdn;
+/* VSTRING *why;
+/* int *rcode;
+/*
+/* int dns_lookup_rl(name, rflags, list, fqdn, why, rcode, lflags,
+/* ltype, ...)
+/* const char *name;
+/* unsigned rflags;
+/* DNS_RR **list;
+/* VSTRING *fqdn;
+/* VSTRING *why;
+/* int *rcode;
+/* int lflags;
+/* unsigned ltype;
+/*
+/* int dns_lookup_rv(name, rflags, list, fqdn, why, rcode, lflags,
+/* ltype)
+/* const char *name;
+/* unsigned rflags;
+/* DNS_RR **list;
+/* VSTRING *fqdn;
+/* VSTRING *why;
+/* int *rcode;
+/* int lflags;
+/* unsigned *ltype;
+/*
+/* int dns_lookup_x(name, type, rflags, list, fqdn, why, rcode, lflags)
+/* const char *name;
+/* unsigned type;
+/* unsigned rflags;
+/* DNS_RR **list;
+/* VSTRING *fqdn;
+/* VSTRING *why;
+/* int *rcode;
+/* unsigned lflags;
+/* DESCRIPTION
+/* dns_lookup() looks up DNS resource records. When requested to
+/* look up data other than type CNAME, it will follow a limited
+/* number of CNAME indirections. All result names (including
+/* null terminator) will fit a buffer of size DNS_NAME_LEN.
+/* All name results are validated by \fIvalid_hostname\fR();
+/* an invalid name is reported as a DNS_INVAL result, while
+/* malformed replies are reported as transient errors.
+/*
+/* dns_lookup_l() and dns_lookup_v() allow the user to specify
+/* a list of resource types.
+/*
+/* dns_lookup_x, dns_lookup_r(), dns_lookup_rl() and dns_lookup_rv()
+/* accept or return additional information.
+/*
+/* The var_dns_ncache_ttl_fix variable controls a workaround
+/* for res_search(3) implementations that break the
+/* DNS_REQ_FLAG_NCACHE_TTL feature. The workaround does not
+/* support EDNS0 or DNSSEC, but it should be sufficient for
+/* DNSBL/DNSWL lookups.
+/* INPUTS
+/* .ad
+/* .fi
+/* .IP name
+/* The name to be looked up in the domain name system.
+/* This name must pass the valid_hostname() test; it
+/* must not be an IP address.
+/* .IP type
+/* The resource record type to be looked up (T_A, T_MX etc.).
+/* .IP rflags
+/* Resolver flags. These are a bitwise OR of:
+/* .RS
+/* .IP RES_DEBUG
+/* Print debugging information.
+/* .IP RES_DNSRCH
+/* Search local domain and parent domains.
+/* .IP RES_DEFNAMES
+/* Append local domain to unqualified names.
+/* .IP RES_USE_DNSSEC
+/* Request DNSSEC validation. This flag is silently ignored
+/* when the system stub resolver API, resolver(3), does not
+/* implement DNSSEC.
+/* Automatically turns on the RES_TRUSTAD flag on systems that
+/* support this flag (this behavior will be more configurable
+/* in a later release).
+/* .RE
+/* .IP lflags
+/* Flags that control the operation of the dns_lookup*()
+/* functions. DNS_REQ_FLAG_NONE requests no special processing.
+/* Otherwise, specify one or more of the following:
+/* .RS
+/* .IP DNS_REQ_FLAG_STOP_INVAL
+/* This flag is used by dns_lookup_l() and dns_lookup_v().
+/* Invoke dns_lookup() for the resource types in the order as
+/* specified, and return when dns_lookup() returns DNS_INVAL.
+/* .IP DNS_REQ_FLAG_STOP_NULLMX
+/* This flag is used by dns_lookup_l() and dns_lookup_v().
+/* Invoke dns_lookup() for the resource types in the order as
+/* specified, and return when dns_lookup() returns DNS_NULLMX.
+/* .IP DNS_REQ_FLAG_STOP_MX_POLICY
+/* This flag is used by dns_lookup_l() and dns_lookup_v().
+/* Invoke dns_lookup() for the resource types in the order as
+/* specified, and return when dns_lookup() returns DNS_POLICY
+/* for an MX query.
+/* .IP DNS_REQ_FLAG_STOP_OK
+/* This flag is used by dns_lookup_l() and dns_lookup_v().
+/* Invoke dns_lookup() for the resource types in the order as
+/* specified, and return when dns_lookup() returns DNS_OK.
+/* .IP DNS_REQ_FLAG_NCACHE_TTL
+/* When the lookup result status is DNS_NOTFOUND, return the
+/* SOA record(s) from the authority section in the reply, if
+/* available. The per-record reply TTL specifies how long the
+/* DNS_NOTFOUND answer is valid. The caller should pass the
+/* record(s) to dns_rr_free().
+/* .RE
+/* .IP ltype
+/* The resource record types to be looked up. In the case of
+/* dns_lookup_l(), this is a null-terminated argument list.
+/* In the case of dns_lookup_v(), this is a null-terminated
+/* integer array.
+/* OUTPUTS
+/* .ad
+/* .fi
+/* .IP list
+/* A null pointer, or a pointer to a variable that receives a
+/* list of requested resource records.
+/* .IP fqdn
+/* A null pointer, or storage for the fully-qualified domain
+/* name found for \fIname\fR.
+/* .IP why
+/* A null pointer, or storage for the reason for failure.
+/* .IP rcode
+/* Pointer to storage for the reply RCODE value. This gives
+/* more detailed information than DNS_FAIL, DNS_RETRY, etc.
+/* DIAGNOSTICS
+/* If DNSSEC validation is requested but the response is not
+/* DNSSEC validated, dns_lookup() will send a one-time probe
+/* query as configured with the \fBdnssec_probe\fR configuration
+/* parameter, and will log a warning when the probe response
+/* was not DNSSEC validated.
+/* .PP
+/* dns_lookup() returns one of the following codes and sets the
+/* \fIwhy\fR argument accordingly:
+/* .IP DNS_OK
+/* The DNS query succeeded.
+/* .IP DNS_POLICY
+/* The DNS query succeeded, but the answer did not pass the
+/* policy filter.
+/* .IP DNS_NOTFOUND
+/* The DNS query succeeded; the requested information was not found.
+/* .IP DNS_NULLMX
+/* The DNS query succeeded; the requested service is unavailable.
+/* This is returned when the list argument is not a null
+/* pointer, and an MX lookup result contains a null server
+/* name (so-called "nullmx" record).
+/* .IP DNS_INVAL
+/* The DNS query succeeded; the result failed the valid_hostname() test.
+/*
+/* NOTE: the valid_hostname() test is skipped for results that
+/* the caller suppresses explicitly. For example, when the
+/* caller requests MX record lookup but specifies a null
+/* resource record list argument, no syntax check will be done
+/* for MX server names.
+/* .IP DNS_RETRY
+/* The query failed, or the reply was malformed.
+/* The problem is considered transient.
+/* .IP DNS_FAIL
+/* The query failed.
+/* BUGS
+/* dns_lookup() implements a subset of all possible resource types:
+/* CNAME, MX, A, and some records with similar formatting requirements.
+/* It is unwise to specify the T_ANY wildcard resource type.
+/*
+/* It takes a surprising amount of code to accomplish what appears
+/* to be a simple task. Later versions of the mail system may implement
+/* their own DNS client software.
+/* SEE ALSO
+/* dns_rr(3) resource record memory and list management
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <netdb.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <vstring.h>
+#include <msg.h>
+#include <valid_hostname.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* DNS library. */
+
+#define LIBDNS_INTERNAL
+#include "dns.h"
+
+/* Local stuff. */
+
+ /*
+ * Structure to keep track of things while decoding a name server reply.
+ */
+#define DEF_DNS_REPLY_SIZE 4096 /* in case we're using TCP */
+#define MAX_DNS_REPLY_SIZE 65536 /* in case we're using TCP */
+#define MAX_DNS_QUERY_SIZE 2048 /* XXX */
+
+typedef struct DNS_REPLY {
+ unsigned char *buf; /* raw reply data */
+ size_t buf_len; /* reply buffer length */
+ int rcode; /* unfiltered reply code */
+ int dnssec_ad; /* DNSSEC AD bit */
+ int query_count; /* number of queries */
+ int answer_count; /* number of answers */
+ int auth_count; /* number of authority records */
+ unsigned char *query_start; /* start of query data */
+ unsigned char *answer_start; /* start of answer data */
+ unsigned char *end; /* first byte past reply */
+} DNS_REPLY;
+
+ /*
+ * Test/set primitives to determine if the reply buffer contains a server
+ * response. We use this when the caller requests DNS_REQ_FLAG_NCACHE_TTL,
+ * and the DNS server replies that the requested record does not exist.
+ */
+#define TEST_HAVE_DNS_REPLY_PACKET(r) ((r)->end > (r)->buf)
+#define SET_HAVE_DNS_REPLY_PACKET(r, l) ((r)->end = (r)->buf + (l))
+#define SET_NO_DNS_REPLY_PACKET(r) ((r)->end = (r)->buf)
+
+#define INET_ADDR_LEN 4 /* XXX */
+#define INET6_ADDR_LEN 16 /* XXX */
+
+ /*
+ * To improve postscreen's whitelisting support, we need to know how long a
+ * DNSBL "not found" answer is valid. The 2010 implementation assumed it was
+ * valid for 3600 seconds. That is too long by 2015 standards.
+ *
+ * Instead of guessing, Postfix 3.1 and later implement RFC 2308 (DNS NCACHE),
+ * where a DNS server provides the TTL of a "not found" response as the TTL
+ * of an SOA record in the authority section.
+ *
+ * Unfortunately, the res_search() and res_query() API gets in the way. These
+ * functions overload their result value, the server reply length, and
+ * return -1 when the requested record does not exist. With libbind-based
+ * implementations, the server response is still available in an application
+ * buffer, thanks to the promise that res_query() and res_search() invoke
+ * res_send(), which returns the full server response even if the requested
+ * record does not exist.
+ *
+ * If this promise is broken (for example, res_search() does not call
+ * res_send(), but some non-libbind implementation that updates the
+ * application buffer only when the requested record exists), then we have a
+ * way out by setting the var_dns_ncache_ttl_fix variable. This enables a
+ * limited res_query() clone that should be sufficient for DNSBL / DNSWL
+ * lookups.
+ *
+ * The libunbound API does not comingle the reply length and reply status
+ * information, but that will have to wait until it is safe to make
+ * libunbound a mandatory dependency for Postfix.
+ */
+
+/* dns_res_query - a res_query() clone that can return negative replies */
+
+static int dns_res_query(const char *name, int class, int type,
+ unsigned char *answer, int anslen)
+{
+ unsigned char msg_buf[MAX_DNS_QUERY_SIZE];
+ HEADER *reply_header = (HEADER *) answer;
+ int len;
+
+ /*
+ * Differences with res_query() from libbind:
+ *
+ * - This function returns a positive server reply length not only in case
+ * of success, but in all cases where a server reply is available that
+ * passes the preliminary checks in res_send().
+ *
+ * - This function clears h_errno in case of success. The caller must use
+ * h_errno instead of the return value to decide if the lookup was
+ * successful.
+ *
+ * - No support for EDNS0 and DNSSEC (including turning off EDNS0 after
+ * error). That should be sufficient for DNS reputation lookups where the
+ * reply contains a small number of IP addresses. TXT records are out of
+ * scope for this workaround.
+ */
+ reply_header->rcode = NOERROR;
+
+#define NO_MKQUERY_DATA_BUF ((unsigned char *) 0)
+#define NO_MKQUERY_DATA_LEN ((int) 0)
+#define NO_MKQUERY_NEWRR ((unsigned char *) 0)
+
+ if ((len = res_mkquery(QUERY, name, class, type, NO_MKQUERY_DATA_BUF,
+ NO_MKQUERY_DATA_LEN, NO_MKQUERY_NEWRR,
+ msg_buf, sizeof(msg_buf))) < 0) {
+ SET_H_ERRNO(NO_RECOVERY);
+ if (msg_verbose)
+ msg_info("res_mkquery() failed");
+ return (len);
+ } else if ((len = res_send(msg_buf, len, answer, anslen)) < 0) {
+ SET_H_ERRNO(TRY_AGAIN);
+ if (msg_verbose)
+ msg_info("res_send() failed");
+ return (len);
+ } else {
+ switch (reply_header->rcode) {
+ case NXDOMAIN:
+ SET_H_ERRNO(HOST_NOT_FOUND);
+ break;
+ case NOERROR:
+ if (reply_header->ancount != 0)
+ SET_H_ERRNO(0);
+ else
+ SET_H_ERRNO(NO_DATA);
+ break;
+ case SERVFAIL:
+ SET_H_ERRNO(TRY_AGAIN);
+ break;
+ default:
+ SET_H_ERRNO(NO_RECOVERY);
+ break;
+ }
+ return (len);
+ }
+}
+
+/* dns_res_search - res_search() that can return negative replies */
+
+static int dns_res_search(const char *name, int class, int type,
+ unsigned char *answer, int anslen, int keep_notfound)
+{
+ int len;
+
+ /*
+ * Differences with res_search() from libbind:
+ *
+ * - With a non-zero keep_notfound argument, this function returns a
+ * positive server reply length not only in case of success, but also in
+ * case of a "notfound" reply status. The keep_notfound argument is
+ * usually zero, which allows us to avoid an unnecessary memset() call in
+ * the most common use case.
+ *
+ * - This function clears h_errno in case of success. The caller must use
+ * h_errno instead of the return value to decide if a lookup was
+ * successful.
+ */
+#define NOT_FOUND_H_ERRNO(he) ((he) == HOST_NOT_FOUND || (he) == NO_DATA)
+
+ if (keep_notfound)
+ /* Prepare for returning a null-padded server reply. */
+ memset(answer, 0, anslen);
+ len = res_search(name, class, type, answer, anslen);
+ /* Begin API creep workaround. */
+ if (len < 0 && h_errno == 0) {
+ SET_H_ERRNO(TRY_AGAIN);
+ msg_warn("res_query(\"%s\", %d, %d, %p, %d) returns %d with h_errno==0"
+ " -- setting h_errno=TRY_AGAIN",
+ name, class, type, answer, anslen, len);
+ }
+ /* End API creep workaround. */
+ if (len > 0) {
+ SET_H_ERRNO(0);
+ } else if (keep_notfound && NOT_FOUND_H_ERRNO(h_errno)) {
+ /* Expect to return a null-padded server reply. */
+ len = anslen;
+ }
+ return (len);
+}
+
+/* dns_query - query name server and pre-parse the reply */
+
+static int dns_query(const char *name, int type, unsigned flags,
+ DNS_REPLY *reply, VSTRING *why, unsigned lflags)
+{
+ HEADER *reply_header;
+ int len;
+ unsigned long saved_options;
+ int keep_notfound = (lflags & DNS_REQ_FLAG_NCACHE_TTL);
+
+ /*
+ * Initialize the reply buffer.
+ */
+ if (reply->buf == 0) {
+ reply->buf = (unsigned char *) mymalloc(DEF_DNS_REPLY_SIZE);
+ reply->buf_len = DEF_DNS_REPLY_SIZE;
+ }
+
+ /*
+ * Initialize the name service.
+ */
+ if ((_res.options & RES_INIT) == 0 && res_init() < 0) {
+ if (why)
+ vstring_strcpy(why, "Name service initialization failure");
+ return (DNS_FAIL);
+ }
+
+ /*
+ * Set search options: debugging, parent domain search, append local
+ * domain. Do not allow the user to control other features.
+ */
+#define USER_FLAGS (RES_DEBUG | RES_DNSRCH | RES_DEFNAMES | RES_USE_DNSSEC)
+
+ if ((flags & USER_FLAGS) != flags)
+ msg_panic("dns_query: bad flags: %d", flags);
+
+ /*
+ * Set extra options that aren't exposed to the application.
+ */
+#define XTRA_FLAGS (RES_USE_EDNS0 | RES_TRUSTAD)
+
+ if (DNS_WANT_DNSSEC_VALIDATION(flags))
+ flags |= (RES_USE_EDNS0 | RES_TRUSTAD);
+
+ /*
+ * Save and restore resolver options that we overwrite, to avoid
+ * surprising behavior in other code that also invokes the resolver.
+ */
+#define SAVE_FLAGS (USER_FLAGS | XTRA_FLAGS)
+
+ saved_options = (_res.options & SAVE_FLAGS);
+
+ /*
+ * Perform the lookup. Claim that the information cannot be found if and
+ * only if the name server told us so.
+ */
+ for (;;) {
+ _res.options &= ~saved_options;
+ _res.options |= flags;
+ if (keep_notfound && var_dns_ncache_ttl_fix) {
+ len = dns_res_query((char *) name, C_IN, type, reply->buf,
+ reply->buf_len);
+ } else {
+ len = dns_res_search((char *) name, C_IN, type, reply->buf,
+ reply->buf_len, keep_notfound);
+ }
+ _res.options &= ~flags;
+ _res.options |= saved_options;
+ reply_header = (HEADER *) reply->buf;
+ reply->rcode = reply_header->rcode;
+ if ((reply->dnssec_ad = !!reply_header->ad) != 0)
+ DNS_SEC_STATS_SET(DNS_SEC_FLAG_AVAILABLE);
+ if (h_errno != 0) {
+ if (why)
+ vstring_sprintf(why, "Host or domain name not found. "
+ "Name service error for name=%s type=%s: %s",
+ name, dns_strtype(type), dns_strerror(h_errno));
+ if (msg_verbose)
+ msg_info("dns_query: %s (%s): %s",
+ name, dns_strtype(type), dns_strerror(h_errno));
+ switch (h_errno) {
+ case NO_RECOVERY:
+ return (DNS_FAIL);
+ case HOST_NOT_FOUND:
+ case NO_DATA:
+ if (keep_notfound)
+ break;
+ SET_NO_DNS_REPLY_PACKET(reply);
+ return (DNS_NOTFOUND);
+ default:
+ return (DNS_RETRY);
+ }
+ } else {
+ if (msg_verbose)
+ msg_info("dns_query: %s (%s): OK", name, dns_strtype(type));
+ }
+
+ if (reply_header->tc == 0 || reply->buf_len >= MAX_DNS_REPLY_SIZE)
+ break;
+ reply->buf = (unsigned char *)
+ myrealloc((void *) reply->buf, 2 * reply->buf_len);
+ reply->buf_len *= 2;
+ }
+
+ /*
+ * Future proofing. If this reaches the panic call, then some code change
+ * introduced a bug.
+ */
+ if (len < 0)
+ msg_panic("dns_query: bad length %d (h_errno=%s)",
+ len, dns_strerror(h_errno));
+
+ /*
+ * Paranoia.
+ */
+ if (len > reply->buf_len) {
+ msg_warn("reply length %d > buffer length %d for name=%s type=%s",
+ len, (int) reply->buf_len, name, dns_strtype(type));
+ len = reply->buf_len;
+ }
+
+ /*
+ * Initialize the reply structure. Some structure members are filled on
+ * the fly while the reply is being parsed.
+ */
+ SET_HAVE_DNS_REPLY_PACKET(reply, len);
+ reply->query_start = reply->buf + sizeof(HEADER);
+ reply->answer_start = 0;
+ reply->query_count = ntohs(reply_header->qdcount);
+ reply->answer_count = ntohs(reply_header->ancount);
+ reply->auth_count = ntohs(reply_header->nscount);
+ if (msg_verbose > 1)
+ msg_info("dns_query: reply len=%d ancount=%d nscount=%d",
+ len, reply->answer_count, reply->auth_count);
+
+ /*
+ * Future proofing. If this reaches the panic call, then some code change
+ * introduced a bug.
+ */
+ if (h_errno == 0) {
+ return (DNS_OK);
+ } else if (keep_notfound) {
+ return (DNS_NOTFOUND);
+ } else {
+ msg_panic("dns_query: unexpected reply status: %s",
+ dns_strerror(h_errno));
+ }
+}
+
+/* dns_skip_query - skip query data in name server reply */
+
+static int dns_skip_query(DNS_REPLY *reply)
+{
+ int query_count = reply->query_count;
+ unsigned char *pos = reply->query_start;
+ int len;
+
+ /*
+ * For each query, skip over the domain name and over the fixed query
+ * data.
+ */
+ while (query_count-- > 0) {
+ if (pos >= reply->end)
+ return DNS_RETRY;
+ len = dn_skipname(pos, reply->end);
+ if (len < 0)
+ return (DNS_RETRY);
+ pos += len + QFIXEDSZ;
+ }
+ reply->answer_start = pos;
+ return (DNS_OK);
+}
+
+/* dns_get_fixed - extract fixed data from resource record */
+
+static int dns_get_fixed(unsigned char *pos, DNS_FIXED *fixed)
+{
+ GETSHORT(fixed->type, pos);
+ GETSHORT(fixed->class, pos);
+ GETLONG(fixed->ttl, pos);
+ GETSHORT(fixed->length, pos);
+
+ if (fixed->class != C_IN) {
+ msg_warn("dns_get_fixed: bad class: %u", fixed->class);
+ return (DNS_RETRY);
+ }
+ return (DNS_OK);
+}
+
+/* valid_rr_name - validate hostname in resource record */
+
+static int valid_rr_name(const char *name, const char *location,
+ unsigned type, DNS_REPLY *reply)
+{
+ char temp[DNS_NAME_LEN];
+ char *query_name;
+ int len;
+ char *gripe;
+ int result;
+
+ /*
+ * People aren't supposed to specify numeric names where domain names are
+ * required, but it "works" with some mailers anyway, so people complain
+ * when software doesn't bend over backwards.
+ */
+#define PASS_NAME 1
+#define REJECT_NAME 0
+
+ if (valid_hostaddr(name, DONT_GRIPE)) {
+ result = PASS_NAME;
+ gripe = "numeric domain name";
+ } else if (!valid_hostname(name, DO_GRIPE)) {
+ result = REJECT_NAME;
+ gripe = "malformed domain name";
+ } else {
+ result = PASS_NAME;
+ gripe = 0;
+ }
+
+ /*
+ * If we have a gripe, show some context, including the name used in the
+ * query and the type of reply that we're looking at.
+ */
+ if (gripe) {
+ len = dn_expand(reply->buf, reply->end, reply->query_start,
+ temp, DNS_NAME_LEN);
+ query_name = (len < 0 ? "*unparsable*" : temp);
+ msg_warn("%s in %s of %s record for %s: %.100s",
+ gripe, location, dns_strtype(type), query_name, name);
+ }
+ return (result);
+}
+
+/* dns_get_rr - extract resource record from name server reply */
+
+static int dns_get_rr(DNS_RR **list, const char *orig_name, DNS_REPLY *reply,
+ unsigned char *pos, char *rr_name,
+ DNS_FIXED *fixed)
+{
+ char temp[DNS_NAME_LEN];
+ char *tempbuf = temp;
+ UINT32_TYPE soa_buf[5];
+ int comp_len;
+ ssize_t data_len;
+ unsigned pref = 0;
+ unsigned char *src;
+ unsigned char *dst;
+ int ch;
+
+#define MIN2(a, b) ((unsigned)(a) < (unsigned)(b) ? (a) : (b))
+
+ *list = 0;
+
+ switch (fixed->type) {
+ default:
+ msg_panic("dns_get_rr: don't know how to extract resource type %s",
+ dns_strtype(fixed->type));
+ case T_CNAME:
+ case T_DNAME:
+ case T_MB:
+ case T_MG:
+ case T_MR:
+ case T_NS:
+ case T_PTR:
+ if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0)
+ return (DNS_RETRY);
+ if (!valid_rr_name(temp, "resource data", fixed->type, reply))
+ return (DNS_INVAL);
+ data_len = strlen(temp) + 1;
+ break;
+ case T_MX:
+ GETSHORT(pref, pos);
+ if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0)
+ return (DNS_RETRY);
+ /* Don't even think of returning an invalid hostname to the caller. */
+ if (*temp == 0)
+ return (DNS_NULLMX); /* TODO: descriptive text */
+ if (!valid_rr_name(temp, "resource data", fixed->type, reply))
+ return (DNS_INVAL);
+ data_len = strlen(temp) + 1;
+ break;
+ case T_A:
+ if (fixed->length != INET_ADDR_LEN) {
+ msg_warn("extract_answer: bad address length: %d", fixed->length);
+ return (DNS_RETRY);
+ }
+ if (fixed->length > sizeof(temp))
+ msg_panic("dns_get_rr: length %d > DNS_NAME_LEN",
+ fixed->length);
+ memcpy(temp, pos, fixed->length);
+ data_len = fixed->length;
+ break;
+#ifdef T_AAAA
+ case T_AAAA:
+ if (fixed->length != INET6_ADDR_LEN) {
+ msg_warn("extract_answer: bad address length: %d", fixed->length);
+ return (DNS_RETRY);
+ }
+ if (fixed->length > sizeof(temp))
+ msg_panic("dns_get_rr: length %d > DNS_NAME_LEN",
+ fixed->length);
+ memcpy(temp, pos, fixed->length);
+ data_len = fixed->length;
+ break;
+#endif
+
+ /*
+ * We impose the same length limit here as for DNS names. However,
+ * see T_TLSA discussion below.
+ */
+ case T_TXT:
+ data_len = MIN2(pos[0] + 1, MIN2(fixed->length + 1, sizeof(temp)));
+ for (src = pos + 1, dst = (unsigned char *) (temp);
+ dst < (unsigned char *) (temp) + data_len - 1; /* */ ) {
+ ch = *src++;
+ *dst++ = (ISPRINT(ch) ? ch : ' ');
+ }
+ *dst = 0;
+ break;
+
+ /*
+ * For a full certificate, fixed->length may be longer than
+ * sizeof(tmpbuf) == DNS_NAME_LEN. Since we don't need a decode
+ * buffer, just copy the raw data into the rr.
+ *
+ * XXX Reject replies with bogus length < 3.
+ *
+ * XXX What about enforcing a sane upper bound? The RFC 1035 hard
+ * protocol limit is the RRDATA length limit of 65535.
+ */
+ case T_TLSA:
+ data_len = fixed->length;
+ tempbuf = (char *) pos;
+ break;
+
+ /*
+ * We use the SOA record TTL to determine the negative reply TTL. We
+ * save the time fields in the SOA record for debugging, but for now
+ * we don't bother saving the source host and mailbox information, as
+ * that would require changes to the DNS_RR structure and APIs. See
+ * also code in dns_strrecord().
+ */
+ case T_SOA:
+ comp_len = dn_skipname(pos, reply->end);
+ if (comp_len < 0)
+ return (DNS_RETRY);
+ pos += comp_len;
+ comp_len = dn_skipname(pos, reply->end);
+ if (comp_len < 0)
+ return (DNS_RETRY);
+ pos += comp_len;
+ if (reply->end - pos < sizeof(soa_buf)) {
+ msg_warn("extract_answer: bad SOA length: %d", fixed->length);
+ return (DNS_RETRY);
+ }
+ GETLONG(soa_buf[0], pos); /* Serial */
+ GETLONG(soa_buf[1], pos); /* Refresh */
+ GETLONG(soa_buf[2], pos); /* Retry */
+ GETLONG(soa_buf[3], pos); /* Expire */
+ GETLONG(soa_buf[4], pos); /* Ncache TTL */
+ tempbuf = (char *) soa_buf;
+ data_len = sizeof(soa_buf);
+ break;
+ }
+ *list = dns_rr_create(orig_name, rr_name, fixed->type, fixed->class,
+ fixed->ttl, pref, tempbuf, data_len);
+ return (DNS_OK);
+}
+
+/* dns_get_alias - extract CNAME from name server reply */
+
+static int dns_get_alias(DNS_REPLY *reply, unsigned char *pos,
+ DNS_FIXED *fixed, char *cname, int c_len)
+{
+ if (fixed->type != T_CNAME)
+ msg_panic("dns_get_alias: bad type %s", dns_strtype(fixed->type));
+ if (dn_expand(reply->buf, reply->end, pos, cname, c_len) < 0)
+ return (DNS_RETRY);
+ if (!valid_rr_name(cname, "resource data", fixed->type, reply))
+ return (DNS_INVAL);
+ return (DNS_OK);
+}
+
+/* dns_get_answer - extract answers from name server reply */
+
+static int dns_get_answer(const char *orig_name, DNS_REPLY *reply, int type,
+ DNS_RR **rrlist, VSTRING *fqdn, char *cname, int c_len,
+ int *maybe_secure)
+{
+ char rr_name[DNS_NAME_LEN];
+ unsigned char *pos;
+ int answer_count = reply->answer_count;
+ int len;
+ DNS_FIXED fixed;
+ DNS_RR *rr;
+ int resource_found = 0;
+ int cname_found = 0;
+ int not_found_status = DNS_NOTFOUND; /* can't happen */
+ int status;
+
+ /*
+ * Initialize. Skip over the name server query if we haven't yet.
+ */
+ if (reply->answer_start == 0)
+ if ((status = dns_skip_query(reply)) < 0)
+ return (status);
+ pos = reply->answer_start;
+
+ /*
+ * Either this, or use a GOTO for emergency exits. The purpose is to
+ * prevent incomplete answers from being passed back to the caller.
+ */
+#define CORRUPT(status) { \
+ if (rrlist && *rrlist) { \
+ dns_rr_free(*rrlist); \
+ *rrlist = 0; \
+ } \
+ return (status); \
+ }
+
+ /*
+ * Iterate over all answers.
+ */
+ while (answer_count-- > 0) {
+
+ /*
+ * Optionally extract the fully-qualified domain name.
+ */
+ if (pos >= reply->end)
+ CORRUPT(DNS_RETRY);
+ len = dn_expand(reply->buf, reply->end, pos, rr_name, DNS_NAME_LEN);
+ if (len < 0)
+ CORRUPT(DNS_RETRY);
+ pos += len;
+
+ /*
+ * Extract the fixed reply data: type, class, ttl, length.
+ */
+ if (pos + RRFIXEDSZ > reply->end)
+ CORRUPT(DNS_RETRY);
+ if ((status = dns_get_fixed(pos, &fixed)) != DNS_OK)
+ CORRUPT(status);
+ if (strcmp(orig_name, ".") == 0 && *rr_name == 0)
+ /* Allow empty response name for root queries. */ ;
+ else if (!valid_rr_name(rr_name, "resource name", fixed.type, reply))
+ CORRUPT(DNS_INVAL);
+ if (fqdn)
+ vstring_strcpy(fqdn, rr_name);
+ if (msg_verbose)
+ msg_info("dns_get_answer: type %s for %s",
+ dns_strtype(fixed.type), rr_name);
+ pos += RRFIXEDSZ;
+
+ /*
+ * Optionally extract the requested resource or CNAME data.
+ */
+ if (pos + fixed.length > reply->end)
+ CORRUPT(DNS_RETRY);
+ if (type == fixed.type || type == T_ANY) { /* requested type */
+ if (rrlist) {
+ if ((status = dns_get_rr(&rr, orig_name, reply, pos, rr_name,
+ &fixed)) == DNS_OK) {
+ resource_found++;
+ rr->dnssec_valid = *maybe_secure ? reply->dnssec_ad : 0;
+ *rrlist = dns_rr_append(*rrlist, rr);
+ } else if (status == DNS_NULLMX) {
+ CORRUPT(status); /* TODO: use better name */
+ } else if (not_found_status != DNS_RETRY)
+ not_found_status = status;
+ } else
+ resource_found++;
+ } else if (fixed.type == T_CNAME) { /* cname resource */
+ cname_found++;
+ if (cname && c_len > 0)
+ if ((status = dns_get_alias(reply, pos, &fixed, cname, c_len)) != DNS_OK)
+ CORRUPT(status);
+ if (!reply->dnssec_ad)
+ *maybe_secure = 0;
+ }
+ pos += fixed.length;
+ }
+
+ /*
+ * See what answer we came up with. Report success when the requested
+ * information was found. Otherwise, when a CNAME was found, report that
+ * more recursion is needed. Otherwise report failure.
+ */
+ if (resource_found)
+ return (DNS_OK);
+ if (cname_found)
+ return (DNS_RECURSE);
+ return (not_found_status);
+}
+
+/* dns_lookup_x - DNS lookup user interface */
+
+int dns_lookup_x(const char *name, unsigned type, unsigned flags,
+ DNS_RR **rrlist, VSTRING *fqdn, VSTRING *why,
+ int *rcode, unsigned lflags)
+{
+ char cname[DNS_NAME_LEN];
+ int c_len = sizeof(cname);
+ static DNS_REPLY reply;
+ int count;
+ int status;
+ int maybe_secure = 1; /* Query name presumed secure */
+ const char *orig_name = name;
+
+ /*
+ * Reset results early. DNS_OK is not the only status that returns
+ * resource records; DNS_NOTFOUND will do that too, if requested.
+ */
+ if (rrlist)
+ *rrlist = 0;
+
+ /*
+ * DJBDNS produces a bogus A record when given a numerical hostname.
+ */
+ if (valid_hostaddr(name, DONT_GRIPE)) {
+ if (why)
+ vstring_sprintf(why,
+ "Name service error for %s: invalid host or domain name",
+ name);
+ if (rcode)
+ *rcode = NXDOMAIN;
+ SET_H_ERRNO(HOST_NOT_FOUND);
+ return (DNS_NOTFOUND);
+ }
+
+ /*
+ * The Linux resolver misbehaves when given an invalid domain name.
+ */
+ if (strcmp(name, ".") && !valid_hostname(name, DONT_GRIPE)) {
+ if (why)
+ vstring_sprintf(why,
+ "Name service error for %s: invalid host or domain name",
+ name);
+ if (rcode)
+ *rcode = NXDOMAIN;
+ SET_H_ERRNO(HOST_NOT_FOUND);
+ return (DNS_NOTFOUND);
+ }
+
+ /*
+ * Perform the lookup. Follow CNAME chains, but only up to a
+ * pre-determined maximum.
+ */
+ for (count = 0; count < 10; count++) {
+
+ /*
+ * Perform the DNS lookup, and pre-parse the name server reply.
+ */
+ status = dns_query(name, type, flags, &reply, why, lflags);
+ if (rcode)
+ *rcode = reply.rcode;
+ if (status != DNS_OK) {
+
+ /*
+ * If the record does not exist, and we have a copy of the server
+ * response, try to extract the negative caching TTL for the SOA
+ * record in the authority section. DO NOT return an error if an
+ * SOA record is malformed.
+ */
+ if (status == DNS_NOTFOUND && TEST_HAVE_DNS_REPLY_PACKET(&reply)
+ && reply.auth_count > 0) {
+ reply.answer_count = reply.auth_count; /* XXX TODO: Fix API */
+ (void) dns_get_answer(orig_name, &reply, T_SOA, rrlist, fqdn,
+ cname, c_len, &maybe_secure);
+ }
+ if (DNS_WANT_DNSSEC_VALIDATION(flags)
+ && !DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE | \
+ DNS_SEC_FLAG_DONT_PROBE))
+ dns_sec_probe(flags); /* XXX Clobbers 'reply' */
+ return (status);
+ }
+
+ /*
+ * Extract resource records of the requested type. Pick up CNAME
+ * information just in case the requested data is not found.
+ */
+ status = dns_get_answer(orig_name, &reply, type, rrlist, fqdn,
+ cname, c_len, &maybe_secure);
+ if (DNS_WANT_DNSSEC_VALIDATION(flags)
+ && !DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE | \
+ DNS_SEC_FLAG_DONT_PROBE))
+ dns_sec_probe(flags); /* XXX Clobbers 'reply' */
+ switch (status) {
+ default:
+ if (why)
+ vstring_sprintf(why, "Name service error for name=%s type=%s: "
+ "Malformed or unexpected name server reply",
+ name, dns_strtype(type));
+ return (status);
+ case DNS_NULLMX:
+ if (why)
+ vstring_sprintf(why, "Domain %s does not accept mail (nullMX)",
+ name);
+ SET_H_ERRNO(NO_DATA);
+ return (status);
+ case DNS_OK:
+ if (rrlist && dns_rr_filter_maps) {
+ if (dns_rr_filter_execute(rrlist) < 0) {
+ if (why)
+ vstring_sprintf(why,
+ "Error looking up name=%s type=%s: "
+ "Invalid DNS reply filter syntax",
+ name, dns_strtype(type));
+ dns_rr_free(*rrlist);
+ *rrlist = 0;
+ status = DNS_RETRY;
+ } else if (*rrlist == 0) {
+ if (why)
+ vstring_sprintf(why,
+ "Error looking up name=%s type=%s: "
+ "DNS reply filter drops all results",
+ name, dns_strtype(type));
+ status = DNS_POLICY;
+ }
+ }
+ return (status);
+ case DNS_RECURSE:
+ if (msg_verbose)
+ msg_info("dns_lookup: %s aliased to %s", name, cname);
+#if RES_USE_DNSSEC
+
+ /*
+ * Once an intermediate CNAME reply is not validated, all
+ * consequent RRs are deemed not validated, so we don't ask for
+ * further DNSSEC replies.
+ */
+ if (maybe_secure == 0)
+ flags &= ~RES_USE_DNSSEC;
+#endif
+ name = cname;
+ }
+ }
+ if (why)
+ vstring_sprintf(why, "Name server loop for %s", name);
+ msg_warn("dns_lookup: Name server loop for %s", name);
+ return (DNS_NOTFOUND);
+}
+
+/* dns_lookup_rl - DNS lookup interface with types list */
+
+int dns_lookup_rl(const char *name, unsigned flags, DNS_RR **rrlist,
+ VSTRING *fqdn, VSTRING *why, int *rcode,
+ int lflags,...)
+{
+ va_list ap;
+ unsigned type, next;
+ int status = DNS_NOTFOUND;
+ int hpref_status = INT_MIN;
+ VSTRING *hpref_rtext = 0;
+ int hpref_rcode;
+ int hpref_h_errno;
+ DNS_RR *rr;
+
+ /* Save intermediate highest-priority result. */
+#define SAVE_HPREF_STATUS() do { \
+ hpref_status = status; \
+ if (rcode) \
+ hpref_rcode = *rcode; \
+ if (why && status != DNS_OK) \
+ vstring_strcpy(hpref_rtext ? hpref_rtext : \
+ (hpref_rtext = vstring_alloc(VSTRING_LEN(why))), \
+ vstring_str(why)); \
+ hpref_h_errno = h_errno; \
+ } while (0)
+
+ /* Restore intermediate highest-priority result. */
+#define RESTORE_HPREF_STATUS() do { \
+ status = hpref_status; \
+ if (rcode) \
+ *rcode = hpref_rcode; \
+ if (why && status != DNS_OK) \
+ vstring_strcpy(why, vstring_str(hpref_rtext)); \
+ SET_H_ERRNO(hpref_h_errno); \
+ } while (0)
+
+ if (rrlist)
+ *rrlist = 0;
+ va_start(ap, lflags);
+ for (type = va_arg(ap, unsigned); type != 0; type = next) {
+ next = va_arg(ap, unsigned);
+ if (msg_verbose)
+ msg_info("lookup %s type %s flags %s",
+ name, dns_strtype(type), dns_str_resflags(flags));
+ status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
+ fqdn, why, rcode, lflags);
+ if (rrlist && rr)
+ *rrlist = dns_rr_append(*rrlist, rr);
+ if (status == DNS_OK) {
+ if (lflags & DNS_REQ_FLAG_STOP_OK)
+ break;
+ } else if (status == DNS_INVAL) {
+ if (lflags & DNS_REQ_FLAG_STOP_INVAL)
+ break;
+ } else if (status == DNS_POLICY) {
+ if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY))
+ break;
+ } else if (status == DNS_NULLMX) {
+ if (lflags & DNS_REQ_FLAG_STOP_NULLMX)
+ break;
+ }
+ /* XXX Stop after NXDOMAIN error. */
+ if (next == 0)
+ break;
+ if (status >= hpref_status)
+ SAVE_HPREF_STATUS(); /* save last info */
+ }
+ va_end(ap);
+ if (status < hpref_status)
+ RESTORE_HPREF_STATUS(); /* else report last info */
+ if (hpref_rtext)
+ vstring_free(hpref_rtext);
+ return (status);
+}
+
+/* dns_lookup_rv - DNS lookup interface with types vector */
+
+int dns_lookup_rv(const char *name, unsigned flags, DNS_RR **rrlist,
+ VSTRING *fqdn, VSTRING *why, int *rcode,
+ int lflags, unsigned *types)
+{
+ unsigned type, next;
+ int status = DNS_NOTFOUND;
+ int hpref_status = INT_MIN;
+ VSTRING *hpref_rtext = 0;
+ int hpref_rcode;
+ int hpref_h_errno;
+ DNS_RR *rr;
+
+ if (rrlist)
+ *rrlist = 0;
+ for (type = *types++; type != 0; type = next) {
+ next = *types++;
+ if (msg_verbose)
+ msg_info("lookup %s type %s flags %s",
+ name, dns_strtype(type), dns_str_resflags(flags));
+ status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
+ fqdn, why, rcode, lflags);
+ if (rrlist && rr)
+ *rrlist = dns_rr_append(*rrlist, rr);
+ if (status == DNS_OK) {
+ if (lflags & DNS_REQ_FLAG_STOP_OK)
+ break;
+ } else if (status == DNS_INVAL) {
+ if (lflags & DNS_REQ_FLAG_STOP_INVAL)
+ break;
+ } else if (status == DNS_POLICY) {
+ if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY))
+ break;
+ } else if (status == DNS_NULLMX) {
+ if (lflags & DNS_REQ_FLAG_STOP_NULLMX)
+ break;
+ }
+ /* XXX Stop after NXDOMAIN error. */
+ if (next == 0)
+ break;
+ if (status >= hpref_status)
+ SAVE_HPREF_STATUS(); /* save last info */
+ }
+ if (status < hpref_status)
+ RESTORE_HPREF_STATUS(); /* else report last info */
+ if (hpref_rtext)
+ vstring_free(hpref_rtext);
+ return (status);
+}
diff --git a/src/dns/dns_rr.c b/src/dns/dns_rr.c
new file mode 100644
index 0000000..b550788
--- /dev/null
+++ b/src/dns/dns_rr.c
@@ -0,0 +1,347 @@
+/*++
+/* NAME
+/* dns_rr 3
+/* SUMMARY
+/* resource record memory and list management
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* DNS_RR *dns_rr_create(qname, rname, type, class, ttl, preference,
+/* data, data_len)
+/* const char *qname;
+/* const char *rname;
+/* unsigned short type;
+/* unsigned short class;
+/* unsigned int ttl;
+/* unsigned preference;
+/* const char *data;
+/* size_t data_len;
+/*
+/* void dns_rr_free(list)
+/* DNS_RR *list;
+/*
+/* DNS_RR *dns_rr_copy(record)
+/* DNS_RR *record;
+/*
+/* DNS_RR *dns_rr_append(list, record)
+/* DNS_RR *list;
+/* DNS_RR *record;
+/*
+/* DNS_RR *dns_rr_sort(list, compar)
+/* DNS_RR *list
+/* int (*compar)(DNS_RR *, DNS_RR *);
+/*
+/* int dns_rr_compare_pref_ipv6(DNS_RR *a, DNS_RR *b)
+/* DNS_RR *list
+/* DNS_RR *list
+/*
+/* int dns_rr_compare_pref_ipv4(DNS_RR *a, DNS_RR *b)
+/* DNS_RR *list
+/* DNS_RR *list
+/*
+/* int dns_rr_compare_pref_any(DNS_RR *a, DNS_RR *b)
+/* DNS_RR *list
+/* DNS_RR *list
+/*
+/* DNS_RR *dns_rr_shuffle(list)
+/* DNS_RR *list;
+/*
+/* DNS_RR *dns_rr_remove(list, record)
+/* DNS_RR *list;
+/* DNS_RR *record;
+/* DESCRIPTION
+/* The routines in this module maintain memory for DNS resource record
+/* information, and maintain lists of DNS resource records.
+/*
+/* dns_rr_create() creates and initializes one resource record.
+/* The \fIqname\fR field specifies the query name.
+/* The \fIrname\fR field specifies the reply name.
+/* \fIpreference\fR is used for MX records; \fIdata\fR is a null
+/* pointer or specifies optional resource-specific data;
+/* \fIdata_len\fR is the amount of resource-specific data.
+/*
+/* dns_rr_free() releases the resource used by of zero or more
+/* resource records.
+/*
+/* dns_rr_copy() makes a copy of a resource record.
+/*
+/* dns_rr_append() appends a resource record to a (list of) resource
+/* record(s).
+/* A null input list is explicitly allowed.
+/*
+/* dns_rr_sort() sorts a list of resource records into ascending
+/* order according to a user-specified criterion. The result is the
+/* sorted list.
+/*
+/* dns_rr_compare_pref_XXX() are dns_rr_sort() helpers to sort
+/* records by their MX preference and by their address family.
+/*
+/* dns_rr_shuffle() randomly permutes a list of resource records.
+/*
+/* dns_rr_remove() removes the specified record from the specified list.
+/* The updated list is the result value.
+/* The record MUST be a list member.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <myrand.h>
+
+/* DNS library. */
+
+#include "dns.h"
+
+/* dns_rr_create - fill in resource record structure */
+
+DNS_RR *dns_rr_create(const char *qname, const char *rname,
+ ushort type, ushort class,
+ unsigned int ttl, unsigned pref,
+ const char *data, size_t data_len)
+{
+ DNS_RR *rr;
+
+ rr = (DNS_RR *) mymalloc(sizeof(*rr) + data_len - 1);
+ rr->qname = mystrdup(qname);
+ rr->rname = mystrdup(rname);
+ rr->type = type;
+ rr->class = class;
+ rr->ttl = ttl;
+ rr->dnssec_valid = 0;
+ rr->pref = pref;
+ if (data && data_len > 0)
+ memcpy(rr->data, data, data_len);
+ rr->data_len = data_len;
+ rr->next = 0;
+ return (rr);
+}
+
+/* dns_rr_free - destroy resource record structure */
+
+void dns_rr_free(DNS_RR *rr)
+{
+ if (rr) {
+ if (rr->next)
+ dns_rr_free(rr->next);
+ myfree(rr->qname);
+ myfree(rr->rname);
+ myfree((void *) rr);
+ }
+}
+
+/* dns_rr_copy - copy resource record */
+
+DNS_RR *dns_rr_copy(DNS_RR *src)
+{
+ ssize_t len = sizeof(*src) + src->data_len - 1;
+ DNS_RR *dst;
+
+ /*
+ * Combine struct assignment and data copy in one block copy operation.
+ */
+ dst = (DNS_RR *) mymalloc(len);
+ memcpy((void *) dst, (void *) src, len);
+ dst->qname = mystrdup(src->qname);
+ dst->rname = mystrdup(src->rname);
+ dst->next = 0;
+ return (dst);
+}
+
+/* dns_rr_append - append resource record to list */
+
+DNS_RR *dns_rr_append(DNS_RR *list, DNS_RR *rr)
+{
+ if (list == 0) {
+ list = rr;
+ } else {
+ list->next = dns_rr_append(list->next, rr);
+ }
+ return (list);
+}
+
+/* dns_rr_compare_pref_ipv6 - compare records by preference, ipv6 preferred */
+
+int dns_rr_compare_pref_ipv6(DNS_RR *a, DNS_RR *b)
+{
+ if (a->pref != b->pref)
+ return (a->pref - b->pref);
+#ifdef HAS_IPV6
+ if (a->type == b->type) /* 200412 */
+ return 0;
+ if (a->type == T_AAAA)
+ return (-1);
+ if (b->type == T_AAAA)
+ return (+1);
+#endif
+ return 0;
+}
+
+/* dns_rr_compare_pref_ipv4 - compare records by preference, ipv4 preferred */
+
+int dns_rr_compare_pref_ipv4(DNS_RR *a, DNS_RR *b)
+{
+ if (a->pref != b->pref)
+ return (a->pref - b->pref);
+#ifdef HAS_IPV6
+ if (a->type == b->type)
+ return 0;
+ if (a->type == T_AAAA)
+ return (+1);
+ if (b->type == T_AAAA)
+ return (-1);
+#endif
+ return 0;
+}
+
+/* dns_rr_compare_pref_any - compare records by preference, protocol-neutral */
+
+int dns_rr_compare_pref_any(DNS_RR *a, DNS_RR *b)
+{
+ if (a->pref != b->pref)
+ return (a->pref - b->pref);
+ return 0;
+}
+
+/* dns_rr_compare_pref - binary compatibility helper after name change */
+
+int dns_rr_compare_pref(DNS_RR *a, DNS_RR *b)
+{
+ return (dns_rr_compare_pref_ipv6(a, b));
+}
+
+/* dns_rr_sort_callback - glue function */
+
+static int (*dns_rr_sort_user) (DNS_RR *, DNS_RR *);
+
+static int dns_rr_sort_callback(const void *a, const void *b)
+{
+ DNS_RR *aa = *(DNS_RR **) a;
+ DNS_RR *bb = *(DNS_RR **) b;
+
+ return (dns_rr_sort_user(aa, bb));
+}
+
+/* dns_rr_sort - sort resource record list */
+
+DNS_RR *dns_rr_sort(DNS_RR *list, int (*compar) (DNS_RR *, DNS_RR *))
+{
+ int (*saved_user) (DNS_RR *, DNS_RR *);
+ DNS_RR **rr_array;
+ DNS_RR *rr;
+ int len;
+ int i;
+
+ /*
+ * Save state and initialize.
+ */
+ saved_user = dns_rr_sort_user;
+ dns_rr_sort_user = compar;
+
+ /*
+ * Build linear array with pointers to each list element.
+ */
+ for (len = 0, rr = list; rr != 0; len++, rr = rr->next)
+ /* void */ ;
+ rr_array = (DNS_RR **) mymalloc(len * sizeof(*rr_array));
+ for (len = 0, rr = list; rr != 0; len++, rr = rr->next)
+ rr_array[len] = rr;
+
+ /*
+ * Sort by user-specified criterion.
+ */
+ qsort((void *) rr_array, len, sizeof(*rr_array), dns_rr_sort_callback);
+
+ /*
+ * Fix the links.
+ */
+ for (i = 0; i < len - 1; i++)
+ rr_array[i]->next = rr_array[i + 1];
+ rr_array[i]->next = 0;
+ list = rr_array[0];
+
+ /*
+ * Cleanup.
+ */
+ myfree((void *) rr_array);
+ dns_rr_sort_user = saved_user;
+ return (list);
+}
+
+/* dns_rr_shuffle - shuffle resource record list */
+
+DNS_RR *dns_rr_shuffle(DNS_RR *list)
+{
+ DNS_RR **rr_array;
+ DNS_RR *rr;
+ int len;
+ int i;
+ int r;
+
+ /*
+ * Build linear array with pointers to each list element.
+ */
+ for (len = 0, rr = list; rr != 0; len++, rr = rr->next)
+ /* void */ ;
+ rr_array = (DNS_RR **) mymalloc(len * sizeof(*rr_array));
+ for (len = 0, rr = list; rr != 0; len++, rr = rr->next)
+ rr_array[len] = rr;
+
+ /*
+ * Shuffle resource records. Every element has an equal chance of landing
+ * in slot 0. After that every remaining element has an equal chance of
+ * landing in slot 1, ... This is exactly n! states for n! permutations.
+ */
+ for (i = 0; i < len - 1; i++) {
+ r = i + (myrand() % (len - i)); /* Victor&Son */
+ rr = rr_array[i];
+ rr_array[i] = rr_array[r];
+ rr_array[r] = rr;
+ }
+
+ /*
+ * Fix the links.
+ */
+ for (i = 0; i < len - 1; i++)
+ rr_array[i]->next = rr_array[i + 1];
+ rr_array[i]->next = 0;
+ list = rr_array[0];
+
+ /*
+ * Cleanup.
+ */
+ myfree((void *) rr_array);
+ return (list);
+}
+
+/* dns_rr_remove - remove record from list, return new list */
+
+DNS_RR *dns_rr_remove(DNS_RR *list, DNS_RR *record)
+{
+ if (list == 0)
+ msg_panic("dns_rr_remove: record not found");
+
+ if (list == record) {
+ list = record->next;
+ record->next = 0;
+ dns_rr_free(record);
+ } else {
+ list->next = dns_rr_remove(list->next, record);
+ }
+ return (list);
+}
diff --git a/src/dns/dns_rr_eq_sa.c b/src/dns/dns_rr_eq_sa.c
new file mode 100644
index 0000000..8113d6b
--- /dev/null
+++ b/src/dns/dns_rr_eq_sa.c
@@ -0,0 +1,157 @@
+/*++
+/* NAME
+/* dns_rr_eq_sa 3
+/* SUMMARY
+/* compare resource record with socket address
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* int dns_rr_eq_sa(DNS_RR *rr, struct sockaddr *sa)
+/* DNS_RR *rr;
+/* struct sockaddr *sa;
+/*
+/* int DNS_RR_EQ_SA(DNS_RR *rr, struct sockaddr *sa)
+/* DNS_RR *rr;
+/* struct sockaddr *sa;
+/* DESCRIPTION
+/* dns_rr_eq_sa() compares a DNS resource record with a socket
+/* address. The result is non-zero when the resource type
+/* matches the socket address family, and when the network
+/* address information is identical.
+/*
+/* DNS_RR_EQ_SA() is an unsafe macro version for those who live fast.
+/*
+/* Arguments:
+/* .IP rr
+/* DNS resource record pointer.
+/* .IP sa
+/* Binary address pointer.
+/* DIAGNOSTICS
+/* Panic: unknown socket address family.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <sock_addr.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* dns_rr_eq_sa - compare resource record with socket address */
+
+int dns_rr_eq_sa(DNS_RR *rr, struct sockaddr *sa)
+{
+ const char *myname = "dns_rr_eq_sa";
+
+ if (sa->sa_family == AF_INET) {
+ return (rr->type == T_A
+ && SOCK_ADDR_IN_ADDR(sa).s_addr == IN_ADDR(rr->data).s_addr);
+#ifdef HAS_IPV6
+ } else if (sa->sa_family == AF_INET6) {
+ return (rr->type == T_AAAA
+ && memcmp((void *) &SOCK_ADDR_IN6_ADDR(sa),
+ rr->data, rr->data_len) == 0);
+#endif
+ } else {
+ msg_panic("%s: unsupported socket address family type: %d",
+ myname, sa->sa_family);
+ }
+}
+
+ /*
+ * Stand-alone test program.
+ */
+#ifdef TEST
+#include <stdlib.h>
+#include <vstream.h>
+#include <myaddrinfo.h>
+#include <inet_proto.h>
+#include <mymalloc.h>
+
+static const char *myname;
+
+static NORETURN usage(void)
+{
+ msg_fatal("usage: %s hostname address", myname);
+}
+
+static int compare_family(const void *a, const void *b)
+{
+ struct addrinfo *resa = *(struct addrinfo **) a;
+ struct addrinfo *resb = *(struct addrinfo **) b;
+
+ return (resa->ai_family - resb->ai_family);
+}
+
+int main(int argc, char **argv)
+{
+ MAI_HOSTADDR_STR hostaddr;
+ DNS_RR *rr;
+ struct addrinfo *res0;
+ struct addrinfo *res1;
+ struct addrinfo *res;
+ struct addrinfo **resv;
+ size_t len, n;
+ int aierr;
+
+ myname = argv[0];
+
+ if (argc < 3)
+ usage();
+
+ inet_proto_init(argv[0], INET_PROTO_NAME_ALL);
+
+ while (*++argv) {
+ if (argv[1] == 0)
+ usage();
+
+ if ((aierr = hostaddr_to_sockaddr(argv[1], (char *) 0, 0, &res1)) != 0)
+ msg_fatal("host address %s: %s", argv[1], MAI_STRERROR(aierr));
+ if ((rr = dns_sa_to_rr(argv[1], 0, res1->ai_addr)) == 0)
+ msg_fatal("dns_sa_to_rr: %m");
+ freeaddrinfo(res1);
+
+ if ((aierr = hostname_to_sockaddr(argv[0], (char *) 0, 0, &res0)) != 0)
+ msg_fatal("host name %s: %s", argv[0], MAI_STRERROR(aierr));
+ for (len = 0, res = res0; res != 0; res = res->ai_next)
+ len += 1;
+ resv = (struct addrinfo **) mymalloc(len * sizeof(*resv));
+ for (len = 0, res = res0; res != 0; res = res->ai_next)
+ resv[len++] = res;
+ qsort((void *) resv, len, sizeof(*resv), compare_family);
+ for (n = 0; n < len; n++) {
+ SOCKADDR_TO_HOSTADDR(resv[n]->ai_addr, resv[n]->ai_addrlen,
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ vstream_printf("%s =?= %s\n", hostaddr.buf, argv[1]);
+ vstream_printf("tested by function: %s\n",
+ dns_rr_eq_sa(rr, resv[n]->ai_addr) ?
+ "yes" : "no");
+ vstream_printf("tested by macro: %s\n",
+ DNS_RR_EQ_SA(rr, resv[n]->ai_addr) ?
+ "yes" : "no");
+ }
+ dns_rr_free(rr);
+ freeaddrinfo(res0);
+ myfree((void *) resv);
+ vstream_fflush(VSTREAM_OUT);
+ argv += 1;
+ }
+ return (0);
+}
+
+#endif
diff --git a/src/dns/dns_rr_eq_sa.in b/src/dns/dns_rr_eq_sa.in
new file mode 100644
index 0000000..89834d5
--- /dev/null
+++ b/src/dns/dns_rr_eq_sa.in
@@ -0,0 +1,4 @@
+spike.porcupine.org 168.100.189.2
+spike.porcupine.org 168.100.189.3
+spike.porcupine.org 2604:8d00:189::2
+spike.porcupine.org 2604:8d00:189::3
diff --git a/src/dns/dns_rr_eq_sa.ref b/src/dns/dns_rr_eq_sa.ref
new file mode 100644
index 0000000..75fb0c3
--- /dev/null
+++ b/src/dns/dns_rr_eq_sa.ref
@@ -0,0 +1,24 @@
+168.100.189.2 =?= 168.100.189.2
+tested by function: yes
+tested by macro: yes
+2604:8d00:189::2 =?= 168.100.189.2
+tested by function: no
+tested by macro: no
+168.100.189.2 =?= 168.100.189.3
+tested by function: no
+tested by macro: no
+2604:8d00:189::2 =?= 168.100.189.3
+tested by function: no
+tested by macro: no
+168.100.189.2 =?= 2604:8d00:189::2
+tested by function: no
+tested by macro: no
+2604:8d00:189::2 =?= 2604:8d00:189::2
+tested by function: yes
+tested by macro: yes
+168.100.189.2 =?= 2604:8d00:189::3
+tested by function: no
+tested by macro: no
+2604:8d00:189::2 =?= 2604:8d00:189::3
+tested by function: no
+tested by macro: no
diff --git a/src/dns/dns_rr_filter.c b/src/dns/dns_rr_filter.c
new file mode 100644
index 0000000..a02d3de
--- /dev/null
+++ b/src/dns/dns_rr_filter.c
@@ -0,0 +1,150 @@
+/*++
+/* NAME
+/* dns_rr_filter 3
+/* SUMMARY
+/* DNS resource record filter
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* void dns_rr_filter_compile(title, map_names)
+/* const char *title;
+/* const char *map_names;
+/* INTERNAL INTERFACES
+/* int dns_rr_filter_execute(rrlist)
+/* DNS_RR **rrlist;
+/*
+/* MAPS *dns_rr_filter_maps;
+/* DESCRIPTION
+/* This module implements a simple filter for dns_lookup*()
+/* results.
+/*
+/* dns_rr_filter_compile() initializes a result filter. The
+/* title and map_names arguments are as with maps_create().
+/* This function may be invoked more than once; only the last
+/* filter takes effect.
+/*
+/* dns_rr_filter_execute() converts each resource record in the
+/* specified list with dns_strrecord to ASCII form and matches
+/* that against the specified maps. If a match is found it
+/* executes the corresponding action. Currently, only the
+/* "ignore" action is implemented. This removes the matched
+/* record from the list. The result is 0 in case of success,
+/* -1 in case of error.
+/*
+/* dns_rr_filter_maps is updated by dns_rr_filter_compile().
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <vstring.h>
+#include <myaddrinfo.h>
+
+ /*
+ * Global library.
+ */
+#include <maps.h>
+
+ /*
+ * DNS library.
+ */
+#define LIBDNS_INTERNAL
+#include <dns.h>
+
+ /*
+ * Application-specific.
+ */
+MAPS *dns_rr_filter_maps;
+
+static DNS_RR dns_rr_filter_error[1];
+
+#define STR vstring_str
+
+/* dns_rr_filter_compile - compile dns result filter */
+
+void dns_rr_filter_compile(const char *title, const char *map_names)
+{
+ if (dns_rr_filter_maps != 0)
+ maps_free(dns_rr_filter_maps);
+ dns_rr_filter_maps = maps_create(title, map_names,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
+}
+
+/* dns_rr_action - execute action from filter map */
+
+static DNS_RR *dns_rr_action(const char *cmd, DNS_RR *rr, const char *rr_text)
+{
+ const char *cmd_args = cmd + strcspn(cmd, " \t");
+ int cmd_len = cmd_args - cmd;
+
+ while (*cmd_args && ISSPACE(*cmd_args))
+ cmd_args++;
+
+#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
+
+ if (STREQUAL(cmd, "IGNORE", cmd_len)) {
+ msg_info("ignoring DNS RR: %s", rr_text);
+ return (0);
+ } else {
+ msg_warn("%s: unknown DNS filter action: \"%s\"",
+ dns_rr_filter_maps->title, cmd);
+ return (dns_rr_filter_error);
+ }
+ return (rr);
+}
+
+/* dns_rr_filter_execute - filter DNS lookup result */
+
+int dns_rr_filter_execute(DNS_RR **rrlist)
+{
+ static VSTRING *buf = 0;
+ DNS_RR **rrp;
+ DNS_RR *rr;
+ const char *map_res;
+ DNS_RR *act_res;
+
+ /*
+ * Convert the resource record to string form, then search the maps for a
+ * matching action.
+ */
+ if (buf == 0)
+ buf = vstring_alloc(100);
+ for (rrp = rrlist; (rr = *rrp) != 0; /* see below */ ) {
+ map_res = maps_find(dns_rr_filter_maps, dns_strrecord(buf, rr),
+ DICT_FLAG_NONE);
+ if (map_res != 0) {
+ if ((act_res = dns_rr_action(map_res, rr, STR(buf))) == 0) {
+ *rrp = rr->next; /* do not advance in the list */
+ rr->next = 0;
+ dns_rr_free(rr);
+ continue;
+ } else if (act_res == dns_rr_filter_error) {
+ return (-1);
+ }
+ } else if (dns_rr_filter_maps->error) {
+ return (-1);
+ }
+ rrp = &(rr->next); /* do advance in the list */
+ }
+ return (0);
+}
diff --git a/src/dns/dns_rr_to_pa.c b/src/dns/dns_rr_to_pa.c
new file mode 100644
index 0000000..bfd93a0
--- /dev/null
+++ b/src/dns/dns_rr_to_pa.c
@@ -0,0 +1,113 @@
+/*++
+/* NAME
+/* dns_rr_to_pa 3
+/* SUMMARY
+/* resource record to printable address
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* const char *dns_rr_to_pa(rr, hostaddr)
+/* DNS_RR *rr;
+/* MAI_HOSTADDR_STR *hostaddr;
+/* DESCRIPTION
+/* dns_rr_to_pa() converts the address in a DNS resource record
+/* into printable form and returns a pointer to the result.
+/*
+/* Arguments:
+/* .IP rr
+/* The DNS resource record.
+/* .IP hostaddr
+/* Storage for the printable address.
+/* DIAGNOSTICS
+/* The result is null in case of problems, with errno set
+/* to indicate the nature of the problem.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* dns_rr_to_pa - resource record to printable address */
+
+const char *dns_rr_to_pa(DNS_RR *rr, MAI_HOSTADDR_STR *hostaddr)
+{
+ if (rr->type == T_A) {
+ return (inet_ntop(AF_INET, rr->data, hostaddr->buf,
+ sizeof(hostaddr->buf)));
+#ifdef HAS_IPV6
+ } else if (rr->type == T_AAAA) {
+ return (inet_ntop(AF_INET6, rr->data, hostaddr->buf,
+ sizeof(hostaddr->buf)));
+#endif
+ } else {
+ errno = EAFNOSUPPORT;
+ return (0);
+ }
+}
+
+ /*
+ * Stand-alone test program.
+ */
+#ifdef TEST
+#include <vstream.h>
+#include <myaddrinfo.h>
+
+static const char *myname;
+
+static NORETURN usage(void)
+{
+ msg_fatal("usage: %s dnsaddrtype hostname", myname);
+}
+
+int main(int argc, char **argv)
+{
+ DNS_RR *rr;
+ MAI_HOSTADDR_STR hostaddr;
+ VSTRING *why;
+ int type;
+
+ myname = argv[0];
+ if (argc < 3)
+ usage();
+ why = vstring_alloc(1);
+
+ while (*++argv) {
+ if (argv[1] == 0)
+ usage();
+ if ((type = dns_type(argv[0])) == 0)
+ usage();
+ if (dns_lookup(argv[1], type, 0, &rr, (VSTRING *) 0, why) != DNS_OK)
+ msg_fatal("%s: %s", argv[1], vstring_str(why));
+ if (dns_rr_to_pa(rr, &hostaddr) == 0)
+ msg_fatal("dns_rr_to_sa: %m");
+ vstream_printf("%s -> %s\n", argv[1], hostaddr.buf);
+ vstream_fflush(VSTREAM_OUT);
+ argv += 1;
+ dns_rr_free(rr);
+ }
+ vstring_free(why);
+ return (0);
+}
+
+#endif
diff --git a/src/dns/dns_rr_to_pa.in b/src/dns/dns_rr_to_pa.in
new file mode 100644
index 0000000..28d0e77
--- /dev/null
+++ b/src/dns/dns_rr_to_pa.in
@@ -0,0 +1,2 @@
+a spike.porcupine.org
+aaaa spike.porcupine.org
diff --git a/src/dns/dns_rr_to_pa.ref b/src/dns/dns_rr_to_pa.ref
new file mode 100644
index 0000000..86c022d
--- /dev/null
+++ b/src/dns/dns_rr_to_pa.ref
@@ -0,0 +1,2 @@
+spike.porcupine.org -> 168.100.189.2
+spike.porcupine.org -> 2604:8d00:189::2
diff --git a/src/dns/dns_rr_to_sa.c b/src/dns/dns_rr_to_sa.c
new file mode 100644
index 0000000..f264260
--- /dev/null
+++ b/src/dns/dns_rr_to_sa.c
@@ -0,0 +1,163 @@
+/*++
+/* NAME
+/* dns_rr_to_sa 3
+/* SUMMARY
+/* resource record to socket address
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* int dns_rr_to_sa(rr, port, sa, sa_length)
+/* DNS_RR *rr;
+/* unsigned port;
+/* struct sockaddr *sa;
+/* SOCKADDR_SIZE *sa_length;
+/* DESCRIPTION
+/* dns_rr_to_sa() converts the address in a DNS resource record into
+/* a socket address of the corresponding type.
+/*
+/* Arguments:
+/* .IP rr
+/* DNS resource record pointer.
+/* .IP port
+/* TCP or UDP port, network byte order.
+/* .IP sa
+/* Socket address pointer.
+/* .IP sa_length
+/* On input, the available socket address storage space.
+/* On output, the amount of space actually used.
+/* DIAGNOSTICS
+/* The result is non-zero in case of problems, with the
+/* error type returned via the errno variable.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* dns_rr_to_sa - resource record to socket address */
+
+int dns_rr_to_sa(DNS_RR *rr, unsigned port, struct sockaddr *sa,
+ SOCKADDR_SIZE *sa_length)
+{
+ SOCKADDR_SIZE sock_addr_len;
+
+ if (rr->type == T_A) {
+ if (rr->data_len != sizeof(SOCK_ADDR_IN_ADDR(sa))) {
+ errno = EINVAL;
+ return (-1);
+ } else if ((sock_addr_len = sizeof(*SOCK_ADDR_IN_PTR(sa))) > *sa_length) {
+ errno = ENOSPC;
+ return (-1);
+ } else {
+ memset((void *) SOCK_ADDR_IN_PTR(sa), 0, sock_addr_len);
+ SOCK_ADDR_IN_FAMILY(sa) = AF_INET;
+ SOCK_ADDR_IN_PORT(sa) = port;
+ SOCK_ADDR_IN_ADDR(sa) = IN_ADDR(rr->data);
+#ifdef HAS_SA_LEN
+ sa->sa_len = sock_addr_len;
+#endif
+ *sa_length = sock_addr_len;
+ return (0);
+ }
+#ifdef HAS_IPV6
+ } else if (rr->type == T_AAAA) {
+ if (rr->data_len != sizeof(SOCK_ADDR_IN6_ADDR(sa))) {
+ errno = EINVAL;
+ return (-1);
+ } else if ((sock_addr_len = sizeof(*SOCK_ADDR_IN6_PTR(sa))) > *sa_length) {
+ errno = ENOSPC;
+ return (-1);
+ } else {
+ memset((void *) SOCK_ADDR_IN6_PTR(sa), 0, sock_addr_len);
+ SOCK_ADDR_IN6_FAMILY(sa) = AF_INET6;
+ SOCK_ADDR_IN6_PORT(sa) = port;
+ SOCK_ADDR_IN6_ADDR(sa) = IN6_ADDR(rr->data);
+#ifdef HAS_SA_LEN
+ sa->sa_len = sock_addr_len;
+#endif
+ *sa_length = sock_addr_len;
+ return (0);
+ }
+#endif
+ } else {
+ errno = EAFNOSUPPORT;
+ return (-1);
+ }
+}
+
+ /*
+ * Stand-alone test program.
+ */
+#ifdef TEST
+#include <stdlib.h>
+
+#include <stringops.h>
+#include <vstream.h>
+#include <myaddrinfo.h>
+
+static const char *myname;
+
+static NORETURN usage(void)
+{
+ msg_fatal("usage: %s dnsaddrtype hostname portnumber", myname);
+}
+
+int main(int argc, char **argv)
+{
+ DNS_RR *rr;
+ MAI_HOSTADDR_STR hostaddr;
+ MAI_SERVPORT_STR portnum;
+ struct sockaddr_storage ss;
+ struct sockaddr *sa = (struct sockaddr *) &ss;
+ SOCKADDR_SIZE sa_length = sizeof(ss);
+ VSTRING *why;
+ int type;
+ int port;
+
+ myname = argv[0];
+ if (argc < 4)
+ usage();
+ why = vstring_alloc(1);
+
+ while (*++argv) {
+ if (argv[1] == 0 || argv[2] == 0)
+ usage();
+ if ((type = dns_type(argv[0])) == 0)
+ usage();
+ if (!alldig(argv[2]) || (port = atoi(argv[2])) > 65535)
+ usage();
+ if (dns_lookup(argv[1], type, 0, &rr, (VSTRING *) 0, why) != DNS_OK)
+ msg_fatal("%s: %s", argv[1], vstring_str(why));
+ sa_length = sizeof(ss);
+ if (dns_rr_to_sa(rr, htons(port), sa, &sa_length) != 0)
+ msg_fatal("dns_rr_to_sa: %m");
+ SOCKADDR_TO_HOSTADDR(sa, sa_length, &hostaddr, &portnum, 0);
+ vstream_printf("%s %s -> %s %s\n",
+ argv[1], argv[2], hostaddr.buf, portnum.buf);
+ vstream_fflush(VSTREAM_OUT);
+ argv += 2;
+ dns_rr_free(rr);
+ }
+ vstring_free(why);
+ return (0);
+}
+
+#endif
diff --git a/src/dns/dns_rr_to_sa.in b/src/dns/dns_rr_to_sa.in
new file mode 100644
index 0000000..1fff6c0
--- /dev/null
+++ b/src/dns/dns_rr_to_sa.in
@@ -0,0 +1,2 @@
+a spike.porcupine.org 25
+aaaa spike.porcupine.org 25
diff --git a/src/dns/dns_rr_to_sa.ref b/src/dns/dns_rr_to_sa.ref
new file mode 100644
index 0000000..217b9a2
--- /dev/null
+++ b/src/dns/dns_rr_to_sa.ref
@@ -0,0 +1,2 @@
+spike.porcupine.org 25 -> 168.100.189.2 25
+spike.porcupine.org 25 -> 2604:8d00:189::2 25
diff --git a/src/dns/dns_sa_to_rr.c b/src/dns/dns_sa_to_rr.c
new file mode 100644
index 0000000..6b9efcc
--- /dev/null
+++ b/src/dns/dns_sa_to_rr.c
@@ -0,0 +1,138 @@
+/*++
+/* NAME
+/* dns_sa_to_rr 3
+/* SUMMARY
+/* socket address to resource record
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* DNS_RR *dns_sa_to_rr(hostname, pref, sa)
+/* const char *hostname;
+/* unsigned pref;
+/* struct sockaddr *sa;
+/* DESCRIPTION
+/* dns_sa_to_rr() converts a socket address into a DNS resource record.
+/*
+/* Arguments:
+/* .IP hostname
+/* The resource record host name. This will be both the qname
+/* and the rname in the synthetic DNS resource record.
+/* .IP pref
+/* The resource record MX host preference, if applicable.
+/* .IP sa
+/* Binary address.
+/* DIAGNOSTICS
+/* The result is a null pointer in case of problems, with the
+/* errno variable set to indicate the problem type.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* dns_sa_to_rr - socket address to resource record */
+
+DNS_RR *dns_sa_to_rr(const char *hostname, unsigned pref, struct sockaddr *sa)
+{
+#define DUMMY_TTL 0
+
+ if (sa->sa_family == AF_INET) {
+ return (dns_rr_create(hostname, hostname, T_A, C_IN, DUMMY_TTL, pref,
+ (char *) &SOCK_ADDR_IN_ADDR(sa),
+ sizeof(SOCK_ADDR_IN_ADDR(sa))));
+#ifdef HAS_IPV6
+ } else if (sa->sa_family == AF_INET6) {
+ return (dns_rr_create(hostname, hostname, T_AAAA, C_IN, DUMMY_TTL, pref,
+ (char *) &SOCK_ADDR_IN6_ADDR(sa),
+ sizeof(SOCK_ADDR_IN6_ADDR(sa))));
+#endif
+ } else {
+ errno = EAFNOSUPPORT;
+ return (0);
+ }
+}
+
+ /*
+ * Stand-alone test program.
+ */
+#ifdef TEST
+#include <stdlib.h>
+#include <vstream.h>
+#include <myaddrinfo.h>
+#include <inet_proto.h>
+#include <mymalloc.h>
+
+static const char *myname;
+
+static NORETURN usage(void)
+{
+ msg_fatal("usage: %s hostname", myname);
+}
+
+static int compare_family(const void *a, const void *b)
+{
+ struct addrinfo *resa = *(struct addrinfo **) a;
+ struct addrinfo *resb = *(struct addrinfo **) b;
+
+ return (resa->ai_family - resb->ai_family);
+}
+
+int main(int argc, char **argv)
+{
+ MAI_HOSTADDR_STR hostaddr;
+ struct addrinfo *res0;
+ struct addrinfo *res;
+ struct addrinfo **resv;
+ size_t len, n;
+ DNS_RR *rr;
+ int aierr;
+
+ myname = argv[0];
+ if (argc < 2)
+ usage();
+
+ inet_proto_init(argv[0], INET_PROTO_NAME_ALL);
+
+ while (*++argv) {
+ if ((aierr = hostname_to_sockaddr(argv[0], (char *) 0, 0, &res0)) != 0)
+ msg_fatal("%s: %s", argv[0], MAI_STRERROR(aierr));
+ for (len = 0, res = res0; res != 0; res = res->ai_next)
+ len += 1;
+ resv = (struct addrinfo **) mymalloc(len * sizeof(*resv));
+ for (len = 0, res = res0; res != 0; res = res->ai_next)
+ resv[len++] = res;
+ qsort((void *) resv, len, sizeof(*resv), compare_family);
+ for (n = 0; n < len; n++) {
+ if ((rr = dns_sa_to_rr(argv[0], 0, resv[n]->ai_addr)) == 0)
+ msg_fatal("dns_sa_to_rr: %m");
+ if (dns_rr_to_pa(rr, &hostaddr) == 0)
+ msg_fatal("dns_rr_to_pa: %m");
+ vstream_printf("%s -> %s\n", argv[0], hostaddr.buf);
+ vstream_fflush(VSTREAM_OUT);
+ dns_rr_free(rr);
+ }
+ freeaddrinfo(res0);
+ myfree((void *) resv);
+ }
+ return (0);
+}
+
+#endif
diff --git a/src/dns/dns_sa_to_rr.in b/src/dns/dns_sa_to_rr.in
new file mode 100644
index 0000000..4f83a7d
--- /dev/null
+++ b/src/dns/dns_sa_to_rr.in
@@ -0,0 +1 @@
+spike.porcupine.org
diff --git a/src/dns/dns_sa_to_rr.ref b/src/dns/dns_sa_to_rr.ref
new file mode 100644
index 0000000..86c022d
--- /dev/null
+++ b/src/dns/dns_sa_to_rr.ref
@@ -0,0 +1,2 @@
+spike.porcupine.org -> 168.100.189.2
+spike.porcupine.org -> 2604:8d00:189::2
diff --git a/src/dns/dns_sec.c b/src/dns/dns_sec.c
new file mode 100644
index 0000000..849627e
--- /dev/null
+++ b/src/dns/dns_sec.c
@@ -0,0 +1,144 @@
+/*++
+/* NAME
+/* dns_sec 3
+/* SUMMARY
+/* DNSSEC validation availability
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* DNS_SEC_STATS_SET(
+/* int flags)
+/*
+/* DNS_SEC_STATS_TEST(
+/* int flags)
+/*
+/* void dns_sec_probe(
+/* int rflags)
+/* DESCRIPTION
+/* This module maintains information about the availability of
+/* DNSSEC validation, in global flags that summarize
+/* process-lifetime history.
+/* .IP DNS_SEC_FLAG_AVAILABLE
+/* The process has received at least one DNSSEC validated
+/* response to a query that requested DNSSEC validation.
+/* .IP DNS_SEC_FLAG_DONT_PROBE
+/* The process has sent a DNSSEC probe (see below), or DNSSEC
+/* probing is disabled by configuration.
+/* .PP
+/* DNS_SEC_STATS_SET() sets one or more DNS_SEC_FLAG_* flags,
+/* and DNS_SEC_STATS_TEST() returns non-zero if any of the
+/* specified flags is set.
+/*
+/* dns_sec_probe() generates a query to the target specified
+/* with the \fBdnssec_probe\fR configuration parameter. It
+/* sets the DNS_SEC_FLAG_DONT_PROBE flag, and it calls
+/* dns_lookup() which sets DNS_SEC_FLAG_AVAILABLE if it receives
+/* a DNSSEC validated response. Preconditions:
+/* .IP \(bu
+/* The rflags argument must request DNSSEC validation (in the
+/* same manner as dns_lookup() rflags argument).
+/* .IP \(bu
+/* The DNS_SEC_FLAG_AVAILABLE and DNS_SEC_FLAG_DONT_PROBE
+/* flags must be false.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <mymalloc.h>
+#include <split_at.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+
+ /*
+ * DNS library.
+ */
+#include <dns.h>
+
+int dns_sec_stats;
+
+/* dns_sec_probe - send a probe to establish DNSSEC viability */
+
+void dns_sec_probe(int rflags)
+{
+ const char myname[] = "dns_sec_probe";
+ char *saved_dnssec_probe;
+ char *qname;
+ int qtype;
+ DNS_RR *rrlist = 0;
+ int dns_status;
+ VSTRING *why;
+
+ /*
+ * Sanity checks.
+ */
+ if (!DNS_WANT_DNSSEC_VALIDATION(rflags))
+ msg_panic("%s: DNSSEC is not requested", myname);
+ if (DNS_SEC_STATS_TEST(DNS_SEC_FLAG_DONT_PROBE))
+ msg_panic("%s: DNSSEC probe was already sent, or probing is disabled",
+ myname);
+ if (DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE))
+ msg_panic("%s: already have validated DNS response", myname);
+
+ /*
+ * Don't recurse.
+ */
+ DNS_SEC_STATS_SET(DNS_SEC_FLAG_DONT_PROBE);
+
+ /*
+ * Don't probe.
+ */
+ if (*var_dnssec_probe == 0)
+ return;
+
+ /*
+ * Parse the probe spec. Format is type:resource.
+ */
+ saved_dnssec_probe = mystrdup(var_dnssec_probe);
+ if ((qname = split_at(saved_dnssec_probe, ':')) == 0 || *qname == 0
+ || (qtype = dns_type(saved_dnssec_probe)) == 0)
+ msg_fatal("malformed %s value: %s format is qtype:qname",
+ VAR_DNSSEC_PROBE, var_dnssec_probe);
+
+ why = vstring_alloc(100);
+ dns_status = dns_lookup(qname, qtype, rflags, &rrlist, (VSTRING *) 0, why);
+ if (!DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE))
+ msg_warn("DNSSEC validation may be unavailable");
+ else if (msg_verbose)
+ msg_info(VAR_DNSSEC_PROBE
+ " '%s' received a response that is DNSSEC validated",
+ var_dnssec_probe);
+ switch (dns_status) {
+ default:
+ if (!DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE))
+ msg_warn("reason: " VAR_DNSSEC_PROBE
+ " '%s' received a response that is not DNSSEC validated",
+ var_dnssec_probe);
+ if (rrlist)
+ dns_rr_free(rrlist);
+ break;
+ case DNS_RETRY:
+ case DNS_FAIL:
+ msg_warn("reason: " VAR_DNSSEC_PROBE " '%s' received no response: %s",
+ var_dnssec_probe, vstring_str(why));
+ break;
+ }
+ myfree(saved_dnssec_probe);
+ vstring_free(why);
+}
diff --git a/src/dns/dns_str_resflags.c b/src/dns/dns_str_resflags.c
new file mode 100644
index 0000000..df32345
--- /dev/null
+++ b/src/dns/dns_str_resflags.c
@@ -0,0 +1,124 @@
+/*++
+/* NAME
+/* dns_str_resflags 3
+/* SUMMARY
+/* convert resolver flags to printable form
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* const char *dns_str_resflags(mask)
+/* unsigned long mask;
+/* DESCRIPTION
+/* dns_str_resflags() converts RES_* resolver(5) flags from internal
+/* form to printable string. Individual flag names are separated
+/* with '|'. The result is overwritten with each call.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+
+ /*
+ * Utility library.
+ */
+#include <name_mask.h>
+
+ /*
+ * DNS library.
+ */
+#include <dns.h>
+
+ /*
+ * Application-specific.
+ */
+
+ /*
+ * This list overlaps with dns_res_opt_masks[] in smtp.c, but there we
+ * permit only a small subset of all possible flags.
+ */
+static const LONG_NAME_MASK resflag_table[] = {
+ "RES_INIT", RES_INIT,
+ "RES_DEBUG", RES_DEBUG,
+ "RES_AAONLY", RES_AAONLY,
+ "RES_USEVC", RES_USEVC,
+ "RES_PRIMARY", RES_PRIMARY,
+ "RES_IGNTC", RES_IGNTC,
+ "RES_RECURSE", RES_RECURSE,
+ "RES_DEFNAMES", RES_DEFNAMES,
+ "RES_STAYOPEN", RES_STAYOPEN,
+ "RES_DNSRCH", RES_DNSRCH,
+#ifdef RES_INSECURE1
+ "RES_INSECURE1", RES_INSECURE1,
+#endif
+#ifdef RES_INSECURE2
+ "RES_INSECURE2", RES_INSECURE2,
+#endif
+ "RES_NOALIASES", RES_NOALIASES,
+#ifdef RES_USE_INET6
+ "RES_USE_INET6", RES_USE_INET6,
+#endif
+#ifdef RES_ROTATE
+ "RES_ROTATE", RES_ROTATE,
+#endif
+#ifdef RES_NOCHECKNAME
+ "RES_NOCHECKNAME", RES_NOCHECKNAME,
+#endif
+ "RES_USE_EDNS0", RES_USE_EDNS0,
+ "RES_USE_DNSSEC", RES_USE_DNSSEC,
+#ifdef RES_KEEPTSIG
+ "RES_KEEPTSIG", RES_KEEPTSIG,
+#endif
+#ifdef RES_BLAST
+ "RES_BLAST", RES_BLAST,
+#endif
+#ifdef RES_USEBSTRING
+ "RES_USEBSTRING", RES_USEBSTRING,
+#endif
+#ifdef RES_NSID
+ "RES_NSID", RES_NSID,
+#endif
+#ifdef RES_NOIP6DOTINT
+ "RES_NOIP6DOTINT", RES_NOIP6DOTINT,
+#endif
+#ifdef RES_USE_DNAME
+ "RES_USE_DNAME", RES_USE_DNAME,
+#endif
+#ifdef RES_NO_NIBBLE2
+ "RES_NO_NIBBLE2", RES_NO_NIBBLE2,
+#endif
+#ifdef RES_SNGLKUP
+ "RES_SNGLKUP", RES_SNGLKUP,
+#endif
+#ifdef RES_SNGLKUPREOP
+ "RES_SNGLKUPREOP", RES_SNGLKUPREOP,
+#endif
+#ifdef RES_NOTLDQUERY
+ "RES_NOTLDQUERY", RES_NOTLDQUERY,
+#endif
+ 0,
+};
+
+/* dns_str_resflags - convert RES_* resolver flags to printable form */
+
+const char *dns_str_resflags(unsigned long mask)
+{
+ static VSTRING *buf;
+
+ if (buf == 0)
+ buf = vstring_alloc(20);
+ return (str_long_name_mask_opt(buf, "dsns_str_resflags", resflag_table,
+ mask, NAME_MASK_NUMBER | NAME_MASK_PIPE));
+}
diff --git a/src/dns/dns_strerror.c b/src/dns/dns_strerror.c
new file mode 100644
index 0000000..9e56d3b
--- /dev/null
+++ b/src/dns/dns_strerror.c
@@ -0,0 +1,69 @@
+/*++
+/* NAME
+/* dns_strerror 3
+/* SUMMARY
+/* name service lookup error code to string
+/* SYNOPSIS
+/* #include <dhs.h>
+/*
+/* const char *dns_strerror(code)
+/* int code;
+/* DESCRIPTION
+/* dns_strerror() maps a name service lookup error to printable string.
+/* The result is for read-only purposes, and unknown codes share a
+/* common string buffer.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <netdb.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+
+/* DNS library. */
+
+#include "dns.h"
+
+ /*
+ * Mapping from error code to printable string. The herror() routine does
+ * something similar, but has output only to the stderr stream.
+ */
+struct dns_error_map {
+ unsigned error;
+ const char *text;
+};
+
+static struct dns_error_map dns_error_map[] = {
+ HOST_NOT_FOUND, "Host not found",
+ TRY_AGAIN, "Host not found, try again",
+ NO_RECOVERY, "Non-recoverable error",
+ NO_DATA, "Host found but no data record of requested type",
+};
+
+/* dns_strerror - map resolver error code to printable string */
+
+const char *dns_strerror(unsigned error)
+{
+ static VSTRING *unknown = 0;
+ unsigned i;
+
+ for (i = 0; i < sizeof(dns_error_map) / sizeof(dns_error_map[0]); i++)
+ if (dns_error_map[i].error == error)
+ return (dns_error_map[i].text);
+ if (unknown == 0)
+ unknown = vstring_alloc(sizeof("Unknown error XXXXXX"));
+ vstring_sprintf(unknown, "Unknown error %u", error);
+ return (vstring_str(unknown));
+}
diff --git a/src/dns/dns_strrecord.c b/src/dns/dns_strrecord.c
new file mode 100644
index 0000000..6b8e989
--- /dev/null
+++ b/src/dns/dns_strrecord.c
@@ -0,0 +1,117 @@
+/*++
+/* NAME
+/* dns_strrecord 3
+/* SUMMARY
+/* name service resource record printable forms
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* char *dns_strrecord(buf, record)
+/* VSTRING *buf;
+/* DNS_RR *record;
+/* DESCRIPTION
+/* dns_strrecord() formats a DNS resource record as "name ttl
+/* class type preference value", where the class field is
+/* always "IN", the preference field exists only for MX records,
+/* and all names end in ".". The result value is the payload
+/* of the buffer argument.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h> /* memcpy */
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <msg.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* dns_strrecord - format resource record as generic string */
+
+char *dns_strrecord(VSTRING *buf, DNS_RR *rr)
+{
+ const char myname[] = "dns_strrecord";
+ MAI_HOSTADDR_STR host;
+ UINT32_TYPE soa_buf[5];
+
+ vstring_sprintf(buf, "%s. %u IN %s ",
+ rr->rname, rr->ttl, dns_strtype(rr->type));
+ switch (rr->type) {
+ case T_A:
+#ifdef T_AAAA
+ case T_AAAA:
+#endif
+ if (dns_rr_to_pa(rr, &host) == 0)
+ msg_fatal("%s: conversion error for resource record type %s: %m",
+ myname, dns_strtype(rr->type));
+ vstring_sprintf_append(buf, "%s", host.buf);
+ break;
+ case T_CNAME:
+ case T_DNAME:
+ case T_MB:
+ case T_MG:
+ case T_MR:
+ case T_NS:
+ case T_PTR:
+ vstring_sprintf_append(buf, "%s.", rr->data);
+ break;
+ case T_TXT:
+ vstring_sprintf_append(buf, "%s", rr->data);
+ break;
+ case T_MX:
+ vstring_sprintf_append(buf, "%u %s.", rr->pref, rr->data);
+ break;
+ case T_TLSA:
+ if (rr->data_len >= 3) {
+ uint8_t *ip = (uint8_t *) rr->data;
+ uint8_t usage = *ip++;
+ uint8_t selector = *ip++;
+ uint8_t mtype = *ip++;
+ unsigned i;
+
+ /* /\.example\. \d+ IN TLSA \d+ \d+ \d+ [\da-f]*$/ IGNORE */
+ vstring_sprintf_append(buf, "%d %d %d ", usage, selector, mtype);
+ for (i = 3; i < rr->data_len; ++i)
+ vstring_sprintf_append(buf, "%02x", *ip++);
+ } else {
+ vstring_sprintf_append(buf, "[truncated record]");
+ }
+
+ /*
+ * We use the SOA record TTL to determine the negative reply TTL. We
+ * save the time fields in the SOA record for debugging, but for now
+ * we don't bother saving the source host and mailbox information, as
+ * that would require changes to the DNS_RR structure. See also code
+ * in dns_get_rr().
+ */
+ case T_SOA:
+ memcpy(soa_buf, rr->data, sizeof(soa_buf));
+ vstring_sprintf_append(buf, "- - %u %u %u %u %u",
+ soa_buf[0], soa_buf[1], soa_buf[2],
+ soa_buf[3], soa_buf[4]);
+ break;
+ default:
+ msg_fatal("%s: don't know how to print type %s",
+ myname, dns_strtype(rr->type));
+ }
+ return (vstring_str(buf));
+}
diff --git a/src/dns/dns_strtype.c b/src/dns/dns_strtype.c
new file mode 100644
index 0000000..70e59ac
--- /dev/null
+++ b/src/dns/dns_strtype.c
@@ -0,0 +1,211 @@
+/*++
+/* NAME
+/* dns_strtype 3
+/* SUMMARY
+/* name service lookup type codes and printable forms
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* const char *dns_strtype(code)
+/* int code;
+/*
+/* int dns_type(strval)
+/* const char *strval;
+/* DESCRIPTION
+/* dns_strtype() maps a name service lookup type to printable string.
+/* The result is for read-only purposes, and unknown codes share a
+/* common string buffer.
+/*
+/* dns_type() converts a name service lookup string value to a numeric
+/* code. A null result means the code was not found. The input can be
+/* in lower case, upper case or mixed case.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <vstring.h>
+
+/* DNS library. */
+
+#include "dns.h"
+
+ /*
+ * Mapping from type code to printable string. Some names are possibly not
+ * defined on every platform, so I have #ifdef-ed them all just to be safe.
+ */
+struct dns_type_map {
+ unsigned type;
+ const char *text;
+};
+
+static struct dns_type_map dns_type_map[] = {
+#ifdef T_A
+ T_A, "A",
+#endif
+#ifdef T_AAAA
+ T_AAAA, "AAAA",
+#endif
+#ifdef T_NS
+ T_NS, "NS",
+#endif
+#ifdef T_MD
+ T_MD, "MD",
+#endif
+#ifdef T_MF
+ T_MF, "MF",
+#endif
+#ifdef T_CNAME
+ T_CNAME, "CNAME",
+#endif
+#ifdef T_SOA
+ T_SOA, "SOA",
+#endif
+#ifdef T_MB
+ T_MB, "MB",
+#endif
+#ifdef T_MG
+ T_MG, "MG",
+#endif
+#ifdef T_MR
+ T_MR, "MR",
+#endif
+#ifdef T_NULL
+ T_NULL, "NULL",
+#endif
+#ifdef T_WKS
+ T_WKS, "WKS",
+#endif
+#ifdef T_PTR
+ T_PTR, "PTR",
+#endif
+#ifdef T_HINFO
+ T_HINFO, "HINFO",
+#endif
+#ifdef T_MINFO
+ T_MINFO, "MINFO",
+#endif
+#ifdef T_MX
+ T_MX, "MX",
+#endif
+#ifdef T_TXT
+ T_TXT, "TXT",
+#endif
+#ifdef T_RP
+ T_RP, "RP",
+#endif
+#ifdef T_AFSDB
+ T_AFSDB, "AFSDB",
+#endif
+#ifdef T_X25
+ T_X25, "X25",
+#endif
+#ifdef T_ISDN
+ T_ISDN, "ISDN",
+#endif
+#ifdef T_RT
+ T_RT, "RT",
+#endif
+#ifdef T_NSAP
+ T_NSAP, "NSAP",
+#endif
+#ifdef T_NSAP_PTR
+ T_NSAP_PTR, "NSAP_PTR",
+#endif
+#ifdef T_SIG
+ T_SIG, "SIG",
+#endif
+#ifdef T_KEY
+ T_KEY, "KEY",
+#endif
+#ifdef T_PX
+ T_PX, "PX",
+#endif
+#ifdef T_GPOS
+ T_GPOS, "GPOS",
+#endif
+#ifdef T_AAAA
+ T_AAAA, "AAAA",
+#endif
+#ifdef T_LOC
+ T_LOC, "LOC",
+#endif
+#ifdef T_UINFO
+ T_UINFO, "UINFO",
+#endif
+#ifdef T_UID
+ T_UID, "UID",
+#endif
+#ifdef T_GID
+ T_GID, "GID",
+#endif
+#ifdef T_UNSPEC
+ T_UNSPEC, "UNSPEC",
+#endif
+#ifdef T_AXFR
+ T_AXFR, "AXFR",
+#endif
+#ifdef T_MAILB
+ T_MAILB, "MAILB",
+#endif
+#ifdef T_MAILA
+ T_MAILA, "MAILA",
+#endif
+#ifdef T_TLSA
+ T_TLSA, "TLSA",
+#endif
+#ifdef T_RRSIG
+ T_RRSIG, "RRSIG",
+#endif
+#ifdef T_DNAME
+ T_DNAME, "DNAME",
+#endif
+#ifdef T_ANY
+ T_ANY, "ANY",
+#endif
+};
+
+/* dns_strtype - translate DNS query type to string */
+
+const char *dns_strtype(unsigned type)
+{
+ static VSTRING *unknown = 0;
+ unsigned i;
+
+ for (i = 0; i < sizeof(dns_type_map) / sizeof(dns_type_map[0]); i++)
+ if (dns_type_map[i].type == type)
+ return (dns_type_map[i].text);
+ if (unknown == 0)
+ unknown = vstring_alloc(sizeof("Unknown type XXXXXX"));
+ vstring_sprintf(unknown, "Unknown type %u", type);
+ return (vstring_str(unknown));
+}
+
+/* dns_type - translate string to DNS query type */
+
+unsigned dns_type(const char *text)
+{
+ unsigned i;
+
+ for (i = 0; i < sizeof(dns_type_map) / sizeof(dns_type_map[0]); i++)
+ if (strcasecmp(dns_type_map[i].text, text) == 0)
+ return (dns_type_map[i].type);
+ return (0);
+}
diff --git a/src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref b/src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref
new file mode 100644
index 0000000..cbfc059
--- /dev/null
+++ b/src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref
@@ -0,0 +1,15 @@
+./test_dns_lookup: lookup 1.0.0.127.zen.spamhaus.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: 1.0.0.127.zen.spamhaus.org (A): Host not found
+./test_dns_lookup: dns_get_answer: type SOA for zen.spamhaus.org
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.zen.spamhaus.org type=A: Host not found (rcode=3)
+1.0.0.127.zen.spamhaus.org: fqdn: zen.spamhaus.org
+ad: 0, rr: zen.spamhaus.org. TTL IN SOA - - D D D D D
+./test_dns_lookup: lookup 1.0.0.127.b.barracudacentral.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: 1.0.0.127.b.barracudacentral.org (A): Host not found
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.b.barracudacentral.org type=A: Host not found (rcode=3)
+./test_dns_lookup: lookup 1.0.0.127.bl.spamcop.net type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: 1.0.0.127.bl.spamcop.net (A): Host not found
+./test_dns_lookup: dns_get_answer: type SOA for bl.spamcop.net
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.bl.spamcop.net type=A: Host not found (rcode=3)
+1.0.0.127.bl.spamcop.net: fqdn: bl.spamcop.net
+ad: 0, rr: bl.spamcop.net. TTL IN SOA - - D D D D D
diff --git a/src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref b/src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref
new file mode 100644
index 0000000..eee08eb
--- /dev/null
+++ b/src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref
@@ -0,0 +1,9 @@
+./test_dns_lookup: lookup 1.0.0.127.zen.spamhaus.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: 1.0.0.127.zen.spamhaus.org (A): Host not found
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.zen.spamhaus.org type=A: Host not found (rcode=3)
+./test_dns_lookup: lookup 1.0.0.127.b.barracudacentral.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: 1.0.0.127.b.barracudacentral.org (A): Host not found
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.b.barracudacentral.org type=A: Host not found (rcode=3)
+./test_dns_lookup: lookup 1.0.0.127.bl.spamcop.net type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: 1.0.0.127.bl.spamcop.net (A): Host not found
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.bl.spamcop.net type=A: Host not found (rcode=3)
diff --git a/src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref b/src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref
new file mode 100644
index 0000000..f1eb56b
--- /dev/null
+++ b/src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref
@@ -0,0 +1,15 @@
+./test_dns_lookup: lookup 2.0.0.127.zen.spamhaus.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: 2.0.0.127.zen.spamhaus.org (A): OK
+./test_dns_lookup: dns_get_answer: type A for 2.0.0.127.zen.spamhaus.org
+2.0.0.127.zen.spamhaus.org: fqdn: 2.0.0.127.zen.spamhaus.org
+ad: 0, rr: 2.0.0.127.zen.spamhaus.org. TTL IN A 127.0.0.D
+./test_dns_lookup: lookup 2.0.0.127.b.barracudacentral.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: 2.0.0.127.b.barracudacentral.org (A): OK
+./test_dns_lookup: dns_get_answer: type A for 2.0.0.127.b.barracudacentral.org
+2.0.0.127.b.barracudacentral.org: fqdn: 2.0.0.127.b.barracudacentral.org
+ad: 0, rr: 2.0.0.127.b.barracudacentral.org. TTL IN A 127.0.0.D
+./test_dns_lookup: lookup 2.0.0.127.bl.spamcop.net type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: 2.0.0.127.bl.spamcop.net (A): OK
+./test_dns_lookup: dns_get_answer: type A for 2.0.0.127.bl.spamcop.net
+2.0.0.127.bl.spamcop.net: fqdn: 2.0.0.127.bl.spamcop.net
+ad: 0, rr: 2.0.0.127.bl.spamcop.net. TTL IN A 127.0.0.D
diff --git a/src/dns/error.ref b/src/dns/error.ref
new file mode 100644
index 0000000..806cfe0
--- /dev/null
+++ b/src/dns/error.ref
@@ -0,0 +1,13 @@
+./test_dns_lookup: lookup spike.porcupine.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: spike.porcupine.org (A): OK
+./test_dns_lookup: dns_get_answer: type A for spike.porcupine.org
+./test_dns_lookup: dict_regexp_lookup: error.reg: spike.porcupine.org. 3600 IN A 168.100.189.2
+./test_dns_lookup: maps_find: DNS reply filter: regexp:error.reg(0,lock|fold_fix): spike.porcupine.org. 3600 IN A 168.100.189.2 = oops
+./test_dns_lookup: warning: DNS reply filter: unknown DNS filter action: "oops"
+./test_dns_lookup: lookup spike.porcupine.org type AAAA flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: spike.porcupine.org (AAAA): OK
+./test_dns_lookup: dns_get_answer: type AAAA for spike.porcupine.org
+./test_dns_lookup: dict_regexp_lookup: error.reg: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2
+./test_dns_lookup: maps_find: DNS reply filter: regexp:error.reg(0,lock|fold_fix): spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2 = oops
+./test_dns_lookup: warning: DNS reply filter: unknown DNS filter action: "oops"
+./test_dns_lookup: warning: Error looking up name=spike.porcupine.org type=AAAA: Invalid DNS reply filter syntax (rcode=0)
diff --git a/src/dns/error.reg b/src/dns/error.reg
new file mode 100644
index 0000000..4e553e8
--- /dev/null
+++ b/src/dns/error.reg
@@ -0,0 +1 @@
+/./ oops
diff --git a/src/dns/mxonly_test.ref b/src/dns/mxonly_test.ref
new file mode 100644
index 0000000..44f22d6
--- /dev/null
+++ b/src/dns/mxonly_test.ref
@@ -0,0 +1,11 @@
+./test_dns_lookup: lookup porcupine.org type MX flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: porcupine.org (MX): OK
+./test_dns_lookup: dns_get_answer: type MX for porcupine.org
+./test_dns_lookup: dns_get_answer: type MX for porcupine.org
+./test_dns_lookup: dns_get_answer: type MX for porcupine.org
+./test_dns_lookup: lookup porcupine.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: porcupine.org (A): Host found but no data record of requested type
+ad: 0, rr: porcupine.org. 3600 IN MX 10 spike.porcupine.org.
+ad: 0, rr: porcupine.org. 3600 IN MX 30 m1.porcupine.org.
+ad: 0, rr: porcupine.org. 3600 IN MX 30 vz.porcupine.org.
+porcupine.org: fqdn: porcupine.org
diff --git a/src/dns/no-a.ref b/src/dns/no-a.ref
new file mode 100644
index 0000000..88ba511
--- /dev/null
+++ b/src/dns/no-a.ref
@@ -0,0 +1,13 @@
+./test_dns_lookup: lookup spike.porcupine.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: spike.porcupine.org (A): OK
+./test_dns_lookup: dns_get_answer: type A for spike.porcupine.org
+./test_dns_lookup: dict_regexp_lookup: no-a.reg: spike.porcupine.org. 3600 IN A 168.100.189.2
+./test_dns_lookup: maps_find: DNS reply filter: regexp:no-a.reg(0,lock|fold_fix): spike.porcupine.org. 3600 IN A 168.100.189.2 = ignore
+./test_dns_lookup: ignoring DNS RR: spike.porcupine.org. 3600 IN A 168.100.189.2
+./test_dns_lookup: lookup spike.porcupine.org type AAAA flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: spike.porcupine.org (AAAA): OK
+./test_dns_lookup: dns_get_answer: type AAAA for spike.porcupine.org
+./test_dns_lookup: dict_regexp_lookup: no-a.reg: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2
+./test_dns_lookup: maps_find: DNS reply filter: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2: not found
+spike.porcupine.org: fqdn: spike.porcupine.org
+ad: 0, rr: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2
diff --git a/src/dns/no-a.reg b/src/dns/no-a.reg
new file mode 100644
index 0000000..69e05e5
--- /dev/null
+++ b/src/dns/no-a.reg
@@ -0,0 +1 @@
+/ +a +/ ignore
diff --git a/src/dns/no-aaaa.ref b/src/dns/no-aaaa.ref
new file mode 100644
index 0000000..f2a678c
--- /dev/null
+++ b/src/dns/no-aaaa.ref
@@ -0,0 +1,13 @@
+./test_dns_lookup: lookup spike.porcupine.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: spike.porcupine.org (A): OK
+./test_dns_lookup: dns_get_answer: type A for spike.porcupine.org
+./test_dns_lookup: dict_regexp_lookup: no-aaaa.reg: spike.porcupine.org. 3600 IN A 168.100.189.2
+./test_dns_lookup: maps_find: DNS reply filter: spike.porcupine.org. 3600 IN A 168.100.189.2: not found
+./test_dns_lookup: lookup spike.porcupine.org type AAAA flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: spike.porcupine.org (AAAA): OK
+./test_dns_lookup: dns_get_answer: type AAAA for spike.porcupine.org
+./test_dns_lookup: dict_regexp_lookup: no-aaaa.reg: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2
+./test_dns_lookup: maps_find: DNS reply filter: regexp:no-aaaa.reg(0,lock|fold_fix): spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2 = ignore
+./test_dns_lookup: ignoring DNS RR: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2
+spike.porcupine.org: fqdn: spike.porcupine.org
+ad: 0, rr: spike.porcupine.org. 3600 IN A 168.100.189.2
diff --git a/src/dns/no-aaaa.reg b/src/dns/no-aaaa.reg
new file mode 100644
index 0000000..962adda
--- /dev/null
+++ b/src/dns/no-aaaa.reg
@@ -0,0 +1 @@
+/ +aaaa +/ ignore
diff --git a/src/dns/no-mx.ref b/src/dns/no-mx.ref
new file mode 100644
index 0000000..5adc7bf
--- /dev/null
+++ b/src/dns/no-mx.ref
@@ -0,0 +1,15 @@
+./test_dns_lookup: dict_regexp_lookup: no-mx.reg: porcupine.org. 3600 IN MX 10 spike.porcupine.org.
+./test_dns_lookup: dict_regexp_lookup: no-mx.reg: porcupine.org. 3600 IN MX 30 m1.porcupine.org.
+./test_dns_lookup: dict_regexp_lookup: no-mx.reg: porcupine.org. 3600 IN MX 30 vz.porcupine.org.
+./test_dns_lookup: dns_get_answer: type MX for porcupine.org
+./test_dns_lookup: dns_get_answer: type MX for porcupine.org
+./test_dns_lookup: dns_get_answer: type MX for porcupine.org
+./test_dns_lookup: dns_query: porcupine.org (MX): OK
+./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 10 spike.porcupine.org.
+./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 30 m1.porcupine.org.
+./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 30 vz.porcupine.org.
+./test_dns_lookup: lookup porcupine.org type MX flags RES_USE_DNSSEC
+./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 10 spike.porcupine.org. = ignore
+./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 30 m1.porcupine.org. = ignore
+./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 30 vz.porcupine.org. = ignore
+./test_dns_lookup: warning: Error looking up name=porcupine.org type=MX: DNS reply filter drops all results (rcode=0)
diff --git a/src/dns/no-mx.reg b/src/dns/no-mx.reg
new file mode 100644
index 0000000..69cf05d
--- /dev/null
+++ b/src/dns/no-mx.reg
@@ -0,0 +1 @@
+/ +mx +/ ignore
diff --git a/src/dns/no-txt.reg b/src/dns/no-txt.reg
new file mode 100644
index 0000000..175600b
--- /dev/null
+++ b/src/dns/no-txt.reg
@@ -0,0 +1 @@
+/ +txt +/ ignore
diff --git a/src/dns/nullmx_test.ref b/src/dns/nullmx_test.ref
new file mode 100644
index 0000000..2386b53
--- /dev/null
+++ b/src/dns/nullmx_test.ref
@@ -0,0 +1,8 @@
+./test_dns_lookup: lookup nullmx.porcupine.org type MX flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: nullmx.porcupine.org (MX): OK
+./test_dns_lookup: dns_get_answer: type MX for nullmx.porcupine.org
+./test_dns_lookup: lookup nullmx.porcupine.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: nullmx.porcupine.org (A): OK
+./test_dns_lookup: dns_get_answer: type A for nullmx.porcupine.org
+nullmx.porcupine.org: fqdn: nullmx.porcupine.org
+ad: 0, rr: nullmx.porcupine.org. 3600 IN A 168.100.189.13
diff --git a/src/dns/nxdomain_test.ref b/src/dns/nxdomain_test.ref
new file mode 100644
index 0000000..15be203
--- /dev/null
+++ b/src/dns/nxdomain_test.ref
@@ -0,0 +1,5 @@
+./test_dns_lookup: lookup nxdomain.porcupine.org type MX flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: nxdomain.porcupine.org (MX): Host not found
+./test_dns_lookup: lookup nxdomain.porcupine.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: nxdomain.porcupine.org (A): Host not found
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=nxdomain.porcupine.org type=A: Host not found (rcode=3)
diff --git a/src/dns/test_dns_lookup.c b/src/dns/test_dns_lookup.c
new file mode 100644
index 0000000..8366cf7
--- /dev/null
+++ b/src/dns/test_dns_lookup.c
@@ -0,0 +1,129 @@
+/*++
+/* NAME
+/* test_dns_lookup 1
+/* SUMMARY
+/* DNS lookup test program
+/* SYNOPSIS
+/* test_dns_lookup query-type domain-name
+/* DESCRIPTION
+/* test_dns_lookup performs a DNS query of the specified resource
+/* type for the specified resource name.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <msg.h>
+#include <msg_vstream.h>
+#include <mymalloc.h>
+#include <argv.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "dns.h"
+
+static void print_rr(VSTRING *buf, DNS_RR *rr)
+{
+ while (rr) {
+ vstream_printf("ad: %u, rr: %s\n",
+ rr->dnssec_valid, dns_strrecord(buf, rr));
+ rr = rr->next;
+ }
+}
+
+static NORETURN usage(char **argv)
+{
+ msg_fatal("usage: %s [-npv] [-f filter] types name", argv[0]);
+}
+
+int main(int argc, char **argv)
+{
+ ARGV *types_argv;
+ unsigned *types;
+ char *name;
+ VSTRING *fqdn = vstring_alloc(100);
+ VSTRING *why = vstring_alloc(100);
+ VSTRING *buf;
+ int rcode;
+ DNS_RR *rr;
+ int i;
+ int ch;
+ int lflags = DNS_REQ_FLAG_NONE;
+
+ if (var_dnssec_probe == 0)
+ var_dnssec_probe = mystrdup(DEF_DNSSEC_PROBE);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ while ((ch = GETOPT(argc, argv, "f:npv")) > 0) {
+ switch (ch) {
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'f':
+ dns_rr_filter_compile("DNS reply filter", optarg);
+ break;
+ case 'n':
+ lflags |= DNS_REQ_FLAG_NCACHE_TTL;
+ break;
+ case 'p':
+ var_dns_ncache_ttl_fix = 1;
+ break;
+ default:
+ usage(argv);
+ }
+ }
+ if (argc != optind + 2)
+ usage(argv);
+ types_argv = argv_split(argv[optind], CHARS_COMMA_SP);
+ types = (unsigned *) mymalloc(sizeof(*types) * (types_argv->argc + 1));
+ for (i = 0; i < types_argv->argc; i++)
+ if ((types[i] = dns_type(types_argv->argv[i])) == 0)
+ msg_fatal("invalid query type: %s", types_argv->argv[i]);
+ types[i] = 0;
+ argv_free(types_argv);
+ name = argv[optind + 1];
+ msg_verbose = 1;
+ switch (dns_lookup_rv(name, RES_USE_DNSSEC, &rr, fqdn, why,
+ &rcode, lflags, types)) {
+ default:
+ msg_warn("%s (rcode=%d)", vstring_str(why), rcode);
+ case DNS_OK:
+ if (rr) {
+ vstream_printf("%s: fqdn: %s\n", name, vstring_str(fqdn));
+ buf = vstring_alloc(100);
+ print_rr(buf, rr);
+ dns_rr_free(rr);
+ vstring_free(buf);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ }
+ myfree((void *) types);
+ exit(0);
+}