diff options
Diffstat (limited to '')
43 files changed, 4415 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..795f9ba --- /dev/null +++ b/src/dns/Makefile.in @@ -0,0 +1,408 @@ +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.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.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.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.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.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.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.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.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..c5f7ad9 --- /dev/null +++ b/src/dns/dns_lookup.c @@ -0,0 +1,1216 @@ +/*++ +/* 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(). +/* Logs a warning if the RES_DNSRCH or RES_DEFNAMES resolver +/* flags are set, and disables those flags. +/* .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. + */ +#ifdef HAVE_RES_SEND + +/* 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); + } +} + +#endif + +/* 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); + + /* + * Can't append domains: we need the right SOA TTL. + */ +#define APPEND_DOMAIN_FLAGS (RES_DNSRCH | RES_DEFNAMES) + + if (keep_notfound && (flags & APPEND_DOMAIN_FLAGS)) { + msg_warn("negative caching disables RES_DEFNAMES and RES_DNSRCH"); + flags &= ~APPEND_DOMAIN_FLAGS; + } + + /* + * 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) { +#ifdef HAVE_RES_SEND + len = dns_res_query((char *) name, C_IN, type, reply->buf, + reply->buf_len); +#else + var_dns_ncache_ttl_fix = 0; + msg_warn("system library does not support %s=yes" + " -- ignoring this setting", VAR_DNS_NCACHE_TTL_FIX); + len = dns_res_search((char *) name, C_IN, type, reply->buf, + reply->buf_len, keep_notfound); +#endif + } 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 | DO_WILDCARD)) { + 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 | DO_WILDCARD)) { + 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..472394c --- /dev/null +++ b/src/dns/dns_str_resflags.c @@ -0,0 +1,128 @@ +/*++ +/* 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, +#ifdef RES_AAONLY + "RES_AAONLY", RES_AAONLY, +#endif + "RES_USEVC", RES_USEVC, +#ifdef RES_PRIMARY + "RES_PRIMARY", RES_PRIMARY, +#endif + "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..af7b6bc --- /dev/null +++ b/src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref @@ -0,0 +1,12 @@ +./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.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..b84554a --- /dev/null +++ b/src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref @@ -0,0 +1,6 @@ +./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.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..81dd845 --- /dev/null +++ b/src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref @@ -0,0 +1,10 @@ +./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.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); +} diff --git a/src/dnsblog/.indent.pro b/src/dnsblog/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/dnsblog/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro
\ No newline at end of file diff --git a/src/dnsblog/Makefile.in b/src/dnsblog/Makefile.in new file mode 100644 index 0000000..c2ed848 --- /dev/null +++ b/src/dnsblog/Makefile.in @@ -0,0 +1,84 @@ +SHELL = /bin/sh +SRCS = dnsblog.c +OBJS = dnsblog.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = dnsblog +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: test + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir 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 *core $(PROG) $(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' +dnsblog.o: ../../include/argv.h +dnsblog.o: ../../include/attr.h +dnsblog.o: ../../include/check_arg.h +dnsblog.o: ../../include/dns.h +dnsblog.o: ../../include/htable.h +dnsblog.o: ../../include/iostuff.h +dnsblog.o: ../../include/mail_conf.h +dnsblog.o: ../../include/mail_params.h +dnsblog.o: ../../include/mail_proto.h +dnsblog.o: ../../include/mail_server.h +dnsblog.o: ../../include/mail_version.h +dnsblog.o: ../../include/msg.h +dnsblog.o: ../../include/myaddrinfo.h +dnsblog.o: ../../include/mymalloc.h +dnsblog.o: ../../include/nvtable.h +dnsblog.o: ../../include/sock_addr.h +dnsblog.o: ../../include/sys_defs.h +dnsblog.o: ../../include/valid_hostname.h +dnsblog.o: ../../include/vbuf.h +dnsblog.o: ../../include/vstream.h +dnsblog.o: ../../include/vstring.h +dnsblog.o: dnsblog.c diff --git a/src/dnsblog/dnsblog.c b/src/dnsblog/dnsblog.c new file mode 100644 index 0000000..021be25 --- /dev/null +++ b/src/dnsblog/dnsblog.c @@ -0,0 +1,319 @@ +/*++ +/* NAME +/* dnsblog 8 +/* SUMMARY +/* Postfix DNS white/blacklist logger +/* SYNOPSIS +/* \fBdnsblog\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBdnsblog\fR(8) server implements an ad-hoc DNS +/* white/blacklist lookup service. This may eventually be +/* replaced by an UDP client that is built directly into the +/* \fBpostscreen\fR(8) server. +/* PROTOCOL +/* .ad +/* .fi +/* With each connection, the \fBdnsblog\fR(8) server receives +/* a DNS white/blacklist domain name, an IP address, and an ID. +/* If the IP address is listed under the DNS white/blacklist, the +/* \fBdnsblog\fR(8) server logs the match and replies with the +/* query arguments plus an address list with the resulting IP +/* addresses, separated by whitespace, and the reply TTL. +/* Otherwise it replies with the query arguments plus an empty +/* address list and the reply TTL; the reply TTL is -1 if there +/* is no reply, or a negative reply that contains no SOA record. +/* Finally, the \fBdnsblog\fR(8) server closes the connection. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically, as +/* \fBdnsblog\fR(8) processes run for only a limited amount +/* of time. Use the command "\fBpostfix reload\fR" to speed +/* up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBpostscreen_dnsbl_sites (empty)\fR" +/* Optional list of DNS white/blacklist domains, filters and weight +/* factors. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* smtpd(8), Postfix SMTP server +/* postconf(5), configuration parameters +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* This service was introduced with Postfix version 2.8. +/* 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 <limits.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> +#include <vstring.h> +#include <argv.h> +#include <myaddrinfo.h> +#include <valid_hostname.h> +#include <sock_addr.h> + +/* Global library. */ + +#include <mail_conf.h> +#include <mail_version.h> +#include <mail_proto.h> +#include <mail_params.h> + +/* DNS library. */ + +#include <dns.h> + +/* Server skeleton. */ + +#include <mail_server.h> + +/* Application-specific. */ + + /* + * Tunable parameters. + */ +int var_dnsblog_delay; + + /* + * Static so we don't allocate and free on every request. + */ +static VSTRING *rbl_domain; +static VSTRING *addr; +static VSTRING *query; +static VSTRING *why; +static VSTRING *result; + + /* + * Silly little macros. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* static void dnsblog_query - query DNSBL for client address */ + +static VSTRING *dnsblog_query(VSTRING *result, int *result_ttl, + const char *dnsbl_domain, + const char *addr) +{ + const char *myname = "dnsblog_query"; + ARGV *octets; + int i; + struct addrinfo *res; + unsigned char *ipv6_addr; + int dns_status; + DNS_RR *addr_list; + DNS_RR *rr; + MAI_HOSTADDR_STR hostaddr; + + if (msg_verbose) + msg_info("%s: addr %s dnsbl_domain %s", + myname, addr, dnsbl_domain); + + VSTRING_RESET(query); + + /* + * Reverse the client IPV6 address, represented as 32 hexadecimal + * nibbles. We use the binary address to avoid tricky code. Asking for an + * AAAA record makes no sense here. Just like with IPv4 we use the lookup + * result as a bit mask, not as an IP address. + */ +#ifdef HAS_IPV6 + if (valid_ipv6_hostaddr(addr, DONT_GRIPE)) { + if (hostaddr_to_sockaddr(addr, (char *) 0, 0, &res) != 0 + || res->ai_family != PF_INET6) + msg_fatal("%s: unable to convert address %s", myname, addr); + ipv6_addr = (unsigned char *) &SOCK_ADDR_IN6_ADDR(res->ai_addr); + for (i = sizeof(SOCK_ADDR_IN6_ADDR(res->ai_addr)) - 1; i >= 0; i--) + vstring_sprintf_append(query, "%x.%x.", + ipv6_addr[i] & 0xf, ipv6_addr[i] >> 4); + freeaddrinfo(res); + } else +#endif + + /* + * Reverse the client IPV4 address, represented as four decimal octet + * values. We use the textual address for convenience. + */ + { + octets = argv_split(addr, "."); + for (i = octets->argc - 1; i >= 0; i--) { + vstring_strcat(query, octets->argv[i]); + vstring_strcat(query, "."); + } + argv_free(octets); + } + + /* + * Tack on the RBL domain name and query the DNS for an A record. + */ + vstring_strcat(query, dnsbl_domain); + dns_status = dns_lookup_x(STR(query), T_A, 0, &addr_list, (VSTRING *) 0, + why, (int *) 0, DNS_REQ_FLAG_NCACHE_TTL); + + /* + * We return the lowest TTL in the response from the A record(s) if + * found, or from the SOA record(s) if available. If the reply specifies + * no TTL, or if the query fails, we return a TTL of -1. + */ + VSTRING_RESET(result); + *result_ttl = -1; + if (dns_status == DNS_OK) { + for (rr = addr_list; rr != 0; rr = rr->next) { + if (dns_rr_to_pa(rr, &hostaddr) == 0) { + msg_warn("%s: skipping reply record type %s for query %s: %m", + myname, dns_strtype(rr->type), STR(query)); + } else { + msg_info("addr %s listed by domain %s as %s", + addr, dnsbl_domain, hostaddr.buf); + if (LEN(result) > 0) + vstring_strcat(result, " "); + vstring_strcat(result, hostaddr.buf); + /* Grab the positive reply TTL. */ + if (*result_ttl < 0 || *result_ttl > rr->ttl) + *result_ttl = rr->ttl; + } + } + dns_rr_free(addr_list); + } else if (dns_status == DNS_NOTFOUND) { + if (msg_verbose) + msg_info("%s: addr %s not listed by domain %s", + myname, addr, dnsbl_domain); + /* Grab the negative reply TTL. */ + for (rr = addr_list; rr != 0; rr = rr->next) { + if (rr->type == T_SOA && (*result_ttl < 0 || *result_ttl > rr->ttl)) + *result_ttl = rr->ttl; + } + dns_rr_free(addr_list); + } else { + msg_warn("%s: lookup error for DNS query %s: %s", + myname, STR(query), STR(why)); + } + VSTRING_TERMINATE(result); + return (result); +} + +/* dnsblog_service - perform service for client */ + +static void dnsblog_service(VSTREAM *client_stream, char *unused_service, + char **argv) +{ + int request_id; + int result_ttl; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This routine runs whenever a client connects to the socket dedicated + * to the dnsblog service. All connection-management stuff is handled by + * the common code in single_server.c. + */ + if (attr_scan(client_stream, + ATTR_FLAG_MORE | ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, rbl_domain), + RECV_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, addr), + RECV_ATTR_INT(MAIL_ATTR_LABEL, &request_id), + ATTR_TYPE_END) == 3) { + (void) dnsblog_query(result, &result_ttl, STR(rbl_domain), STR(addr)); + if (var_dnsblog_delay > 0) + sleep(var_dnsblog_delay); + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, STR(rbl_domain)), + SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, STR(addr)), + SEND_ATTR_INT(MAIL_ATTR_LABEL, request_id), + SEND_ATTR_STR(MAIL_ATTR_RBL_ADDR, STR(result)), + SEND_ATTR_INT(MAIL_ATTR_TTL, result_ttl), + ATTR_TYPE_END); + vstream_fflush(client_stream); + } +} + +/* post_jail_init - post-jail initialization */ + +static void post_jail_init(char *unused_name, char **unused_argv) +{ + rbl_domain = vstring_alloc(100); + addr = vstring_alloc(100); + query = vstring_alloc(100); + why = vstring_alloc(100); + result = vstring_alloc(100); + var_use_limit = 0; +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the multi-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_TIME_TABLE time_table[] = { + VAR_DNSBLOG_DELAY, DEF_DNSBLOG_DELAY, &var_dnsblog_delay, 0, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + single_server_main(argc, argv, dnsblog_service, + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_POST_INIT(post_jail_init), + CA_MAIL_SERVER_UNLIMITED, + CA_MAIL_SERVER_RETIRE_ME, + 0); +} |