From 063644e9d47fc0eb300e319033fb55d96345aa40 Mon Sep 17 00:00:00 2001 From: florian Date: Tue, 10 Aug 2021 08:21:30 +0000 Subject: [PATCH] Update to nsd 4.3.7 OK sthen --- usr.sbin/nsd/Makefile.in | 10 +- usr.sbin/nsd/acx_nlnetlabs.m4 | 7 +- usr.sbin/nsd/config.h.in | 16 + usr.sbin/nsd/configlexer.lex | 6 + usr.sbin/nsd/configparser.y | 80 ++++ usr.sbin/nsd/configure | 88 +++- usr.sbin/nsd/configure.ac | 31 +- usr.sbin/nsd/dbaccess.c | 13 +- usr.sbin/nsd/difffile.c | 106 +++++ usr.sbin/nsd/difffile.h | 11 +- usr.sbin/nsd/dns.c | 102 ++++- usr.sbin/nsd/dns.h | 15 +- usr.sbin/nsd/dnstap/dnstap_collector.c | 221 +++++---- usr.sbin/nsd/doc/ChangeLog | 67 +++ usr.sbin/nsd/doc/RELNOTES | 28 ++ usr.sbin/nsd/edns.c | 179 +++++++- usr.sbin/nsd/edns.h | 37 +- usr.sbin/nsd/namedb.c | 7 +- usr.sbin/nsd/nsd-checkconf.8.in | 8 +- usr.sbin/nsd/nsd-checkconf.c | 95 +++- usr.sbin/nsd/nsd-checkzone.8.in | 2 +- usr.sbin/nsd/nsd-control.8.in | 37 +- usr.sbin/nsd/nsd-control.c | 4 + usr.sbin/nsd/nsd.8.in | 4 +- usr.sbin/nsd/nsd.c | 94 +++- usr.sbin/nsd/nsd.conf.5.in | 58 ++- usr.sbin/nsd/nsd.conf.sample.in | 17 + usr.sbin/nsd/nsd.h | 27 +- usr.sbin/nsd/nsec3.c | 2 +- usr.sbin/nsd/options.c | 58 ++- usr.sbin/nsd/options.h | 31 ++ usr.sbin/nsd/query.c | 16 +- usr.sbin/nsd/query.h | 4 +- usr.sbin/nsd/rdata.c | 231 +++++++++- usr.sbin/nsd/rdata.h | 1 + usr.sbin/nsd/remote.c | 159 +++++++ usr.sbin/nsd/server.c | 195 ++++++-- usr.sbin/nsd/util.c | 57 +++ usr.sbin/nsd/util.h | 11 + usr.sbin/nsd/xfrd-tcp.c | 522 +++++++++++++++++++--- usr.sbin/nsd/xfrd-tcp.h | 43 +- usr.sbin/nsd/xfrd.c | 13 +- usr.sbin/nsd/zonec.c | 590 +++++++++++++++++++++++++ usr.sbin/nsd/zonec.h | 3 + usr.sbin/nsd/zparser.y | 37 +- 45 files changed, 3059 insertions(+), 284 deletions(-) diff --git a/usr.sbin/nsd/Makefile.in b/usr.sbin/nsd/Makefile.in index e27708f8ae3..8aa40269f2a 100644 --- a/usr.sbin/nsd/Makefile.in +++ b/usr.sbin/nsd/Makefile.in @@ -79,7 +79,7 @@ EDIT = $(SED) \ TARGETS=nsd nsd-checkconf nsd-checkzone nsd-control nsd.conf.sample nsd-control-setup.sh MANUALS=nsd.8 nsd-checkconf.8 nsd-checkzone.8 nsd-control.8 nsd.conf.5 -COMMON_OBJ=answer.o axfr.o buffer.o configlexer.o configparser.o dname.o dns.o edns.o iterated_hash.o lookup3.o namedb.o nsec3.o options.o packet.o query.o rbtree.o radtree.o rdata.o region-allocator.o rrl.o tsig.o tsig-openssl.o udb.o udbradtree.o udbzone.o util.o bitset.o popen3.o +COMMON_OBJ=answer.o axfr.o buffer.o configlexer.o configparser.o dname.o dns.o edns.o iterated_hash.o lookup3.o namedb.o nsec3.o options.o packet.o query.o rbtree.o radtree.o rdata.o region-allocator.o rrl.o siphash.o tsig.o tsig-openssl.o udb.o udbradtree.o udbzone.o util.o bitset.o popen3.o XFRD_OBJ=xfrd-disk.o xfrd-notify.o xfrd-tcp.o xfrd.o remote.o $(DNSTAP_OBJ) NSD_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) difffile.o ipc.o mini_event.o netio.o nsd.o server.o dbaccess.o dbcreate.o zlexer.o zonec.o zparser.o ALL_OBJ=$(NSD_OBJ) nsd-checkconf.o nsd-checkzone.o nsd-control.o nsd-mem.o xfr-inspect.o @@ -270,6 +270,9 @@ fake-rfc2553.o: $(srcdir)/compat/fake-rfc2553.c cpuset.o: $(srcdir)/compat/cpuset.c $(COMPILE) -c $(srcdir)/compat/cpuset.c +explicit_bzero.o: $(srcdir)/compat/explicit_bzero.c + $(COMPILE) -c $(srcdir)/compat/explicit_bzero.c + cutest_dname.o: $(srcdir)/tpkg/cutest/cutest_dname.c $(COMPILE) -c $(srcdir)/tpkg/cutest/cutest_dname.c @@ -491,8 +494,8 @@ rrl.o: $(srcdir)/rrl.c config.h $(srcdir)/rrl.h $(srcdir)/query.h $(srcdir)/name server.o: $(srcdir)/server.c config.h $(srcdir)/axfr.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h \ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/query.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/radtree.h $(srcdir)/rbtree.h \ $(srcdir)/packet.h $(srcdir)/tsig.h $(srcdir)/netio.h $(srcdir)/xfrd.h $(srcdir)/options.h $(srcdir)/xfrd-tcp.h $(srcdir)/xfrd-disk.h \ - $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/rrl.h \ - $(srcdir)/dnstap/dnstap_collector.h + $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/dnstap/dnstap_collector.h $(srcdir)/rrl.h +siphash.o: $(srcdir)/siphash.c tsig.o: $(srcdir)/tsig.c config.h $(srcdir)/tsig.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/dname.h \ $(srcdir)/tsig-openssl.h $(srcdir)/dns.h $(srcdir)/packet.h $(srcdir)/namedb.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/query.h $(srcdir)/nsd.h \ $(srcdir)/edns.h @@ -547,6 +550,7 @@ strlcat.o: $(srcdir)/compat/strlcat.c config.h strlcpy.o: $(srcdir)/compat/strlcpy.c config.h strptime.o: $(srcdir)/compat/strptime.c setproctitle.o: $(srcdir)/compat/setproctitle.c config.h +explicit_bzero.o: $(srcdir)/compat/explicit_bzero.c config.h cutest.o: $(srcdir)/tpkg/cutest/cutest.c config.h $(srcdir)/tpkg/cutest/cutest.h cutest_dname.o: $(srcdir)/tpkg/cutest/cutest_dname.c config.h $(srcdir)/tpkg/cutest/cutest.h \ $(srcdir)/region-allocator.h $(srcdir)/dname.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h $(srcdir)/util.h diff --git a/usr.sbin/nsd/acx_nlnetlabs.m4 b/usr.sbin/nsd/acx_nlnetlabs.m4 index dd8d8c32985..7ce79070805 100644 --- a/usr.sbin/nsd/acx_nlnetlabs.m4 +++ b/usr.sbin/nsd/acx_nlnetlabs.m4 @@ -2,7 +2,10 @@ # Copyright 2009, Wouter Wijngaards, NLnet Labs. # BSD licensed. # -# Version 38 +# Version 40 +# 2021-06-14 fix nonblocking test to use host instead of target for mingw test. +# 2021-05-17 fix nonblocking socket test from grep on mingw32 to mingw for +# 64bit compatibility. # 2021-03-24 fix ACX_FUNC_DEPRECATED to use CPPFLAGS and CFLAGS. # 2021-01-05 fix defun for aclocal # 2021-01-05 autoconf 2.70 autoupdate and fixes, no AC_TRY_COMPILE @@ -915,7 +918,7 @@ dnl a nonblocking socket do not work, a new call to select is necessary. AC_DEFUN([ACX_CHECK_NONBLOCKING_BROKEN], [ AC_MSG_CHECKING([if nonblocking sockets work]) -if echo $target | grep mingw32 >/dev/null; then +if echo $host | grep mingw >/dev/null; then AC_MSG_RESULT([no (windows)]) AC_DEFINE([NONBLOCKING_IS_BROKEN], 1, [Define if the network stack does not fully support nonblocking io (causes lower performance).]) else diff --git a/usr.sbin/nsd/config.h.in b/usr.sbin/nsd/config.h.in index cdca7b9e31e..c4cd67f7dfe 100644 --- a/usr.sbin/nsd/config.h.in +++ b/usr.sbin/nsd/config.h.in @@ -3,6 +3,9 @@ /* apply the noreturn attribute to a function that exits the program */ #undef ATTR_NORETURN +/* apply the weak attribute to a symbol */ +#undef ATTR_WEAK + /* Define this to enable BIND8 like NSTATS & XSTATS. */ #undef BIND8_STATS @@ -61,6 +64,9 @@ /* Whether the C compiler accepts the "unused" attribute */ #undef HAVE_ATTR_UNUSED +/* Whether the C compiler accepts the "weak" attribute */ +#undef HAVE_ATTR_WEAK + /* Define to 1 if you have the `b64_ntop' function. */ #undef HAVE_B64_NTOP @@ -151,6 +157,9 @@ /* Define to 1 if you have the `ev_loop' function. */ #undef HAVE_EV_LOOP +/* Define to 1 if you have the `explicit_bzero' function. */ +#undef HAVE_EXPLICIT_BZERO + /* Define to 1 if you have the header file. */ #undef HAVE_FCNTL_H @@ -458,6 +467,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_TIME_H +/* Define if TLS 1.3 is supported by OpenSSL */ +#undef HAVE_TLS_1_3 + /* Define to 1 if you have the `tzset' function. */ #undef HAVE_TZSET @@ -923,6 +935,10 @@ int inet_aton(const char *cp, struct in_addr *addr); #ifndef HAVE_MEMMOVE void *memmove(void *dest, const void *src, size_t n); #endif +#ifndef HAVE_EXPLICIT_BZERO +#define explicit_bzero nsd_explicit_bzero +void explicit_bzero(void* buf, size_t len); +#endif #ifndef HAVE_STRLCAT size_t strlcat(char *dst, const char *src, size_t siz); #endif diff --git a/usr.sbin/nsd/configlexer.lex b/usr.sbin/nsd/configlexer.lex index 99c045dac26..6d4933de381 100644 --- a/usr.sbin/nsd/configlexer.lex +++ b/usr.sbin/nsd/configlexer.lex @@ -240,6 +240,8 @@ provide-xfr{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_PROVIDE_XFR;} allow-query{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ALLOW_QUERY;} outgoing-interface{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_OUTGOING_INTERFACE;} allow-axfr-fallback{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ALLOW_AXFR_FALLBACK;} +tls-auth{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_AUTH;} +auth-domain-name{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_AUTH_DOMAIN_NAME;} key{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_KEY;} algorithm{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ALGORITHM;} secret{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_SECRET;} @@ -288,6 +290,10 @@ tls-service-key{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_SERVICE_KEY; tls-service-ocsp{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_SERVICE_OCSP;} tls-service-pem{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_SERVICE_PEM;} tls-port{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_PORT;} +tls-cert-bundle{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_CERT_BUNDLE; } +answer-cookie{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ANSWER_COOKIE;} +cookie-secret{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_COOKIE_SECRET;} +cookie-secret-file{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_COOKIE_SECRET_FILE;} {NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++;} servers={UNQUOTEDLETTER}* { diff --git a/usr.sbin/nsd/configparser.y b/usr.sbin/nsd/configparser.y index 9ffda88c5f5..6b369cad35d 100644 --- a/usr.sbin/nsd/configparser.y +++ b/usr.sbin/nsd/configparser.y @@ -31,6 +31,7 @@ extern "C" extern config_parser_state_type *cfg_parser; static void append_acl(struct acl_options **list, struct acl_options *acl); +static void add_to_last_acl(struct acl_options **list, char *ac); static int parse_boolean(const char *str, int *bln); static int parse_expire_expr(const char *str, long long *num, uint8_t *expr); static int parse_number(const char *str, long long *num); @@ -113,6 +114,7 @@ static int parse_range(const char *str, long long *low, long long *high); %token VAR_TLS_SERVICE_PEM %token VAR_TLS_SERVICE_OCSP %token VAR_TLS_PORT +%token VAR_TLS_CERT_BUNDLE %token VAR_CPU_AFFINITY %token VAR_XFRD_CPU_AFFINITY %token VAR_SERVER_CPU_AFFINITY @@ -144,6 +146,10 @@ static int parse_range(const char *str, long long *low, long long *high); %token VAR_ALGORITHM %token VAR_SECRET +/* xot auth */ +%token VAR_TLS_AUTH +%token VAR_TLS_AUTH_DOMAIN_NAME + /* pattern */ %token VAR_PATTERN %token VAR_NAME @@ -158,6 +164,9 @@ static int parse_range(const char *str, long long *low, long long *high); %token VAR_REQUEST_XFR %token VAR_ALLOW_AXFR_FALLBACK %token VAR_OUTGOING_INTERFACE +%token VAR_ANSWER_COOKIE +%token VAR_COOKIE_SECRET +%token VAR_COOKIE_SECRET_FILE %token VAR_MAX_REFRESH_TIME %token VAR_MIN_REFRESH_TIME %token VAR_MAX_RETRY_TIME @@ -188,6 +197,7 @@ block: | dnstap | remote_control | key + | tls_auth | pattern | zone ; @@ -433,6 +443,14 @@ server_option: (void)snprintf(buf, sizeof(buf), "%lld", $2); cfg_parser->opt->tls_port = region_strdup(cfg_parser->opt->region, buf); } + | VAR_TLS_CERT_BUNDLE STRING + { cfg_parser->opt->tls_cert_bundle = region_strdup(cfg_parser->opt->region, $2); } + | VAR_ANSWER_COOKIE boolean + { cfg_parser->opt->answer_cookie = $2; } + | VAR_COOKIE_SECRET STRING + { cfg_parser->opt->cookie_secret = region_strdup(cfg_parser->opt->region, $2); } + | VAR_COOKIE_SECRET_FILE STRING + { cfg_parser->opt->cookie_secret_file = region_strdup(cfg_parser->opt->region, $2); } | VAR_CPU_AFFINITY cpus { cfg_parser->opt->cpu_affinity = $2; @@ -614,6 +632,48 @@ remote_control_option: { cfg_parser->opt->control_cert_file = region_strdup(cfg_parser->opt->region, $2); } ; +tls_auth: + VAR_TLS_AUTH + { + tls_auth_options_type *tls_auth = tls_auth_options_create(cfg_parser->opt->region); + assert(cfg_parser->tls_auth == NULL); + cfg_parser->tls_auth = tls_auth; + } + tls_auth_block + { + struct tls_auth_options *tls_auth = cfg_parser->tls_auth; + if(tls_auth->name == NULL) { + yyerror("tls-auth has no name"); + } else if(tls_auth->auth_domain_name == NULL) { + yyerror("tls-auth %s has no auth-domain-name", tls_auth->name); + } else if(tls_auth_options_find(cfg_parser->opt, tls_auth->name)) { + yyerror("duplicate tls-auth %s", tls_auth->name); + } else { + tls_auth_options_insert(cfg_parser->opt, tls_auth); + cfg_parser->tls_auth = NULL; + } + } ; + +tls_auth_block: + tls_auth_block tls_auth_option | ; + +tls_auth_option: + VAR_NAME STRING + { + dname_type *dname; + dname = (dname_type *)dname_parse(cfg_parser->opt->region, $2); + cfg_parser->tls_auth->name = region_strdup(cfg_parser->opt->region, $2); + if(dname == NULL) { + yyerror("bad tls-auth name %s", $2); + } else { + region_recycle(cfg_parser->opt->region, dname, dname_total_size(dname)); + } + } + | VAR_TLS_AUTH_DOMAIN_NAME STRING + { + cfg_parser->tls_auth->auth_domain_name = region_strdup(cfg_parser->opt->region, $2); + }; + key: VAR_KEY { @@ -786,6 +846,8 @@ pattern_or_zone_option: yyerror("address range used for request-xfr"); append_acl(&cfg_parser->pattern->request_xfr, acl); } + tlsauth_option + { } | VAR_REQUEST_XFR VAR_AXFR STRING STRING { acl_options_type *acl = parse_acl_info(cfg_parser->opt->region, $3, $4); @@ -796,6 +858,8 @@ pattern_or_zone_option: yyerror("address range used for request-xfr"); append_acl(&cfg_parser->pattern->request_xfr, acl); } + tlsauth_option + { } | VAR_REQUEST_XFR VAR_UDP STRING STRING { acl_options_type *acl = parse_acl_info(cfg_parser->opt->region, $3, $4); @@ -906,6 +970,11 @@ boolean: } } ; +tlsauth_option: + | STRING + { char *tls_auth_name = region_strdup(cfg_parser->opt->region, $1); + add_to_last_acl(&cfg_parser->pattern->request_xfr, tls_auth_name);} ; + %% static void @@ -923,6 +992,17 @@ append_acl(struct acl_options **list, struct acl_options *acl) } } +static void +add_to_last_acl(struct acl_options **list, char *tls_auth_name) +{ + struct acl_options *tail = *list; + assert(list != NULL); + assert(*list != NULL); + while(tail->next != NULL) + tail = tail->next; + tail->tls_auth_name = tls_auth_name; +} + static int parse_boolean(const char *str, int *bln) { diff --git a/usr.sbin/nsd/configure b/usr.sbin/nsd/configure index da56d9c0b04..156d6467332 100644 --- a/usr.sbin/nsd/configure +++ b/usr.sbin/nsd/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for NSD 4.3.6. +# Generated by GNU Autoconf 2.69 for NSD 4.3.7. # # Report bugs to . # @@ -580,8 +580,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='NSD' PACKAGE_TARNAME='nsd' -PACKAGE_VERSION='4.3.6' -PACKAGE_STRING='NSD 4.3.6' +PACKAGE_VERSION='4.3.7' +PACKAGE_STRING='NSD 4.3.7' PACKAGE_BUGREPORT='nsd-bugs@nlnetlabs.nl' PACKAGE_URL='' @@ -1328,7 +1328,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures NSD 4.3.6 to adapt to many kinds of systems. +\`configure' configures NSD 4.3.7 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1390,7 +1390,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of NSD 4.3.6:";; + short | recursive ) echo "Configuration of NSD 4.3.7:";; esac cat <<\_ACEOF @@ -1563,7 +1563,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -NSD configure 4.3.6 +NSD configure 4.3.7 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2272,7 +2272,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by NSD $as_me 4.3.6, which was +It was created by NSD $as_me 4.3.7, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -5286,6 +5286,7 @@ fi + # Checks for typedefs, structures, and compiler characteristics. # allow user to override the -g -O2 flags. if test "x$CFLAGS" = "x" ; then @@ -5756,6 +5757,49 @@ $as_echo "#define HAVE_ATTR_UNUSED 1" >>confdefs.h fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler (${CC-cc}) accepts the \"weak\" attribute" >&5 +$as_echo_n "checking whether the C compiler (${CC-cc}) accepts the \"weak\" attribute... " >&6; } +if ${ac_cv_c_weak_attribute+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_c_weak_attribute=no +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + #include +__attribute__((weak)) void f(int x) { printf("%d", x); } + +int +main () +{ + + f(1); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_c_weak_attribute="yes" +else + ac_cv_c_weak_attribute="no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_weak_attribute" >&5 +$as_echo "$ac_cv_c_weak_attribute" >&6; } +if test $ac_cv_c_weak_attribute = yes; then + +$as_echo "#define HAVE_ATTR_WEAK 1" >>confdefs.h + + +$as_echo "#define ATTR_WEAK __attribute__((weak))" >>confdefs.h + +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler (${CC-cc}) accepts the \"noreturn\" attribute" >&5 $as_echo_n "checking whether the C compiler (${CC-cc}) accepts the \"noreturn\" attribute... " >&6; } if ${ac_cv_c_noreturn_attribute+:} false; then : @@ -6524,7 +6568,7 @@ fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking if nonblocking sockets work" >&5 $as_echo_n "checking if nonblocking sockets work... " >&6; } -if echo $target | grep mingw32 >/dev/null; then +if echo $host | grep mingw >/dev/null; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no (windows)" >&5 $as_echo "no (windows)" >&6; } @@ -8721,6 +8765,20 @@ esac fi +ac_fn_c_check_func "$LINENO" "explicit_bzero" "ac_cv_func_explicit_bzero" +if test "x$ac_cv_func_explicit_bzero" = xyes; then : + $as_echo "#define HAVE_EXPLICIT_BZERO 1" >>confdefs.h + +else + case " $LIBOBJS " in + *" explicit_bzero.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS explicit_bzero.$ac_objext" + ;; +esac + +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for reallocarray" >&5 $as_echo_n "checking for reallocarray... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext @@ -9629,6 +9687,16 @@ cat >>confdefs.h <<_ACEOF #define HAVE_DECL_SSL_CTX_SET_TMP_ECDH $ac_have_decl _ACEOF + ac_fn_c_check_decl "$LINENO" "TLS1_3_VERSION" "ac_cv_have_decl_TLS1_3_VERSION" "#include +" +if test "x$ac_cv_have_decl_TLS1_3_VERSION" = xyes; then : + +$as_echo "#define HAVE_TLS_1_3 1" >>confdefs.h + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: No TLS 1.3, therefore XFR-over-TLS is disabled" >&5 +$as_echo "$as_me: WARNING: No TLS 1.3, therefore XFR-over-TLS is disabled" >&2;} +fi BAKLIBS="$LIBS" @@ -10978,7 +11046,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by NSD $as_me 4.3.6, which was +This file was extended by NSD $as_me 4.3.7, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -11040,7 +11108,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -NSD config.status 4.3.6 +NSD config.status 4.3.7 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/usr.sbin/nsd/configure.ac b/usr.sbin/nsd/configure.ac index 4f9946e9595..8449e510bdd 100644 --- a/usr.sbin/nsd/configure.ac +++ b/usr.sbin/nsd/configure.ac @@ -5,7 +5,7 @@ dnl sinclude(acx_nlnetlabs.m4) sinclude(dnstap/dnstap.m4) -AC_INIT([NSD],[4.3.6],[nsd-bugs@nlnetlabs.nl]) +AC_INIT([NSD],[4.3.7],[nsd-bugs@nlnetlabs.nl]) AC_CONFIG_HEADERS([config.h]) # @@ -251,6 +251,25 @@ if test $ac_cv_c_unused_attribute = yes; then fi ])dnl +AC_DEFUN([CHECK_WEAK_ATTRIBUTE], +[AC_REQUIRE([AC_PROG_CC]) +AC_MSG_CHECKING(whether the C compiler (${CC-cc}) accepts the "weak" attribute) +AC_CACHE_VAL(ac_cv_c_weak_attribute, +[ac_cv_c_weak_attribute=no +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include +__attribute__((weak)) void f(int x) { printf("%d", x); } +]], [[ + f(1); +]])],[ac_cv_c_weak_attribute="yes"],[ac_cv_c_weak_attribute="no"]) +]) + +AC_MSG_RESULT($ac_cv_c_weak_attribute) +if test $ac_cv_c_weak_attribute = yes; then + AC_DEFINE(HAVE_ATTR_WEAK, 1, [Whether the C compiler accepts the "weak" attribute]) + AC_DEFINE(ATTR_WEAK, [__attribute__((weak))], [apply the weak attribute to a symbol]) +fi +])dnl End of CHECK_WEAK_ATTRIBUTE + AC_DEFUN([CHECK_NORETURN_ATTRIBUTE], [AC_REQUIRE([AC_PROG_CC]) AC_MSG_CHECKING(whether the C compiler (${CC-cc}) accepts the "noreturn" attribute) @@ -332,6 +351,7 @@ AC_TYPE_OFF_T AC_CHECK_FORMAT_ATTRIBUTE AC_CHECK_UNUSED_ATTRIBUTE +CHECK_WEAK_ATTRIBUTE CHECK_NORETURN_ATTRIBUTE ACX_CHECK_MEMCMP_SIGNED AC_CHECK_CTIME_R @@ -794,6 +814,7 @@ AC_REPLACE_FUNCS(strptime) AC_REPLACE_FUNCS(pselect) AC_REPLACE_FUNCS(memmove) AC_REPLACE_FUNCS(setproctitle) +AC_REPLACE_FUNCS(explicit_bzero) AC_MSG_CHECKING([for reallocarray]) AC_LINK_IFELSE([AC_LANG_SOURCE( [[ @@ -1049,7 +1070,9 @@ AC_INCLUDES_DEFAULT #include #include ]) - + AC_CHECK_DECL([TLS1_3_VERSION], + [AC_DEFINE([HAVE_TLS_1_3], [1], [Define if TLS 1.3 is supported by OpenSSL])], + [AC_MSG_WARN([No TLS 1.3, therefore XFR-over-TLS is disabled])], [[#include ]]) BAKLIBS="$LIBS" LIBS="-lssl $LIBS" @@ -1291,6 +1314,10 @@ int inet_aton(const char *cp, struct in_addr *addr); #ifndef HAVE_MEMMOVE void *memmove(void *dest, const void *src, size_t n); #endif +#ifndef HAVE_EXPLICIT_BZERO +#define explicit_bzero nsd_explicit_bzero +void explicit_bzero(void* buf, size_t len); +#endif #ifndef HAVE_STRLCAT size_t strlcat(char *dst, const char *src, size_t siz); #endif diff --git a/usr.sbin/nsd/dbaccess.c b/usr.sbin/nsd/dbaccess.c index e14506a2754..4291451a250 100644 --- a/usr.sbin/nsd/dbaccess.c +++ b/usr.sbin/nsd/dbaccess.c @@ -530,8 +530,17 @@ namedb_read_zonefile(struct nsd* nsd, struct zone* zone, udb_base* taskudb, assert(fname); if(!file_get_mtime(fname, &mtime, &nonexist)) { if(nonexist) { - VERBOSITY(2, (LOG_INFO, "zonefile %s does not exist", - fname)); + if(zone_is_slave(zone->opts)) { + /* for slave zones not as bad, no zonefile + * may just mean we have to transfer it */ + VERBOSITY(2, (LOG_INFO, "zonefile %s does not exist", + fname)); + } else { + /* without a download option, we can never + * serve data, more severe error printout */ + log_msg(LOG_ERR, "zonefile %s does not exist", fname); + } + } else log_msg(LOG_ERR, "zonefile %s: %s", fname, strerror(errno)); diff --git a/usr.sbin/nsd/difffile.c b/usr.sbin/nsd/difffile.c index 09ab8b7ebfb..5a7926452c2 100644 --- a/usr.sbin/nsd/difffile.c +++ b/usr.sbin/nsd/difffile.c @@ -559,6 +559,12 @@ nsec3_add_rr_trigger(namedb_type* db, rr_type* rr, zone_type* zone, if(zone->nsec3_param && rr->type == TYPE_NSEC3 && (!rr->owner->nsec3 || !rr->owner->nsec3->nsec3_node.key) && nsec3_rr_uses_params(rr, zone)) { + if(!zone->nsec3_last) { + /* all nsec3s have previously been deleted, but + * we have nsec3 parameters, set it up again from + * being cleared. */ + nsec3_precompile_newparam(db, zone); + } /* added NSEC3 into the chain */ nsec3_precompile_nsec3rr(db, rr->owner, zone); /* the domain has become an NSEC3-domain, if it was precompiled @@ -1715,6 +1721,47 @@ void task_new_del_key(udb_base* udb, udb_ptr* last, const char* name) udb_ptr_unlink(&e, udb); } +void task_new_add_cookie_secret(udb_base* udb, udb_ptr* last, + const char* secret) { + udb_ptr e; + char* p; + size_t const secret_size = strlen(secret) + 1; + + DEBUG(DEBUG_IPC, 1, (LOG_INFO, "add task add_cookie_secret")); + + if(!task_create_new_elem(udb, last, &e, + sizeof(struct task_list_d) + secret_size, NULL)) { + log_msg(LOG_ERR, "tasklist: out of space, cannot add add_cookie_secret"); + return; + } + TASKLIST(&e)->task_type = task_add_cookie_secret; + p = (char*)TASKLIST(&e)->zname; + memmove(p, secret, secret_size); + udb_ptr_unlink(&e, udb); +} + +void task_new_drop_cookie_secret(udb_base* udb, udb_ptr* last) { + udb_ptr e; + DEBUG(DEBUG_IPC, 1, (LOG_INFO, "add task drop_cookie_secret")); + if(!task_create_new_elem(udb, last, &e, sizeof(struct task_list_d), NULL)) { + log_msg(LOG_ERR, "tasklist: out of space, cannot add drop_cookie_secret"); + return; + } + TASKLIST(&e)->task_type = task_drop_cookie_secret; + udb_ptr_unlink(&e, udb); +} + +void task_new_activate_cookie_secret(udb_base* udb, udb_ptr* last) { + udb_ptr e; + DEBUG(DEBUG_IPC, 1, (LOG_INFO, "add task activate_cookie_secret")); + if(!task_create_new_elem(udb, last, &e, sizeof(struct task_list_d), NULL)) { + log_msg(LOG_ERR, "tasklist: out of space, cannot add activate_cookie_secret"); + return; + } + TASKLIST(&e)->task_type = task_activate_cookie_secret; + udb_ptr_unlink(&e, udb); +} + void task_new_add_pattern(udb_base* udb, udb_ptr* last, struct pattern_options* p) { @@ -1955,6 +2002,56 @@ task_process_del_key(struct nsd* nsd, struct task_list_d* task) key_options_remove(nsd->options, name); } +static void +task_process_add_cookie_secret(struct nsd* nsd, struct task_list_d* task) { + uint8_t secret_tmp[NSD_COOKIE_SECRET_SIZE]; + ssize_t decoded_len; + char* secret = (char*)task->zname; + + DEBUG(DEBUG_IPC, 1, (LOG_INFO, "add_cookie_secret task %s", secret)); + + if( strlen(secret) != 32 ) { + log_msg(LOG_ERR, "invalid cookie secret: %s", secret); + explicit_bzero(secret, strlen(secret)); + return; + } + + decoded_len = hex_pton(secret, secret_tmp, NSD_COOKIE_SECRET_SIZE); + if( decoded_len != 16 ) { + explicit_bzero(secret_tmp, NSD_COOKIE_SECRET_SIZE); + log_msg(LOG_ERR, "unable to parse cookie secret: %s", secret); + explicit_bzero(secret, strlen(secret)); + return; + } + explicit_bzero(secret, strlen(secret)); + add_cookie_secret(nsd, secret_tmp); + explicit_bzero(secret_tmp, NSD_COOKIE_SECRET_SIZE); +} + +static void +task_process_drop_cookie_secret(struct nsd* nsd, struct task_list_d* task) +{ + (void)task; + DEBUG(DEBUG_IPC, 1, (LOG_INFO, "drop_cookie_secret task")); + if( nsd->cookie_count <= 1 ) { + log_msg(LOG_ERR, "can not drop the only active cookie secret"); + return; + } + drop_cookie_secret(nsd); +} + +static void +task_process_activate_cookie_secret(struct nsd* nsd, struct task_list_d* task) +{ + (void)task; + DEBUG(DEBUG_IPC, 1, (LOG_INFO, "activate_cookie_secret task")); + if( nsd->cookie_count <= 1 ) { + log_msg(LOG_ERR, "can not activate the only active cookie secret"); + return; + } + activate_cookie_secret(nsd); +} + static void task_process_add_pattern(struct nsd* nsd, struct task_list_d* task) { @@ -2087,6 +2184,15 @@ void task_process_in_reload(struct nsd* nsd, udb_base* udb, udb_ptr *last_task, case task_apply_xfr: task_process_apply_xfr(nsd, udb, last_task, task); break; + case task_add_cookie_secret: + task_process_add_cookie_secret(nsd, TASKLIST(task)); + break; + case task_drop_cookie_secret: + task_process_drop_cookie_secret(nsd, TASKLIST(task)); + break; + case task_activate_cookie_secret: + task_process_activate_cookie_secret(nsd, TASKLIST(task)); + break; default: log_msg(LOG_WARNING, "unhandled task in reload type %d", (int)TASKLIST(task)->task_type); diff --git a/usr.sbin/nsd/difffile.h b/usr.sbin/nsd/difffile.h index f6db6e28234..b8dc7702830 100644 --- a/usr.sbin/nsd/difffile.h +++ b/usr.sbin/nsd/difffile.h @@ -88,7 +88,13 @@ struct task_list_d { /** options change */ task_opt_change, /** zonestat increment */ - task_zonestat_inc + task_zonestat_inc, + /** add a new cookie secret */ + task_add_cookie_secret, + /** drop the oldest cookie secret */ + task_drop_cookie_secret, + /** make staging cookie secret active */ + task_activate_cookie_secret, } task_type; uint32_t size; /* size of this struct */ @@ -126,6 +132,9 @@ void task_new_add_pattern(udb_base* udb, udb_ptr* last, void task_new_del_pattern(udb_base* udb, udb_ptr* last, const char* name); void task_new_opt_change(udb_base* udb, udb_ptr* last, struct nsd_options* opt); void task_new_zonestat_inc(udb_base* udb, udb_ptr* last, unsigned sz); +void task_new_add_cookie_secret(udb_base* udb, udb_ptr* last, const char* secret); +void task_new_drop_cookie_secret(udb_base* udb, udb_ptr* last); +void task_new_activate_cookie_secret(udb_base* udb, udb_ptr* last); int task_new_apply_xfr(udb_base* udb, udb_ptr* last, const dname_type* zone, uint32_t old_serial, uint32_t new_serial, uint64_t filenumber); void task_process_in_reload(struct nsd* nsd, udb_base* udb, udb_ptr *last_task, diff --git a/usr.sbin/nsd/dns.c b/usr.sbin/nsd/dns.c index 7375b296b04..773facc6db9 100644 --- a/usr.sbin/nsd/dns.c +++ b/usr.sbin/nsd/dns.c @@ -315,10 +315,104 @@ static rrtype_descriptor_type rrtype_descriptors[(RRTYPE_DESCRIPTORS_LENGTH+1)] RDATA_WF_BYTE, /* hash Algorithm */ RDATA_WF_BINARY }, /* digest */ { RDATA_ZF_PERIOD, RDATA_ZF_BYTE, RDATA_ZF_BYTE, RDATA_ZF_HEX } }, - /* 64 */ - { 64, NULL, T_UTYPE, 1, 1, { RDATA_WF_BINARY }, { RDATA_ZF_UNKNOWN } }, - /* 65 */ - { 65, NULL, T_UTYPE, 1, 1, { RDATA_WF_BINARY }, { RDATA_ZF_UNKNOWN } }, + /* 64 - SVCB */ + { TYPE_SVCB, "SVCB", T_SVCB, 2, MAXRDATALEN, + { RDATA_WF_SHORT /* SvcFieldPriority */ + , RDATA_WF_UNCOMPRESSED_DNAME /* SvcDomainName */ + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM /* SvcFieldValue */ + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + }, + { RDATA_ZF_SHORT , RDATA_ZF_DNAME + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + } }, + /* 65 - HTTPS */ + { TYPE_HTTPS, "HTTPS", T_HTTPS, 2, MAXRDATALEN, + { RDATA_WF_SHORT /* SvcFieldPriority */ + , RDATA_WF_UNCOMPRESSED_DNAME /* SvcDomainName */ + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM /* SvcFieldValue */ + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM + }, + { RDATA_ZF_SHORT , RDATA_ZF_DNAME + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM + } }, /* 66 */ { 66, NULL, T_UTYPE, 1, 1, { RDATA_WF_BINARY }, { RDATA_ZF_UNKNOWN } }, /* 67 */ diff --git a/usr.sbin/nsd/dns.h b/usr.sbin/nsd/dns.h index b7d4a280cf3..4702968aa93 100644 --- a/usr.sbin/nsd/dns.h +++ b/usr.sbin/nsd/dns.h @@ -142,6 +142,8 @@ typedef enum nsd_rc nsd_rc_type; #define TYPE_OPENPGPKEY 61 /* RFC 7929 */ #define TYPE_CSYNC 62 /* RFC 7477 */ #define TYPE_ZONEMD 63 /* draft-ietf-dnsop-dns-zone-digest */ +#define TYPE_SVCB 64 /* draft-ietf-dnsop-svcb-https-03 */ +#define TYPE_HTTPS 65 /* draft-ietf-dnsop-svcb-https-03 */ #define TYPE_SPF 99 /* RFC 4408 */ @@ -165,6 +167,15 @@ typedef enum nsd_rc nsd_rc_type; #define TYPE_DLV 32769 /* RFC 4431 */ #define PSEUDO_TYPE_DLV RRTYPE_DESCRIPTORS_LENGTH +#define SVCB_KEY_MANDATORY 0 +#define SVCB_KEY_ALPN 1 +#define SVCB_KEY_NO_DEFAULT_ALPN 2 +#define SVCB_KEY_PORT 3 +#define SVCB_KEY_IPV4HINT 4 +#define SVCB_KEY_ECH 5 +#define SVCB_KEY_IPV6HINT 6 +#define SVCPARAMKEY_COUNT 7 + #define MAXLABELLEN 63 #define MAXDOMAINLEN 255 @@ -204,7 +215,8 @@ enum rdata_wireformat RDATA_WF_ILNP64, /* 64-bit uncompressed IPv6 address. */ RDATA_WF_EUI48, /* 48-bit address. */ RDATA_WF_EUI64, /* 64-bit address. */ - RDATA_WF_LONG_TEXT /* Long (>255) text string. */ + RDATA_WF_LONG_TEXT, /* Long (>255) text string. */ + RDATA_WF_SVCPARAM /* SvcParam [=] */ }; typedef enum rdata_wireformat rdata_wireformat_type; @@ -243,6 +255,7 @@ enum rdata_zoneformat RDATA_ZF_EUI64, /* EUI64 address. */ RDATA_ZF_LONG_TEXT, /* Long (>255) text string. */ RDATA_ZF_TAG, /* Text string without quotes. */ + RDATA_ZF_SVCPARAM, /* SvcParam [=] */ RDATA_ZF_UNKNOWN /* Unknown data. */ }; typedef enum rdata_zoneformat rdata_zoneformat_type; diff --git a/usr.sbin/nsd/dnstap/dnstap_collector.c b/usr.sbin/nsd/dnstap/dnstap_collector.c index c582205579d..5ee1e5f78c6 100644 --- a/usr.sbin/nsd/dnstap/dnstap_collector.c +++ b/usr.sbin/nsd/dnstap/dnstap_collector.c @@ -33,12 +33,15 @@ #include "namedb.h" #include "options.h" +#include "udb.h" +#include "rrl.h" + struct dt_collector* dt_collector_create(struct nsd* nsd) { int i, sv[2]; struct dt_collector* dt_col = (struct dt_collector*)xalloc_zero( sizeof(*dt_col)); - dt_col->count = nsd->child_count; + dt_col->count = nsd->child_count * 2; dt_col->dt_env = NULL; dt_col->region = region_create(xalloc, free); dt_col->send_buffer = buffer_create(dt_col->region, @@ -51,28 +54,32 @@ struct dt_collector* dt_collector_create(struct nsd* nsd) #endif ); - /* open pipes in struct nsd */ + /* open communication channels in struct nsd */ nsd->dt_collector_fd_send = (int*)xalloc_array_zero(dt_col->count, sizeof(int)); nsd->dt_collector_fd_recv = (int*)xalloc_array_zero(dt_col->count, sizeof(int)); for(i=0; icount; i++) { - int fd[2]; - fd[0] = -1; - fd[1] = -1; - if(pipe(fd) < 0) { - error("dnstap_collector: cannot create pipe: %s", + int sv[2]; + int bufsz = buffer_capacity(dt_col->send_buffer); + sv[0] = -1; /* For receiving by parent (dnstap-collector) */ + sv[1] = -1; /* For sending by child (server childs) */ + if(socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0, sv) < 0) { + error("dnstap_collector: cannot create communication channel: %s", strerror(errno)); } - if(fcntl(fd[0], F_SETFL, O_NONBLOCK) == -1) { - log_msg(LOG_ERR, "fcntl failed: %s", strerror(errno)); + if(setsockopt(sv[0], SOL_SOCKET, SO_RCVBUF, &bufsz, sizeof(bufsz))) { + log_msg(LOG_ERR, "setting dnstap_collector " + "receive buffer size failed: %s", strerror(errno)); } - if(fcntl(fd[1], F_SETFL, O_NONBLOCK) == -1) { - log_msg(LOG_ERR, "fcntl failed: %s", strerror(errno)); + if(setsockopt(sv[1], SOL_SOCKET, SO_SNDBUF, &bufsz, sizeof(bufsz))) { + log_msg(LOG_ERR, "setting dnstap_collector " + "send buffer size failed: %s", strerror(errno)); } - nsd->dt_collector_fd_recv[i] = fd[0]; - nsd->dt_collector_fd_send[i] = fd[1]; + nsd->dt_collector_fd_recv[i] = sv[0]; + nsd->dt_collector_fd_send[i] = sv[1]; } + nsd->dt_collector_fd_swap = nsd->dt_collector_fd_send + nsd->child_count; /* open socketpair */ if(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) { @@ -96,15 +103,19 @@ void dt_collector_destroy(struct dt_collector* dt_col, struct nsd* nsd) if(!dt_col) return; free(nsd->dt_collector_fd_recv); nsd->dt_collector_fd_recv = NULL; - free(nsd->dt_collector_fd_send); + if (nsd->dt_collector_fd_send < nsd->dt_collector_fd_swap) + free(nsd->dt_collector_fd_send); + else + free(nsd->dt_collector_fd_swap); nsd->dt_collector_fd_send = NULL; + nsd->dt_collector_fd_swap = NULL; region_destroy(dt_col->region); free(dt_col); } void dt_collector_close(struct dt_collector* dt_col, struct nsd* nsd) { - int i; + int i, *fd_send; if(!dt_col) return; if(dt_col->cmd_socket_dt != -1) { close(dt_col->cmd_socket_dt); @@ -114,14 +125,16 @@ void dt_collector_close(struct dt_collector* dt_col, struct nsd* nsd) close(dt_col->cmd_socket_nsd); dt_col->cmd_socket_nsd = -1; } + fd_send = nsd->dt_collector_fd_send < nsd->dt_collector_fd_swap + ? nsd->dt_collector_fd_send : nsd->dt_collector_fd_swap; for(i=0; icount; i++) { if(nsd->dt_collector_fd_recv[i] != -1) { close(nsd->dt_collector_fd_recv[i]); nsd->dt_collector_fd_recv[i] = -1; } - if(nsd->dt_collector_fd_send[i] != -1) { - close(nsd->dt_collector_fd_send[i]); - nsd->dt_collector_fd_send[i] = -1; + if(fd_send[i] != -1) { + close(fd_send[i]); + fd_send[i] = -1; } } } @@ -137,49 +150,38 @@ dt_handle_cmd_from_nsd(int ATTR_UNUSED(fd), short event, void* arg) } } -/* read data from fd into buffer, true when message is complete */ -static int read_into_buffer(int fd, struct buffer* buf) +/* receive data from fd into buffer, 1 when message received, -1 on error */ +static int recv_into_buffer(int fd, struct buffer* buf) { size_t msglen; ssize_t r; - if(buffer_position(buf) < 4) { - /* read the length of the message */ - r = read(fd, buffer_current(buf), 4 - buffer_position(buf)); - if(r == -1) { - if(errno == EAGAIN || errno == EINTR) { - /* continue to read later */ - return 0; - } - log_msg(LOG_ERR, "dnstap collector: read failed: %s", - strerror(errno)); - return 0; - } - buffer_skip(buf, r); - if(buffer_position(buf) < 4) - return 0; /* continue to read more msglen later */ - } - /* msglen complete */ - msglen = buffer_read_u32_at(buf, 0); - /* assert we have enough space, if we don't and we wanted to continue, - * we would have to skip the message somehow, but that should never - * happen because send_buffer and receive_buffer have the same size */ - assert(buffer_capacity(buf) >= msglen + 4); - r = read(fd, buffer_current(buf), msglen - (buffer_position(buf) - 4)); + assert(buffer_position(buf) == 0); + r = recv(fd, buffer_current(buf), buffer_capacity(buf), MSG_DONTWAIT); if(r == -1) { - if(errno == EAGAIN || errno == EINTR) { - /* continue to read later */ + if(errno == EAGAIN || errno == EINTR || errno == EMSGSIZE) { + /* continue to receive a message later */ return 0; } - log_msg(LOG_ERR, "dnstap collector: read failed: %s", + log_msg(LOG_ERR, "dnstap collector: receive failed: %s", strerror(errno)); + return -1; + } + if(r == 0) { + /* Remote end closed the connection? */ + log_msg(LOG_ERR, "dnstap collector: remote closed connection"); + return -1; + } + assert(r > 4); + msglen = buffer_read_u32_at(buf, 0); + if(msglen != (size_t)(r - 4)) { + /* Is this still possible now the communication channel is of + * type SOCK_DGRAM? I think not, but better safe than sorry. */ + log_msg(LOG_ERR, "dnstap collector: out of sync (msglen: %u)", + (unsigned int) msglen); return 0; } buffer_skip(buf, r); - if(buffer_position(buf) < 4 + msglen) - return 0; /* read more msg later */ - - /* msg complete */ buffer_flip(buf); return 1; } @@ -242,11 +244,15 @@ dt_handle_input(int fd, short event, void* arg) { struct dt_collector_input* dt_input = (struct dt_collector_input*)arg; if((event&EV_READ) != 0) { - /* read */ - if(!read_into_buffer(fd, dt_input->buffer)) + /* receive */ + int r = recv_into_buffer(fd, dt_input->buffer); + if(r == 0) return; - - /* once data is complete, write it to dnstap */ + else if(r < 0) { + event_base_loopexit(dt_input->dt_collector->event_base, NULL); + return; + } + /* once data is complete, send it to dnstap */ VERBOSITY(4, (LOG_INFO, "dnstap collector: received msg len %d", (int)buffer_remaining(dt_input->buffer))); if(dt_input->dt_collector->dt_env) { @@ -376,6 +382,7 @@ static void dt_collector_run(struct dt_collector* dt_col, struct nsd* nsd) void dt_collector_start(struct dt_collector* dt_col, struct nsd* nsd) { + int i, *fd_send; /* fork */ dt_col->dt_pid = fork(); if(dt_col->dt_pid == -1) { @@ -386,6 +393,29 @@ void dt_collector_start(struct dt_collector* dt_col, struct nsd* nsd) /* close the nsd side of the command channel */ close(dt_col->cmd_socket_nsd); dt_col->cmd_socket_nsd = -1; + + /* close the send side of the communication channels */ + assert(nsd->dt_collector_fd_send < nsd->dt_collector_fd_swap); + fd_send = nsd->dt_collector_fd_send < nsd->dt_collector_fd_swap + ? nsd->dt_collector_fd_send : nsd->dt_collector_fd_swap; + for(i=0; icount; i++) { + if(fd_send[i] != -1) { + close(fd_send[i]); + fd_send[i] = -1; + } + } +#ifdef HAVE_SETPROCTITLE + setproctitle("dnstap_collector"); +#endif + /* Free serve process specific memory pages */ +#ifdef RATELIMIT + rrl_mmap_deinit_keep_mmap(); +#endif + udb_base_free_keep_mmap(nsd->task[0]); + udb_base_free_keep_mmap(nsd->task[1]); + namedb_close_udb(nsd->db); /* keeps mmap */ + namedb_close(nsd->db); + dt_collector_run(dt_col, nsd); /* NOTREACH */ exit(0); @@ -394,6 +424,14 @@ void dt_collector_start(struct dt_collector* dt_col, struct nsd* nsd) /* close the dt side of the command channel */ close(dt_col->cmd_socket_dt); dt_col->cmd_socket_dt = -1; + + /* close the receive side of the communication channels */ + for(i=0; icount; i++) { + if(nsd->dt_collector_fd_recv[i] != -1) { + close(nsd->dt_collector_fd_recv[i]); + nsd->dt_collector_fd_recv[i] = -1; + } + } } } @@ -446,37 +484,34 @@ prep_send_data(struct buffer* buf, uint8_t is_response, return 1; } -/* attempt to write buffer to socket, if it blocks do not write it. */ -static void attempt_to_write(int s, uint8_t* data, size_t len) +/* attempt to send buffer to socket, if it blocks do not send it. + * return 0 on success, -1 on error */ +static int attempt_to_send(int s, uint8_t* data, size_t len) { - size_t total = 0; ssize_t r; - while(total < len) { - r = write(s, data+total, len-total); - if(r == -1) { - if(errno == EAGAIN && total == 0) { - /* on first write part, check if pipe is full, - * if the nonblocking fd blocks, then drop - * the message */ - return; - } - if(errno != EAGAIN && errno != EINTR) { - /* some sort of error, print it and drop it */ - log_msg(LOG_ERR, - "dnstap collector: write failed: %s", - strerror(errno)); - return; - } - /* continue and write this again */ - /* for EINTR, we have to do this, - * for EAGAIN, if the first part succeeded, we have - * to continue to write the remainder of the message, - * because otherwise partial messages confuse the - * receiver. */ - continue; + if(len == 0) + return 0; + r = send(s, data, len, MSG_DONTWAIT | MSG_NOSIGNAL); + if(r == -1) { + if(errno == EAGAIN || errno == EINTR || + errno == ENOBUFS || errno == EMSGSIZE) { + /* check if pipe is full, if the nonblocking fd blocks, + * then drop the message */ + return 0; } - total += r; + /* some sort of error, print it */ + log_msg(LOG_ERR, "dnstap collector: send failed: %s", + strerror(errno)); + return -1; } + assert(r > 0); + if(r > 0) { + assert((size_t)r == len); + return 0; + } + /* Other end closed the channel? */ + log_msg(LOG_ERR, "dnstap collector: server child closed the channel"); + return -1; } void dt_collector_submit_auth_query(struct nsd* nsd, @@ -491,6 +526,7 @@ void dt_collector_submit_auth_query(struct nsd* nsd, { if(!nsd->dt_collector) return; if(!nsd->options->dnstap_log_auth_query_messages) return; + if(nsd->dt_collector_fd_send[nsd->this_child->child_num] == -1) return; VERBOSITY(4, (LOG_INFO, "dnstap submit auth query")); /* marshal data into send buffer */ @@ -499,9 +535,14 @@ void dt_collector_submit_auth_query(struct nsd* nsd, return; /* probably did not fit in buffer */ /* attempt to send data; do not block */ - attempt_to_write(nsd->dt_collector_fd_send[nsd->this_child->child_num], - buffer_begin(nsd->dt_collector->send_buffer), - buffer_remaining(nsd->dt_collector->send_buffer)); + if(attempt_to_send(nsd->dt_collector_fd_send[nsd->this_child->child_num], + buffer_begin(nsd->dt_collector->send_buffer), + buffer_remaining(nsd->dt_collector->send_buffer))) { + /* Something went wrong sending to the socket. Don't send to + * this socket again. */ + close(nsd->dt_collector_fd_send[nsd->this_child->child_num]); + nsd->dt_collector_fd_send[nsd->this_child->child_num] = -1; + } } void dt_collector_submit_auth_response(struct nsd* nsd, @@ -517,6 +558,7 @@ void dt_collector_submit_auth_response(struct nsd* nsd, { if(!nsd->dt_collector) return; if(!nsd->options->dnstap_log_auth_response_messages) return; + if(nsd->dt_collector_fd_send[nsd->this_child->child_num] == -1) return; VERBOSITY(4, (LOG_INFO, "dnstap submit auth response")); /* marshal data into send buffer */ @@ -525,7 +567,12 @@ void dt_collector_submit_auth_response(struct nsd* nsd, return; /* probably did not fit in buffer */ /* attempt to send data; do not block */ - attempt_to_write(nsd->dt_collector_fd_send[nsd->this_child->child_num], - buffer_begin(nsd->dt_collector->send_buffer), - buffer_remaining(nsd->dt_collector->send_buffer)); + if(attempt_to_send(nsd->dt_collector_fd_send[nsd->this_child->child_num], + buffer_begin(nsd->dt_collector->send_buffer), + buffer_remaining(nsd->dt_collector->send_buffer))) { + /* Something went wrong sending to the socket. Don't send to + * this socket again. */ + close(nsd->dt_collector_fd_send[nsd->this_child->child_num]); + nsd->dt_collector_fd_send[nsd->this_child->child_num] = -1; + } } diff --git a/usr.sbin/nsd/doc/ChangeLog b/usr.sbin/nsd/doc/ChangeLog index e647367c1d0..80b241c30ea 100644 --- a/usr.sbin/nsd/doc/ChangeLog +++ b/usr.sbin/nsd/doc/ChangeLog @@ -1,5 +1,72 @@ +22 July 2021: Wouter + - tag 4.3.7 release, with the fixes between rc1 and this release. + +20 July 2021: Wouter + - Fix typo in xfrd-tcp.c. + +15 July 2021: Wouter + - tag for 4.3.7rc1. + - Fix compile of cookies on FreeBSD without IPv6. + - Fix for loop initial declaration for nonc99 compiler. + +14 July 2021: Wouter + - Fix truncate test for EDNS COOKIE making one less RR is added. + - Attempt to fix gcc11 warning. + +13 July 2021: Willem + - Fixes for child server processes getting out of sync with the + dnstap-collector process + +13 July 2021: Willem + - Interoperable DNS Cookies support as per RFC7873 and RFC9018 + +9 July 2021: Willem + - Client side DNS Zone Transfer-over-TLS (XoT) support as per + draft-ietf-dprive-xfr-over-tls + +29 June 2021: Willem + - Fix #168: Buffer overflow in the dname_to_string() function + +14 June 2021: Wouter + - Update configure nonblocking test to use host. + +25 May 2021: Wouter + - Fix #179: log notice and server-count. + +21 May 2021: Wouter + - Test code has -q option for quiet output. + +17 May 2021: Wouter + - Update the ACX_CHECK_NONBLOCKING_BROKEN test for the configure + script. + +7 May 2021: Wouter + - Fix #176: please review Loglevel on missing zonefile. + +6 May 2021: Wouter + - Fix #174: NS Records below delegation are not ignored (nsd-checkzone + also does not raise any issue). + +4 May 2021: Wouter + - Fix SVCB sort call sizeof to be the size of the elements sorted. + +29 April 2021: Tom + - Implement Syntax of SVCB and HTTPS RR type as per draft-ietf-dnsop-svcb-https + +13 April 2021: Wouter + - Fix for #128: Skip over sendmmsg invalid argument when port is zero. + - Fix #171: Invalid negative response (NSEC3) after IXFR. + - Fix to make nsec3_chain_find_prev return NULL if one nsec3 left. + - remove debug settings from unit test. + +9 April 2021: Wouter + - Fix for #170: Fix build warnings when IPv6 is disabled. + - Fix #170: Disabled IPv6 and DNSTAP enabled triggers a build error. + 30 March 2021: Wouter - Fix configure failure for enable systemd because of autoconf. + - This became release 4.3.6, the repository continues for 4.3.7 + in development. 29 March 2021: Wouter - Note unlisted changes in RELNOTES and prepare for 4.3.6rc1 tag. diff --git a/usr.sbin/nsd/doc/RELNOTES b/usr.sbin/nsd/doc/RELNOTES index cb0aee219a1..054eb9b8f60 100644 --- a/usr.sbin/nsd/doc/RELNOTES +++ b/usr.sbin/nsd/doc/RELNOTES @@ -1,5 +1,33 @@ NSD RELEASE NOTES +4.3.7 +================ +FEATURES: + - Syntax of SVCB and HTTPS RR type as per draft-ietf-dnsop-svcb-https + - Client side DNS Zone Transfer-over-TLS (XoT) support as per + draft-ietf-dprive-xfr-over-tls + - Interoperable DNS Cookies support as per RFC7873 and RFC9018 +BUG FIXES: + - Fix for #170: Fix build warnings when IPv6 is disabled. + - Fix #170: Disabled IPv6 and DNSTAP enabled triggers a build error. + - Fix for #128: Skip over sendmmsg invalid argument when port is zero. + - Fix #171: Invalid negative response (NSEC3) after IXFR. + - Fix to make nsec3_chain_find_prev return NULL if one nsec3 left. + - Fix #174: NS Records below delegation are not ignored (nsd-checkzone + also does not raise any issue). + - Fix #176: please review Loglevel on missing zonefile. + - Update the ACX_CHECK_NONBLOCKING_BROKEN test for the configure + script. + - Fix #179: log notice and server-count. + - Update configure nonblocking test to use host. + - Fix #168: Buffer overflow in the dname_to_string() function + - Fixes for child server processes getting out of sync with the + dnstap-collector process + - Fix gcc-11 warning on array bounds. + - Fix compile of cookies on FreeBSD without IPv6. + - Fix for loop initial declaration for nonc99 compiler + - Fix typo in xfrd-tcp.c. + 4.3.6 ================ FEATURES: diff --git a/usr.sbin/nsd/edns.c b/usr.sbin/nsd/edns.c index 15e468fc2ba..c7fc39d9d01 100644 --- a/usr.sbin/nsd/edns.c +++ b/usr.sbin/nsd/edns.c @@ -11,6 +11,10 @@ #include "config.h" #include +#ifdef HAVE_SSL +#include +#include +#endif #include "dns.h" #include "edns.h" @@ -33,15 +37,17 @@ edns_init_data(edns_data_type *data, uint16_t max_length) data->error[3] = (max_length & 0xff00) >> 8; /* size_hi */ data->error[4] = max_length & 0x00ff; /* size_lo */ data->error[5] = 1; /* XXX Extended RCODE=BAD VERS */ + + /* COOKIE OPT HDR */ + data->cookie[0] = (COOKIE_CODE & 0xff00) >> 8; + data->cookie[1] = (COOKIE_CODE & 0x00ff); + data->cookie[2] = (24 & 0xff00) >> 8; + data->cookie[3] = (24 & 0x00ff); } void edns_init_nsid(edns_data_type *data, uint16_t nsid_len) { - /* add nsid length bytes */ - data->rdata_nsid[0] = ((OPT_HDR + nsid_len) & 0xff00) >> 8; /* length_hi */ - data->rdata_nsid[1] = ((OPT_HDR + nsid_len) & 0x00ff); /* length_lo */ - /* NSID OPT HDR */ data->nsid[0] = (NSID_CODE & 0xff00) >> 8; data->nsid[1] = (NSID_CODE & 0x00ff); @@ -58,6 +64,8 @@ edns_init_record(edns_record_type *edns) edns->opt_reserved_space = 0; edns->dnssec_ok = 0; edns->nsid = 0; + edns->cookie_status = COOKIE_NOT_PRESENT; + edns->cookie_len = 0; edns->ede = -1; /* -1 means no Extended DNS Error */ edns->ede_text = NULL; edns->ede_text_len = 0; @@ -84,6 +92,24 @@ edns_handle_option(uint16_t optcode, uint16_t optlen, buffer_type* packet, buffer_skip(packet, optlen); } break; + case COOKIE_CODE: + /* Cookies enabled? */ + if(nsd->do_answer_cookie) { + if (optlen == 8) + edns->cookie_status = COOKIE_INVALID; + else if (optlen < 16 || optlen > 40) + return 0; /* FORMERR */ + else + edns->cookie_status = COOKIE_UNVERIFIED; + + edns->cookie_len = optlen; + memcpy(edns->cookie, buffer_current(packet), optlen); + buffer_skip(packet, optlen); + edns->opt_reserved_space += OPT_HDR + 24; + } else { + buffer_skip(packet, optlen); + } + break; default: buffer_skip(packet, optlen); break; @@ -162,3 +188,148 @@ edns_reserved_space(edns_record_type *edns) return edns->status == EDNS_NOT_PRESENT ? 0 : (OPT_LEN + OPT_RDATA + edns->opt_reserved_space); } + +int siphash(const uint8_t *in, const size_t inlen, + const uint8_t *k, uint8_t *out, const size_t outlen); + +/** RFC 1982 comparison, uses unsigned integers, and tries to avoid + * compiler optimization (eg. by avoiding a-b<0 comparisons), + * this routine matches compare_serial(), for SOA serial number checks */ +static int +compare_1982(uint32_t a, uint32_t b) +{ + /* for 32 bit values */ + const uint32_t cutoff = ((uint32_t) 1 << (32 - 1)); + + if (a == b) { + return 0; + } else if ((a < b && b - a < cutoff) || (a > b && a - b > cutoff)) { + return -1; + } else { + return 1; + } +} + +/** if we know that b is larger than a, return the difference between them, + * that is the distance between them. in RFC1982 arith */ +static uint32_t +subtract_1982(uint32_t a, uint32_t b) +{ + /* for 32 bit values */ + const uint32_t cutoff = ((uint32_t) 1 << (32 - 1)); + + if(a == b) + return 0; + if(a < b && b - a < cutoff) { + return b-a; + } + if(a > b && a - b > cutoff) { + return ((uint32_t)0xffffffff) - (a-b-1); + } + /* wrong case, b smaller than a */ + return 0; +} + +void cookie_verify(query_type *q, struct nsd* nsd, uint32_t *now_p) { + uint8_t hash[8], hash2verify[8]; + uint32_t cookie_time, now_uint32; + size_t verify_size; + int i; + + /* We support only draft-sury-toorop-dnsop-server-cookies sizes */ + if(q->edns.cookie_len != 24) + return; + + if(q->edns.cookie[8] != 1) + return; + + q->edns.cookie_status = COOKIE_INVALID; + + cookie_time = (q->edns.cookie[12] << 24) + | (q->edns.cookie[13] << 16) + | (q->edns.cookie[14] << 8) + | q->edns.cookie[15]; + + now_uint32 = *now_p ? *now_p : (*now_p = (uint32_t)time(NULL)); + + if(compare_1982(now_uint32, cookie_time) > 0) { + /* ignore cookies > 1 hour in past */ + if (subtract_1982(cookie_time, now_uint32) > 3600) + return; + } else if (subtract_1982(now_uint32, cookie_time) > 300) { + /* ignore cookies > 5 minutes in future */ + return; + } + + memcpy(hash2verify, q->edns.cookie + 16, 8); + +#ifdef INET6 + if(q->addr.ss_family == AF_INET6) { + memcpy(q->edns.cookie + 16, &((struct sockaddr_in6 *)&q->addr)->sin6_addr, 16); + verify_size = 32; + } else { + memcpy(q->edns.cookie + 16, &((struct sockaddr_in *)&q->addr)->sin_addr, 4); + verify_size = 20; + } +#else + memcpy( q->edns.cookie + 16, &q->addr.sin_addr, 4); + verify_size = 20; +#endif + + q->edns.cookie_status = COOKIE_INVALID; + siphash(q->edns.cookie, verify_size, + nsd->cookie_secrets[0].cookie_secret, hash, 8); + if(CRYPTO_memcmp(hash2verify, hash, 8) == 0 ) { + if (subtract_1982(cookie_time, now_uint32) < 1800) { + q->edns.cookie_status = COOKIE_VALID_REUSE; + memcpy(q->edns.cookie + 16, hash, 8); + } else + q->edns.cookie_status = COOKIE_VALID; + return; + } + for(i = 1; + i < (int)nsd->cookie_count && i < NSD_COOKIE_HISTORY_SIZE; + i++) { + siphash(q->edns.cookie, verify_size, + nsd->cookie_secrets[i].cookie_secret, hash, 8); + if(CRYPTO_memcmp(hash2verify, hash, 8) == 0 ) { + q->edns.cookie_status = COOKIE_VALID; + return; + } + } +} + +void cookie_create(query_type *q, struct nsd* nsd, uint32_t *now_p) +{ + uint8_t hash[8]; + uint32_t now_uint32; + + if (q->edns.cookie_status == COOKIE_VALID_REUSE) + return; + + now_uint32 = *now_p ? *now_p : (*now_p = (uint32_t)time(NULL)); + q->edns.cookie[ 8] = 1; + q->edns.cookie[ 9] = 0; + q->edns.cookie[10] = 0; + q->edns.cookie[11] = 0; + q->edns.cookie[12] = (now_uint32 & 0xFF000000) >> 24; + q->edns.cookie[13] = (now_uint32 & 0x00FF0000) >> 16; + q->edns.cookie[14] = (now_uint32 & 0x0000FF00) >> 8; + q->edns.cookie[15] = now_uint32 & 0x000000FF; +#ifdef INET6 + if (q->addr.ss_family == AF_INET6) { + memcpy( q->edns.cookie + 16 + , &((struct sockaddr_in6 *)&q->addr)->sin6_addr, 16); + siphash(q->edns.cookie, 32, nsd->cookie_secrets[0].cookie_secret, hash, 8); + } else { + memcpy( q->edns.cookie + 16 + , &((struct sockaddr_in *)&q->addr)->sin_addr, 4); + siphash(q->edns.cookie, 20, nsd->cookie_secrets[0].cookie_secret, hash, 8); + } +#else + memcpy( q->edns.cookie + 16, &q->addr.sin_addr, 4); + siphash(q->edns.cookie, 20, nsd->cookie_secrets[0].cookie_secret, hash, 8); +#endif + memcpy(q->edns.cookie + 16, hash, 8); +} + diff --git a/usr.sbin/nsd/edns.h b/usr.sbin/nsd/edns.h index c230a332940..942312c036b 100644 --- a/usr.sbin/nsd/edns.h +++ b/usr.sbin/nsd/edns.h @@ -18,6 +18,7 @@ struct query; #define OPT_RDATA 2 /* holds the rdata length comes after OPT_LEN */ #define OPT_HDR 4U /* NSID opt header length */ #define NSID_CODE 3 /* nsid option code */ +#define COOKIE_CODE 10 /* COOKIE option code */ #define EDE_CODE 15 /* Extended DNS Errors option code */ #define DNSSEC_OK_MASK 0x8000U /* do bit mask */ @@ -26,8 +27,8 @@ struct edns_data char ok[OPT_LEN]; char error[OPT_LEN]; char rdata_none[OPT_RDATA]; - char rdata_nsid[OPT_RDATA]; char nsid[OPT_HDR]; + char cookie[OPT_HDR]; }; typedef struct edns_data edns_data_type; @@ -40,17 +41,30 @@ enum edns_status }; typedef enum edns_status edns_status_type; +enum cookie_status +{ + COOKIE_NOT_PRESENT, + COOKIE_UNVERIFIED, + COOKIE_VALID, + COOKIE_VALID_REUSE, + COOKIE_INVALID +}; +typedef enum cookie_status cookie_status_type; + struct edns_record { - edns_status_type status; - size_t position; - size_t maxlen; - size_t opt_reserved_space; - int dnssec_ok; - int nsid; - int ede; /* RFC 8914 - Extended DNS Errors */ - char* ede_text; /* RFC 8914 - Extended DNS Errors text*/ - uint16_t ede_text_len; + edns_status_type status; + size_t position; + size_t maxlen; + size_t opt_reserved_space; + int dnssec_ok; + int nsid; + cookie_status_type cookie_status; + size_t cookie_len; + uint8_t cookie[40]; + int ede; /* RFC 8914 - Extended DNS Errors */ + char* ede_text; /* RFC 8914 - Extended DNS Errors text*/ + uint16_t ede_text_len; }; typedef struct edns_record edns_record_type; @@ -86,4 +100,7 @@ size_t edns_reserved_space(edns_record_type *data); void edns_init_nsid(edns_data_type *data, uint16_t nsid_len); +void cookie_verify(struct query *q, struct nsd* nsd, uint32_t *now_p); +void cookie_create(struct query *q, struct nsd* nsd, uint32_t *now_p); + #endif /* _EDNS_H_ */ diff --git a/usr.sbin/nsd/namedb.c b/usr.sbin/nsd/namedb.c index 3ee97ca1e68..06bef71147c 100644 --- a/usr.sbin/nsd/namedb.c +++ b/usr.sbin/nsd/namedb.c @@ -581,13 +581,18 @@ domain_find_parent_zone(namedb_type* db, zone_type* zone) domain_type * domain_find_ns_rrsets(domain_type* domain, zone_type* zone, rrset_type **ns) { + /* return highest NS RRset in the zone that is a delegation above */ + domain_type* result = NULL; while (domain && domain != zone->apex) { *ns = domain_find_rrset(domain, zone, TYPE_NS); if (*ns) - return domain; + result = domain; domain = domain->parent; } + if(result) + return result; + *ns = NULL; return NULL; } diff --git a/usr.sbin/nsd/nsd-checkconf.8.in b/usr.sbin/nsd/nsd-checkconf.8.in index 74d5648dcb8..0a514e6dab0 100644 --- a/usr.sbin/nsd/nsd-checkconf.8.in +++ b/usr.sbin/nsd/nsd-checkconf.8.in @@ -1,4 +1,4 @@ -.TH "nsd\-checkconf" "8" "Apr 6, 2021" "NLnet Labs" "nsd 4.3.6" +.TH "nsd\-checkconf" "8" "Jul 22, 2021" "NLnet Labs" "nsd 4.3.7" .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved. .\" See LICENSE for the license. .SH "NAME" @@ -17,6 +17,8 @@ .IR pattern ] .RB [ \-s .IR keyname ] +.RB [ \-t +.IR tlsauthname ] .I configfile .SH "DESCRIPTION" .B nsd\-checkconf @@ -68,6 +70,10 @@ option is not given, nothing is printed. Prints the key secret (base64 blob) configured for this key in the config file. Used to help shell scripts parse the config file. .TP +.B \-t\fI tls-auth +Prints the authentication domain name configured for this tls-auth clause in the +config file. Used to help shell scripts parse the config file. +.TP .B \-p\fI pattern Return the option specified with .B \-o diff --git a/usr.sbin/nsd/nsd-checkconf.c b/usr.sbin/nsd/nsd-checkconf.c index 28fba0dbf7f..342b308fa08 100644 --- a/usr.sbin/nsd/nsd-checkconf.c +++ b/usr.sbin/nsd/nsd-checkconf.c @@ -140,20 +140,21 @@ static void usage(void) { fprintf(stderr, "usage: nsd-checkconf [-v|-h] [-o option] [-z zonename]\n"); - fprintf(stderr, " [-s keyname] \n"); + fprintf(stderr, " [-s keyname] [-t tlsauthname] \n"); fprintf(stderr, " Checks NSD configuration file for errors.\n"); fprintf(stderr, " Version %s. Report bugs to <%s>.\n\n", PACKAGE_VERSION, PACKAGE_BUGREPORT); fprintf(stderr, "Use with a configfile as argument to check syntax.\n"); - fprintf(stderr, "Use with -o, -z or -s options to query the configuration.\n\n"); - fprintf(stderr, "-v Verbose, echo settings that take effect to std output.\n"); - fprintf(stderr, "-h Print this help information.\n"); - fprintf(stderr, "-f Use with -o to print final pathnames, ie. with chroot.\n"); - fprintf(stderr, "-o option Print value of the option specified to stdout.\n"); - fprintf(stderr, "-p pattern Print option value for the pattern given.\n"); - fprintf(stderr, "-z zonename Print option value for the zone given.\n"); - fprintf(stderr, "-a keyname Print algorithm name for the TSIG key.\n"); - fprintf(stderr, "-s keyname Print base64 secret blob for the TSIG key.\n"); + fprintf(stderr, "Use with -o, -z, -t or -s options to query the configuration.\n\n"); + fprintf(stderr, "-v Verbose, echo settings that take effect to std output.\n"); + fprintf(stderr, "-h Print this help information.\n"); + fprintf(stderr, "-f Use with -o to print final pathnames, ie. with chroot.\n"); + fprintf(stderr, "-o option Print value of the option specified to stdout.\n"); + fprintf(stderr, "-p pattern Print option value for the pattern given.\n"); + fprintf(stderr, "-z zonename Print option value for the zone given.\n"); + fprintf(stderr, "-a keyname Print algorithm name for the TSIG key.\n"); + fprintf(stderr, "-s keyname Print base64 secret blob for the TSIG key.\n"); + fprintf(stderr, "-t tls-auth-name Print auth domain name for the tls-auth clause.\n"); exit(1); } @@ -196,9 +197,15 @@ quote_acl(acl_options_type* acl) { while(acl) { - printf("%s %s\n", acl->ip_address_spec, - acl->nokey?"NOKEY":(acl->blocked?"BLOCKED": - (acl->key_name?acl->key_name:"(null)"))); + if (acl->tls_auth_name) + printf("%s %s %s\n", acl->ip_address_spec, + acl->nokey?"NOKEY":(acl->blocked?"BLOCKED": + (acl->key_name?acl->key_name:"(null)")), + acl->tls_auth_name?acl->tls_auth_name:""); + else + printf("%s %s\n", acl->ip_address_spec, + acl->nokey?"NOKEY":(acl->blocked?"BLOCKED": + (acl->key_name?acl->key_name:"(null)"))); acl=acl->next; } } @@ -213,9 +220,15 @@ print_acl(const char* varname, acl_options_type* acl) printf("AXFR "); if(acl->allow_udp) printf("UDP "); - printf("%s %s\n", acl->ip_address_spec, - acl->nokey?"NOKEY":(acl->blocked?"BLOCKED": - (acl->key_name?acl->key_name:"(null)"))); + if (acl->tls_auth_name) + printf("%s %s %s\n", acl->ip_address_spec, + acl->nokey?"NOKEY":(acl->blocked?"BLOCKED": + (acl->key_name?acl->key_name:"(null)")), + acl->tls_auth_name?acl->tls_auth_name:""); + else + printf("%s %s\n", acl->ip_address_spec, + acl->nokey?"NOKEY":(acl->blocked?"BLOCKED": + (acl->key_name?acl->key_name:"(null)"))); if(verbosity>1) { printf("\t# %s", acl->is_ipv6?"ip6":"ip4"); if(acl->port == 0) printf(" noport"); @@ -263,7 +276,7 @@ print_acl_ips(const char* varname, acl_options_type* acl) void config_print_zone(nsd_options_type* opt, const char* k, int s, const char *o, - const char *z, const char* pat, int final) + const char *z, const char* pat, const char* tls, int final) { ip_address_option_type* ip; @@ -282,6 +295,17 @@ config_print_zone(nsd_options_type* opt, const char* k, int s, const char *o, return; } + if (tls) { + /* find tlsauth */ + tls_auth_options_type* tlsauth = tls_auth_options_find(opt, tls); + if(tlsauth) { + quote(tlsauth->auth_domain_name); + return; + } + printf("Could not find tls-auth %s\n", tls); + return; + } + if (!o) { return; } @@ -398,6 +422,10 @@ config_print_zone(nsd_options_type* opt, const char* k, int s, const char *o, SERV_GET_STR(tls_service_ocsp, o); SERV_GET_STR(tls_service_pem, o); SERV_GET_STR(tls_port, o); + SERV_GET_STR(tls_cert_bundle, o); + SERV_GET_STR(cookie_secret, o); + SERV_GET_STR(cookie_secret_file, o); + SERV_GET_BIN(answer_cookie, o); /* int */ SERV_GET_INT(server_count, o); SERV_GET_INT(tcp_count, o); @@ -502,6 +530,7 @@ config_test_print_server(nsd_options_type* opt) { ip_address_option_type* ip; key_options_type* key; + tls_auth_options_type* tlsauth; zone_options_type* zone; pattern_options_type* pat; @@ -606,6 +635,12 @@ config_test_print_server(nsd_options_type* opt) print_string_var("tls-service-pem:", opt->tls_service_pem); print_string_var("tls-service-ocsp:", opt->tls_service_ocsp); print_string_var("tls-port:", opt->tls_port); + print_string_var("tls-cert-bundle:", opt->tls_cert_bundle); + printf("\tanswer-cookie: %s\n", opt->answer_cookie?"yes":"no"); + if (opt->cookie_secret) + print_string_var("cookie-secret:", opt->cookie_secret); + if (opt->cookie_secret_file) + print_string_var("cookie-secret-file:", opt->cookie_secret_file); #ifdef USE_DNSTAP printf("\ndnstap:\n"); @@ -636,6 +671,12 @@ config_test_print_server(nsd_options_type* opt) print_string_var("algorithm:", key->algorithm); print_string_var("secret:", key->secret); } + RBTREE_FOR(tlsauth, tls_auth_options_type*, opt->tls_auths) + { + printf("\ntls-auth:\n"); + print_string_var("name:", tlsauth->name); + print_string_var("auth-domain-name:", tlsauth->auth_domain_name); + } RBTREE_FOR(pat, pattern_options_type*, opt->patterns) { if(pat->implicit) continue; @@ -764,9 +805,10 @@ additional_checks(nsd_options_type* opt, const char* filename) errors ++; } if(errors != 0) { - fprintf(stderr, "%s: %d semantic errors in %d zones, %d keys.\n", + fprintf(stderr, "%s: %d semantic errors in %d zones, %d keys, %d tls-auth.\n", filename, errors, (int)nsd_options_num_zones(opt), - (int)opt->keys->count); + (int)opt->keys->count, + (int)opt->tls_auths->count); } return (errors == 0); @@ -782,6 +824,7 @@ main(int argc, char* argv[]) const char * conf_opt = NULL; /* what option do you want? Can be NULL -> print all */ const char * conf_zone = NULL; /* what zone are we talking about */ const char * conf_key = NULL; /* what key is needed */ + const char * conf_tlsauth = NULL; /* what tls-auth is needed */ const char * conf_pat = NULL; /* what pattern is talked about */ const char* configfile; nsd_options_type *options; @@ -789,7 +832,7 @@ main(int argc, char* argv[]) log_init("nsd-checkconf"); /* Parse the command line... */ - while ((c = getopt(argc, argv, "vfho:a:p:s:z:")) != -1) { + while ((c = getopt(argc, argv, "vfho:a:p:s:z:t:")) != -1) { switch (c) { case 'v': verbose = 1; @@ -819,6 +862,9 @@ main(int argc, char* argv[]) conf_key = optarg; key_sec = 1; break; + case 't': + conf_tlsauth = optarg; + break; case 'z': conf_zone = optarg; break; @@ -841,17 +887,18 @@ main(int argc, char* argv[]) !additional_checks(options, configfile)) { exit(2); } - if (conf_opt || conf_key) { + if (conf_opt || conf_key || conf_tlsauth) { config_print_zone(options, conf_key, key_sec, - underscore(conf_opt), conf_zone, conf_pat, final); + underscore(conf_opt), conf_zone, conf_pat, conf_tlsauth, final); } else { if (verbose) { printf("# Read file %s: %d patterns, %d fixed-zones, " - "%d keys.\n", + "%d keys, %d tls-auth.\n", configfile, (int)options->patterns->count, (int)nsd_options_num_zones(options), - (int)options->keys->count); + (int)options->keys->count, + (int)options->tls_auths->count); config_test_print_server(options); } } diff --git a/usr.sbin/nsd/nsd-checkzone.8.in b/usr.sbin/nsd/nsd-checkzone.8.in index 7ca9c268d3b..6b31cf9ea7a 100644 --- a/usr.sbin/nsd/nsd-checkzone.8.in +++ b/usr.sbin/nsd/nsd-checkzone.8.in @@ -1,4 +1,4 @@ -.TH "nsd\-checkzone" "8" "Apr 6, 2021" "NLnet Labs" "nsd 4.3.6" +.TH "nsd\-checkzone" "8" "Jul 22, 2021" "NLnet Labs" "nsd 4.3.7" .\" Copyright (c) 2014, NLnet Labs. All rights reserved. .\" See LICENSE for the license. .SH "NAME" diff --git a/usr.sbin/nsd/nsd-control.8.in b/usr.sbin/nsd/nsd-control.8.in index 8aa99c8245d..ecefed3051c 100644 --- a/usr.sbin/nsd/nsd-control.8.in +++ b/usr.sbin/nsd/nsd-control.8.in @@ -1,4 +1,4 @@ -.TH "nsd\-control" "8" "Apr 6, 2021" "NLnet Labs" "nsd 4.3.6" +.TH "nsd\-control" "8" "Jul 22, 2021" "NLnet Labs" "nsd 4.3.7" .\" Copyright (c) 2011, NLnet Labs. All rights reserved. .\" See LICENSE for the license. .SH "NAME" @@ -177,6 +177,41 @@ given key. Delete the TSIG key with the given name. Prints error if the key is still in use by some zone. The changes are only in-memory and are gone next restart, for lasting changes edit the nsd.conf file or a file included from it. +.TP +.B add_cookie_secret +Add or replace a cookie secret persistently. needs to be an 128 bit +hex string. + +Cookie secrets can be either \fIactive\fR or \fIstaging\fR. \fIActive\fR cookie +secrets are used to create DNS Cookies, but verification of a DNS Cookie +succeeds with any of the \fIactive\fR or \fIstaging\fR cookie secrets. The +state of the current cookie secrets can be printed with the +\fBprint_cookie_secrets\fR command. + +When there are no cookie secrets configured yet, the is added as +\fIactive\fR. If there is already an \fIactive\fR cookie secret, the +is added as \fIstaging\fR or replacing an existing \fIstaging\fR secret. + +To "roll" a cookie secret used in an anycast set. The new secret has to be +added as staging secret to \fBall\fR nodes in the anycast set. When \fBall\fR +nodes can verify DNS Cookies with the new secret, the new secret can be +activated with the \fBactivate_cookie_secret\fR command. After \fBall\fR nodes +have the new secret \fIactive\fR for at least one hour, the previous secret can +be dropped with the \fBdrop_cookie_secret\fR command. + +Persistence is accomplished by writing to a file which if configured with the +\fBcookie\-secret\-file\fR option in the server section of the config file. +The default value for that is: @configdir@/nsd_cookiesecrets.txt . +.TP +.B drop_cookie_secret +Drop the \fIstaging\fR cookie secret. +.TP +.B activate_cookie_secret +Make the current \fIstaging\fR cookie secret \fIactive\fR, and the current +\fIactive\fR cookie secret \fIstaging\fR. +.TP +.B print_cookie_secrets +Show the current configured cookie secrets with their status. .SH "EXIT CODE" The nsd\-control program exits with status code 1 on error, 0 on success. .SH "SET UP" diff --git a/usr.sbin/nsd/nsd-control.c b/usr.sbin/nsd/nsd-control.c index 3d77790446f..be615a2f915 100644 --- a/usr.sbin/nsd/nsd-control.c +++ b/usr.sbin/nsd/nsd-control.c @@ -113,6 +113,10 @@ usage() printf(" add_tsig [algo] add new key with the given parameters\n"); printf(" assoc_tsig associate with given tsig name\n"); printf(" del_tsig delete tsig from configuration\n"); + printf(" add_cookie_secret add (or replace) a new cookie secret \n"); + printf(" drop_cookie_secret drop a staging cookie secret\n"); + printf(" activate_cookie_secret make a staging cookie secret active\n"); + printf(" print_cookie_secrets show all cookie secrets with their status\n"); exit(1); } diff --git a/usr.sbin/nsd/nsd.8.in b/usr.sbin/nsd/nsd.8.in index 38aa7aa2d70..443c0867e41 100644 --- a/usr.sbin/nsd/nsd.8.in +++ b/usr.sbin/nsd/nsd.8.in @@ -1,9 +1,9 @@ -.TH "NSD" "8" "Apr 6, 2021" "NLnet Labs" "NSD 4.3.6" +.TH "NSD" "8" "Jul 22, 2021" "NLnet Labs" "NSD 4.3.7" .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved. .\" See LICENSE for the license. .SH "NAME" .B nsd -\- Name Server Daemon (NSD) version 4.3.6. +\- Name Server Daemon (NSD) version 4.3.7. .SH "SYNOPSIS" .B nsd .RB [ \-4 ] diff --git a/usr.sbin/nsd/nsd.c b/usr.sbin/nsd/nsd.c index 860a76e0553..e3e8896eee7 100644 --- a/usr.sbin/nsd/nsd.c +++ b/usr.sbin/nsd/nsd.c @@ -25,6 +25,9 @@ #include #endif /* HAVE_LOGIN_CAP_H */ #endif /* HAVE_SETUSERCONTEXT */ +#ifdef HAVE_OPENSSL_RAND_H +#include +#endif #include #include @@ -260,7 +263,6 @@ figure_default_sockets( const char *udp_port, const char *tcp_port, const struct addrinfo *hints) { - int r; size_t i = 0, n = 1; struct addrinfo ai[2] = { *hints, *hints }; @@ -301,6 +303,7 @@ figure_default_sockets( * automatically mapped to our IPv6 socket. */ #ifdef IPV6_V6ONLY + int r; struct addrinfo *addrs[2] = { NULL, NULL }; if((r = getaddrinfo(NULL, udp_port, &ai[0], &addrs[0])) == 0 && @@ -551,12 +554,12 @@ print_sockets( for(i = 0; i < ifs; i++) { assert(udp[i].servers->size == servercnt); - addrport2str(&udp[i].addr.ai_addr, sockbuf, sizeof(sockbuf)); + addrport2str((void*)&udp[i].addr.ai_addr, sockbuf, sizeof(sockbuf)); print_socket_servers(&udp[i], serverbuf, serverbufsz); nsd_bitset_or(servers, servers, udp[i].servers); VERBOSITY(3, (LOG_NOTICE, fmt, sockbuf, "udp", serverbuf)); assert(tcp[i].servers->size == servercnt); - addrport2str(&tcp[i].addr.ai_addr, sockbuf, sizeof(sockbuf)); + addrport2str((void*)&tcp[i].addr.ai_addr, sockbuf, sizeof(sockbuf)); print_socket_servers(&tcp[i], serverbuf, serverbufsz); nsd_bitset_or(servers, servers, tcp[i].servers); VERBOSITY(3, (LOG_NOTICE, fmt, sockbuf, "tcp", serverbuf)); @@ -828,6 +831,40 @@ bind8_stats (struct nsd *nsd) } #endif /* BIND8_STATS */ +static +int cookie_secret_file_read(nsd_type* nsd) { + char secret[NSD_COOKIE_SECRET_SIZE * 2 + 2/*'\n' and '\0'*/]; + char const* file = nsd->options->cookie_secret_file; + FILE* f; + int corrupt = 0; + size_t count; + + assert( nsd->options->cookie_secret_file != NULL ); + f = fopen(file, "r"); + /* a non-existing cookie file is not an error */ + if( f == NULL ) { return errno != EPERM; } + /* cookie secret file exists and is readable */ + nsd->cookie_count = 0; + for( count = 0; count < NSD_COOKIE_HISTORY_SIZE; count++ ) { + size_t secret_len = 0; + ssize_t decoded_len = 0; + if( fgets(secret, sizeof(secret), f) == NULL ) { break; } + secret_len = strlen(secret); + if( secret_len == 0 ) { break; } + assert( secret_len <= sizeof(secret) ); + secret_len = secret[secret_len - 1] == '\n' ? secret_len - 1 : secret_len; + if( secret_len != NSD_COOKIE_SECRET_SIZE * 2 ) { corrupt++; break; } + /* needed for `hex_pton`; stripping potential `\n` */ + secret[secret_len] = '\0'; + decoded_len = hex_pton(secret, nsd->cookie_secrets[count].cookie_secret, + NSD_COOKIE_SECRET_SIZE); + if( decoded_len != NSD_COOKIE_SECRET_SIZE ) { corrupt++; break; } + nsd->cookie_count++; + } + fclose(f); + return corrupt == 0; +} + extern char *optarg; extern int optind; @@ -869,12 +906,15 @@ main(int argc, char *argv[]) nsd.chrootdir = 0; nsd.nsid = NULL; nsd.nsid_len = 0; + nsd.cookie_count = 0; nsd.child_count = 0; nsd.maximum_tcp_count = 0; nsd.current_tcp_count = 0; nsd.file_rotation_ok = 0; + nsd.do_answer_cookie = 1; + /* Set up our default identity to gethostname(2) */ if (gethostname(hostname, MAXHOSTNAMELEN) == 0) { nsd.identity = hostname; @@ -1151,6 +1191,36 @@ main(int argc, char *argv[]) #endif /* IPV6 MTU) */ #endif /* defined(INET6) */ + nsd.do_answer_cookie = nsd.options->answer_cookie; + if (nsd.cookie_count > 0) + ; /* pass */ + + else if (nsd.options->cookie_secret) { + ssize_t len = hex_pton(nsd.options->cookie_secret, + nsd.cookie_secrets[0].cookie_secret, NSD_COOKIE_SECRET_SIZE); + if (len != NSD_COOKIE_SECRET_SIZE ) { + error("A cookie secret must be a " + "128 bit hex string"); + } + nsd.cookie_count = 1; + } else { + size_t j; + size_t const cookie_secret_len = NSD_COOKIE_SECRET_SIZE; + /* Calculate a new random secret */ + srandom(getpid() ^ time(NULL)); + + for( j = 0; j < NSD_COOKIE_HISTORY_SIZE; j++) { +#if defined(HAVE_SSL) + if (!RAND_status() + || !RAND_bytes(nsd.cookie_secrets[j].cookie_secret, cookie_secret_len)) +#endif + for (i = 0; i < cookie_secret_len; i++) + nsd.cookie_secrets[j].cookie_secret[i] = random_generate(256); + } + // XXX: all we have is a random cookie, still pretend we have one + nsd.cookie_count = 1; + } + if (nsd.nsid_len == 0 && nsd.options->nsid) { if (strlen(nsd.options->nsid) % 2 != 0) { error("the NSID must be a hex string of an even length."); @@ -1432,6 +1502,11 @@ main(int argc, char *argv[]) } #endif /* HAVE_SSL */ + if(nsd.options->cookie_secret_file && nsd.options->cookie_secret_file[0] + && !cookie_secret_file_read(&nsd) ) { + log_msg(LOG_ERR, "cookie secret file corrupt or not readable"); + } + /* Unless we're debugging, fork... */ if (!nsd.debug) { int fd; @@ -1590,13 +1665,6 @@ main(int argc, char *argv[]) options_zonestatnames_create(nsd.options); server_zonestat_alloc(&nsd); #endif /* USE_ZONE_STATS */ -#ifdef USE_DNSTAP - if(nsd.options->dnstap_enable) { - nsd.dt_collector = dt_collector_create(&nsd); - dt_collector_start(nsd.dt_collector, &nsd); - } -#endif /* USE_DNSTAP */ - if(nsd.server_kind == NSD_SERVER_MAIN) { server_prepare_xfrd(&nsd); /* xfrd forks this before reading database, so it does not get @@ -1604,6 +1672,12 @@ main(int argc, char *argv[]) server_start_xfrd(&nsd, 0, 0); /* close zonelistfile in non-xfrd processes */ zone_list_close(nsd.options); +#ifdef USE_DNSTAP + if(nsd.options->dnstap_enable) { + nsd.dt_collector = dt_collector_create(&nsd); + dt_collector_start(nsd.dt_collector, &nsd); + } +#endif /* USE_DNSTAP */ } if (server_prepare(&nsd) != 0) { unlinkpid(nsd.pidfile); diff --git a/usr.sbin/nsd/nsd.conf.5.in b/usr.sbin/nsd/nsd.conf.5.in index 9cfe7bfbfdb..ad4bd542650 100644 --- a/usr.sbin/nsd/nsd.conf.5.in +++ b/usr.sbin/nsd/nsd.conf.5.in @@ -1,4 +1,4 @@ -.TH "nsd.conf" "5" "Apr 6, 2021" "NLnet Labs" "nsd 4.3.6" +.TH "nsd.conf" "5" "Jul 22, 2021" "NLnet Labs" "nsd 4.3.7" .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved. .\" See LICENSE for the license. .SH "NAME" @@ -104,6 +104,7 @@ At the top level only .BR key: , .BR pattern: , .BR zone: , +.BR tls-auth: , and .B remote-control: are allowed. These are followed by their attributes or a new top-level keyword. The @@ -117,6 +118,9 @@ server. A attribute is used to define keys for authentication. The .B pattern: attribute is followed by the zone options for zones that use the pattern. +A +.B tls-auth: +attribute is used to define credentials for authenticating an outgoing TLS connection used for XFR-over-TLS. .P Files can be included using the .B include: @@ -488,6 +492,29 @@ specific queries to receive this qps limit instead of the normal limit. With the value 0 the rate is unlimited. .\" rrlend .TP +.B answer\-cookie:\fR +Enable to answer to requests containig DNS Cookies as specified in RFC7873. +Default is yes. +.TP +.B cookie\-secret:\fR <128 bit hex string> +Servers in an anycast deployment need to be able to verify each other's DNS +Server Cookies. For this they need to share the secret used to construct and +verify the DNS Cookies. Default is a 128 bits random secret generated at +startup time. This option is ignored if a \fBcookie\-secret\-file\fR is +present. In that case the secrets from that file are used in DNS Cookie +calculations. +.TP +.B cookie\-secret\-file:\fR +File from which the secrets are read used in DNS Cookie calculations. When this +file exists, the secrets in this file are used and the secret specified by the +\fBcookie-secret\fR option is ignored. +Default is @configdir@/nsd_cookiesecrets.txt + +The content of this file must be manipulated with the \fBadd_cookie_secret\fR, +\fBdrop_cookie_secret\fR and \fBactivate_cookie_secret\fR commands to the +\fInsd\-control\fR(8) tool. Please see that manpage how to perform a safe +cookie secret rollover. +.TP .B tls\-service\-key:\fR If enabled, the server provides TLS service on TCP sockets with the TLS service port number. The port number (853) is configured with tls\-port. @@ -519,6 +546,11 @@ openssl ocsp -no_nonce \\ .B tls\-port:\fR The port number on which to provide TCP TLS service, default is 853, only interfaces configured with that port number as @number get DNS over TLS service. +.TP +.B tls\-cert\-bundle:\fR +If null or "", the default verify locations are used. Set it to the certificate +bundle file, for example "/etc/pki/tls/certs/ca-bundle.crt". These certificates +are used for authenticating Transfer over TLS (XoT) connections. .SS "Remote Control" The .B remote\-control: @@ -687,10 +719,12 @@ Note the ip\-spec ranges do not use spaces around the /, &, @ and \- symbols. .RE .TP -.B request\-xfr:\fR [AXFR|UDP] +.B request\-xfr:\fR [AXFR|UDP] [tls\-auth\-name] Access control list. The listed address (the master) is queried for AXFR/IXFR on update. A port number can be added using a suffix of @number, -for example 1.2.3.4@5300. The specified key is used during AXFR/IXFR. +for example 1.2.3.4@5300. The specified key is used during AXFR/IXFR. If +tls-auth-name is included, the specified tls-auth clause will be used to +perform authenticated XFR-over-TLS. .P .RS If the AXFR option is given, the server will not be contacted with @@ -704,6 +738,11 @@ requests. You should deploy TSIG when allowing UDP transport, to authenticate notifies and zone transfers. Otherwise, NSD is more vulnerable for Kaminsky\-style attacks. If the UDP option is left out then IXFR will be transmitted using TCP. +.P +If a tls-auth-name is given then TLS (by default on port 853) will be used +for all zone transfers for the zone. If authentication of the master based on +the specified tls-auth authentication information fails, the XFR request will +not be sent. Support for TLS 1.3 is required for XFR-over-TLS. .RE .TP .B allow\-axfr\-fallback:\fR @@ -834,6 +873,19 @@ file, which may have different security policies, can be split apart. The content of the secret is the agreed base64 secret content. To make it up, enter a password (its length must be a multiple of 4 characters, A\-Za\-z0\-9), or use dev-random output through a base64 encode filter. +.SS "TLS Auth Declarations" +The +.B tls-auth: +clause establishes authentication attributes to use when authenticating +the far end of an outgoing TLS connection used in access control lists for XFR-over-TLS. +It has the following attributes. +.TP +.B name:\fR +The tls-auth name. Used to refer to this TLS authentication information in the +access control list. +.TP +.B auth-domain-name:\fR +The authentication domain name as defined in RFC8310. .SS DNSTAP Logging Options DNSTAP support, when compiled in, is enabled in the \fBdnstap:\fR section. This starts a collector process that writes the log information to the diff --git a/usr.sbin/nsd/nsd.conf.sample.in b/usr.sbin/nsd/nsd.conf.sample.in index 61eedef3db4..6077ccc85c9 100644 --- a/usr.sbin/nsd/nsd.conf.sample.in +++ b/usr.sbin/nsd/nsd.conf.sample.in @@ -250,6 +250,10 @@ server: # tls-service-pem: "path/to/publiccertfile.pem" # tls-service-ocsp: "path/to/ocsp.pem" # tls-port: 853 + + # Certificates used to authenticate connections made upstream for + # Transfers over TLS (XoT). Default is "" (default verify locations). + # tls-cert-bundle: "path/to/ca-bundle.pem" # DNSTAP config section, if compiled with that # dnstap: @@ -309,6 +313,17 @@ remote-control: # e.g. from dd if=/dev/random of=/dev/stdout count=1 bs=32 | base64 #secret: "K2tf3TRjvQkVCmJF3/Z9vA==" +# The tls-auth clause establishes authentication attributes to use when +# authenticating the far end of an outgoing TLS connection in access control +# lists used for XFR-over-TLS. If authentication fails, the XFR request will not +# be made. Support for TLS 1.3 is required for XFR-over-TLS. It has the +# following attributes: +# +# tls-auth: + # The tls-auth name. Used to refer to this TLS auth information in the access control list. + #name: "tls-authname" + # The authentication domain name as defined in RFC8310. + #auth-domain-name: "example.com" # Patterns have zone configuration and they are shared by one or more zones. # @@ -354,7 +369,9 @@ remote-control: # By default, a slave will request a zone transfer with IXFR/TCP. # If you want to make use of IXFR/UDP use: UDP addr tsigkey # for a master that only speaks AXFR (like NSD) use AXFR addr tsigkey + # If you want to require use of XFR-over-TLS use: addr tsigkey tlsauthname #request-xfr: 192.0.2.2 the_tsig_key_name + #request-xfr: 192.0.2.2 the_tsig_key_name the_tls_auth_name # Attention: You cannot use UDP and AXFR together. AXFR is always over # TCP. If you use UDP, we higly recommend you to deploy TSIG. # Allow AXFR fallback if the master does not support IXFR. Default diff --git a/usr.sbin/nsd/nsd.h b/usr.sbin/nsd/nsd.h index 86b8ad66ee2..177d5d5cf8d 100644 --- a/usr.sbin/nsd/nsd.h +++ b/usr.sbin/nsd/nsd.h @@ -185,6 +185,15 @@ struct nsd_child #endif }; +#define NSD_COOKIE_HISTORY_SIZE 2 +#define NSD_COOKIE_SECRET_SIZE 16 + +typedef struct cookie_secret cookie_secret_type; +struct cookie_secret { + /** cookie secret */ + uint8_t cookie_secret[NSD_COOKIE_SECRET_SIZE]; +}; + /* NSD configuration and run-time variables */ typedef struct nsd nsd_type; struct nsd @@ -305,14 +314,30 @@ struct nsd /* the dnstap collector process info */ struct dt_collector* dt_collector; /* the pipes from server processes to the dt_collector, - * arrays of size child_count. Kept open for (re-)forks. */ + * arrays of size child_count * 2. Kept open for (re-)forks. */ int *dt_collector_fd_send, *dt_collector_fd_recv; + /* the pipes from server processes to the dt_collector. Initially + * these point halfway into dt_collector_fd_send, but during reload + * the pointer is swapped with dt_collector_fd_send in order to + * to prevent writing to the dnstap collector by old serve childs + * simultaneous with new serve childs. */ + int *dt_collector_fd_swap; #endif /* USE_DNSTAP */ /* ratelimit for errors, time value */ time_t err_limit_time; /* ratelimit for errors, packet count */ unsigned int err_limit_count; + /** do answer with server cookie when request contained cookie option */ + int do_answer_cookie; + + /** how many cookies are there in the cookies array */ + size_t cookie_count; + + /* keep track of the last `NSD_COOKIE_HISTORY_SIZE` + * cookies as per rfc requirement .*/ + cookie_secret_type cookie_secrets[NSD_COOKIE_HISTORY_SIZE]; + struct nsd_options* options; #ifdef HAVE_SSL diff --git a/usr.sbin/nsd/nsec3.c b/usr.sbin/nsd/nsec3.c index 3139236af20..3cdd572932e 100644 --- a/usr.sbin/nsd/nsec3.c +++ b/usr.sbin/nsd/nsec3.c @@ -391,7 +391,7 @@ nsec3_chain_find_prev(struct zone* zone, struct domain* domain) return (domain_type*)r->key; } } - if(zone->nsec3_last) + if(zone->nsec3_last && zone->nsec3_last != domain) return zone->nsec3_last; return NULL; } diff --git a/usr.sbin/nsd/options.c b/usr.sbin/nsd/options.c index b5dd5fe0ae9..d8fe022b412 100644 --- a/usr.sbin/nsd/options.c +++ b/usr.sbin/nsd/options.c @@ -52,6 +52,7 @@ nsd_options_create(region_type* region) opt->zonestatnames = rbtree_create(opt->region, rbtree_strcmp); opt->patterns = rbtree_create(region, rbtree_strcmp); opt->keys = rbtree_create(region, rbtree_strcmp); + opt->tls_auths = rbtree_create(region, rbtree_strcmp); opt->ip_addresses = NULL; opt->ip_transparent = 0; opt->ip_freebind = 0; @@ -129,6 +130,10 @@ nsd_options_create(region_type* region) opt->tls_service_ocsp = NULL; opt->tls_service_pem = NULL; opt->tls_port = TLS_PORT; + opt->tls_cert_bundle = NULL; + opt->answer_cookie = 1; + opt->cookie_secret = NULL; + opt->cookie_secret_file = CONFIGDIR"/nsd_cookiesecrets.txt"; opt->control_enable = 0; opt->control_interface = NULL; opt->control_port = NSD_CONTROL_PORT; @@ -200,6 +205,7 @@ parse_options_file(struct nsd_options* opt, const char* file, cfg_parser->pattern = NULL; cfg_parser->zone = NULL; cfg_parser->key = NULL; + cfg_parser->tls_auth = NULL; in = fopen(cfg_parser->filename, "r"); if(!in) { @@ -244,6 +250,14 @@ parse_options_file(struct nsd_options* opt, const char* file, } for(acl=pat->request_xfr; acl; acl=acl->next) { + /* Find tls_auth */ + if (!acl->tls_auth_name) + ; /* pass */ + else if (!(acl->tls_auth_options = + tls_auth_options_find(opt, acl->tls_auth_name))) + c_error("tls_auth %s in pattern %s could not be found", + acl->tls_auth_name, pat->pname); + /* Find key */ if(acl->nokey || acl->blocked) continue; acl->key_options = key_options_find(opt, acl->key_name); @@ -806,6 +820,11 @@ acl_equal(struct acl_options* p, struct acl_options* q) } else if(p->key_name && !q->key_name) return 0; else if(!p->key_name && q->key_name) return 0; /* key_options is derived from key_name */ + if(p->tls_auth_name && q->tls_auth_name) { + if(strcmp(p->tls_auth_name, q->tls_auth_name)!=0) return 0; + } else if(p->tls_auth_name && !q->tls_auth_name) return 0; + else if(!p->tls_auth_name && q->tls_auth_name) return 0; + /* tls_auth_options is derived from tls_auth_name */ return 1; } @@ -873,6 +892,9 @@ acl_delete(region_type* region, struct acl_options* acl) if(acl->key_name) region_recycle(region, (void*)acl->key_name, strlen(acl->key_name)+1); + if(acl->tls_auth_name) + region_recycle(region, (void*)acl->tls_auth_name, + strlen(acl->tls_auth_name)+1); /* key_options is a convenience pointer, not owned by the acl */ region_recycle(region, acl, sizeof(*acl)); } @@ -928,8 +950,11 @@ copy_acl(region_type* region, struct acl_options* a) b->ip_address_spec = region_strdup(region, a->ip_address_spec); if(a->key_name) b->key_name = region_strdup(region, a->key_name); + if(a->tls_auth_name) + b->tls_auth_name = region_strdup(region, a->tls_auth_name); b->next = NULL; b->key_options = NULL; + b->tls_auth_options = NULL; return b; } @@ -943,6 +968,10 @@ copy_acl_list(struct nsd_options* opt, struct acl_options* a) if(b->key_name) b->key_options = key_options_find(opt, b->key_name); else b->key_options = NULL; + /* fixup tls_auth_options */ + if(b->tls_auth_name) + b->tls_auth_options = tls_auth_options_find(opt, b->tls_auth_name); + else b->tls_auth_options = NULL; /* link as last into list */ b->next = NULL; @@ -1178,6 +1207,7 @@ marshal_acl(struct buffer* b, struct acl_options* acl) buffer_write(b, acl, sizeof(*acl)); marshal_str(b, acl->ip_address_spec); marshal_str(b, acl->key_name); + marshal_str(b, acl->tls_auth_name); } static struct acl_options* @@ -1188,8 +1218,10 @@ unmarshal_acl(region_type* r, struct buffer* b) buffer_read(b, acl, sizeof(*acl)); acl->next = NULL; acl->key_options = NULL; + acl->tls_auth_options = NULL; acl->ip_address_spec = unmarshal_str(r, b); acl->key_name = unmarshal_str(r, b); + acl->tls_auth_name = unmarshal_str(r, b); return acl; } @@ -1298,6 +1330,14 @@ key_options_create(region_type* region) return key; } +struct tls_auth_options* +tls_auth_options_create(region_type* region) +{ + struct tls_auth_options* tls_auth_options; + tls_auth_options = (struct tls_auth_options*)region_alloc_zero(region, sizeof(struct tls_auth_options)); + return tls_auth_options; +} + void key_options_insert(struct nsd_options* opt, struct key_options* key) { @@ -1312,6 +1352,20 @@ key_options_find(struct nsd_options* opt, const char* name) return (struct key_options*)rbtree_search(opt->keys, name); } +void +tls_auth_options_insert(struct nsd_options* opt, struct tls_auth_options* auth) +{ + if(!auth->name) return; + auth->node.key = auth->name; + (void)rbtree_insert(opt->tls_auths, &auth->node); +} + +struct tls_auth_options* +tls_auth_options_find(struct nsd_options* opt, const char* name) +{ + return (struct tls_auth_options*)rbtree_search(opt->tls_auths, name); +} + /** remove tsig_key contents */ void key_options_desetup(region_type* region, struct key_options* key) @@ -1797,7 +1851,7 @@ config_make_zonefile(struct zone_options* zone, struct nsd* nsd) static char f[1024]; /* if not a template, return as-is */ if(!strchr(zone->pattern->zonefile, '%')) { - if (nsd->chrootdir && nsd->chrootdir[0] && + if (nsd->chrootdir && nsd->chrootdir[0] && zone->pattern->zonefile && zone->pattern->zonefile[0] == '/' && strncmp(zone->pattern->zonefile, nsd->chrootdir, @@ -1925,6 +1979,8 @@ parse_acl_info(region_type* region, char* ip, const char* key) acl->ixfr_disabled = 0; acl->bad_xfr_count = 0; acl->key_options = 0; + acl->tls_auth_options = 0; + acl->tls_auth_name = 0; acl->is_ipv6 = 0; acl->port = 0; memset(&acl->addr, 0, sizeof(union acl_addr_storage)); diff --git a/usr.sbin/nsd/options.h b/usr.sbin/nsd/options.h index 2bda9aa341e..bb66acb03d5 100644 --- a/usr.sbin/nsd/options.h +++ b/usr.sbin/nsd/options.h @@ -28,6 +28,7 @@ typedef struct cpu_option cpu_option_type; typedef struct cpu_map_option cpu_map_option_type; typedef struct acl_options acl_options_type; typedef struct key_options key_options_type; +typedef struct tls_auth_options tls_auth_options_type; typedef struct config_parser_state config_parser_state_type; /* @@ -59,6 +60,9 @@ struct nsd_options { /* rbtree of keys defined, by name */ rbtree_type* keys; + /* rbtree of tls_auth defined, by name */ + rbtree_type* tls_auths; + /* list of ip addresses to bind to (or NULL for all) */ struct ip_address_option* ip_addresses; @@ -117,6 +121,8 @@ struct nsd_options { char* tls_service_pem; /* TLS dedicated port */ const char* tls_port; + /* TLS certificate bundle */ + const char* tls_cert_bundle; /** remote control section. enable toggle. */ int control_enable; @@ -163,6 +169,13 @@ struct nsd_options { /** true to log dnstap AUTH_RESPONSE message events */ int dnstap_log_auth_response_messages; + /** do answer with server cookie when request contained cookie option */ + int answer_cookie; + /** cookie secret */ + char *cookie_secret; + /** path to cookie secret store */ + char const* cookie_secret_file; + region_type* region; }; @@ -303,6 +316,10 @@ struct acl_options { uint8_t blocked; const char* key_name; struct key_options* key_options; + + /* tls_auth for XoT */ + const char* tls_auth_name; + struct tls_auth_options* tls_auth_options; } ATTR_PACKED; /* @@ -316,6 +333,15 @@ struct key_options { struct tsig_key* tsig_key; } ATTR_PACKED; +/* + * TLS Auth definition for XoT + */ +struct tls_auth_options { + rbnode_type node; /* key of tree is name */ + char* name; + char* auth_domain_name; +}; + /** zone list free space */ struct zonelist_free { struct zonelist_free* next; @@ -348,6 +374,7 @@ struct config_parser_state { struct pattern_options *pattern; struct zone_options *zone; struct key_options *key; + struct tls_auth_options *tls_auth; struct ip_address_option *ip; void (*err)(void*,const char*); void* err_arg; @@ -392,6 +419,10 @@ int key_options_equal(struct key_options* p, struct key_options* q); void key_options_add_modify(struct nsd_options* opt, struct key_options* key); void key_options_setup(region_type* region, struct key_options* key); void key_options_desetup(region_type* region, struct key_options* key); +/* TLS auth */ +struct tls_auth_options* tls_auth_options_create(region_type* region); +void tls_auth_options_insert(struct nsd_options* opt, struct tls_auth_options* auth); +struct tls_auth_options* tls_auth_options_find(struct nsd_options* opt, const char* name); /* read in zone list file. Returns false on failure */ int parse_zone_list_file(struct nsd_options* opt); /* create zone entry and add to the zonelist file */ diff --git a/usr.sbin/nsd/query.c b/usr.sbin/nsd/query.c index 7092968a19c..44b6690ad67 100644 --- a/usr.sbin/nsd/query.c +++ b/usr.sbin/nsd/query.c @@ -1495,7 +1495,7 @@ query_prepare_response(query_type *q) * */ query_state_type -query_process(query_type *q, nsd_type *nsd) +query_process(query_type *q, nsd_type *nsd, uint32_t *now_p) { /* The query... */ nsd_rc_type rc; @@ -1555,7 +1555,7 @@ query_process(query_type *q, nsd_type *nsd) if(process_edns(nsd, q) == NSD_RC_OK) { int opcode = OPCODE(q->packet); (void)query_error(q, NSD_RC_FORMAT); - query_add_optional(q, nsd); + query_add_optional(q, nsd, now_p); FLAGS_SET(q->packet, FLAGS(q->packet) & 0x0100U); /* Preserve the RD flag. Clear the rest. */ OPCODE_SET(q->packet, opcode); @@ -1652,6 +1652,9 @@ query_process(query_type *q, nsd_type *nsd) return QUERY_PROCESSED; } + if (q->edns.cookie_status == COOKIE_UNVERIFIED) + cookie_verify(q, nsd, now_p); + query_prepare_response(q); if (q->qclass != CLASS_IN && q->qclass != CLASS_ANY) { @@ -1679,7 +1682,7 @@ query_process(query_type *q, nsd_type *nsd) } void -query_add_optional(query_type *q, nsd_type *nsd) +query_add_optional(query_type *q, nsd_type *nsd, uint32_t *now_p) { struct edns_data *edns = &nsd->edns_ipv4; #if defined(INET6) @@ -1719,6 +1722,13 @@ query_add_optional(query_type *q, nsd_type *nsd) /* nsid payload */ buffer_write(q->packet, nsd->nsid, nsd->nsid_len); } + if(q->edns.cookie_status != COOKIE_NOT_PRESENT) { + /* cookie opt header */ + buffer_write(q->packet, edns->cookie, OPT_HDR); + /* cookie payload */ + cookie_create(q, nsd, now_p); + buffer_write(q->packet, q->edns.cookie, 24); + } /* Append Extended DNS Error (RFC8914) option if needed */ if (q->edns.ede >= 0) { /* < 0 means no EDE */ /* OPTION-CODE */ diff --git a/usr.sbin/nsd/query.h b/usr.sbin/nsd/query.h index 2497f6f5fa9..af7ee7323bb 100644 --- a/usr.sbin/nsd/query.h +++ b/usr.sbin/nsd/query.h @@ -184,7 +184,7 @@ void query_reset(query_type *query, size_t maxlen, int is_tcp); /* * Process a query and write the response in the query I/O buffer. */ -query_state_type query_process(query_type *q, nsd_type *nsd); +query_state_type query_process(query_type *q, nsd_type *nsd, uint32_t *now_p); /* * Prepare the query structure for writing the response. The packet @@ -197,7 +197,7 @@ void query_prepare_response(query_type *q); /* * Add EDNS0 information to the response if required. */ -void query_add_optional(query_type *q, nsd_type *nsd); +void query_add_optional(query_type *q, nsd_type *nsd, uint32_t *now_p); /* * Write an error response into the query structure with the indicated diff --git a/usr.sbin/nsd/rdata.c b/usr.sbin/nsd/rdata.c index 68d03ce82e2..7628fdf0b1a 100644 --- a/usr.sbin/nsd/rdata.c +++ b/usr.sbin/nsd/rdata.c @@ -66,6 +66,11 @@ lookup_table_type dns_algorithms[] = { { 0, NULL } }; +const char *svcparamkey_strs[] = { + "mandatory", "alpn", "no-default-alpn", "port", + "ipv4hint", "ech", "ipv6hint" + }; + typedef int (*rdata_to_string_type)(buffer_type *output, rdata_atom_type rdata, rr_type *rr); @@ -642,6 +647,223 @@ rdata_loc_to_string(buffer_type *ATTR_UNUSED(output), return 0; } +static void +buffer_print_svcparamkey(buffer_type *output, uint16_t svcparamkey) +{ + if (svcparamkey < SVCPARAMKEY_COUNT) + buffer_printf(output, "%s", svcparamkey_strs[svcparamkey]); + else + buffer_printf(output, "key%d", (int)svcparamkey); +} + +static int +rdata_svcparam_port_to_string(buffer_type *output, uint16_t val_len, + uint16_t *data) +{ + if (val_len != 2) + return 0; /* wireformat error, a short is 2 bytes */ + buffer_printf(output, "=%d", (int)ntohs(data[0])); + return 1; +} + +static int +rdata_svcparam_ipv4hint_to_string(buffer_type *output, uint16_t val_len, + uint16_t *data) +{ + char ip_str[INET_ADDRSTRLEN + 1]; + + assert(val_len > 0); /* Guaranteed by rdata_svcparam_to_string */ + + if ((val_len % IP4ADDRLEN) == 0) { + if (inet_ntop(AF_INET, data, ip_str, sizeof(ip_str)) == NULL) + return 0; /* wireformat error, incorrect size or inet family */ + + buffer_printf(output, "=%s", ip_str); + data += IP4ADDRLEN / sizeof(uint16_t); + + while ((val_len -= IP4ADDRLEN) > 0) { + if (inet_ntop(AF_INET, data, ip_str, sizeof(ip_str)) == NULL) + return 0; /* wireformat error, incorrect size or inet family */ + + buffer_printf(output, ",%s", ip_str); + data += IP4ADDRLEN / sizeof(uint16_t); + } + return 1; + } else + return 0; +} + +static int +rdata_svcparam_ipv6hint_to_string(buffer_type *output, uint16_t val_len, + uint16_t *data) +{ + char ip_str[INET6_ADDRSTRLEN + 1]; + + assert(val_len > 0); /* Guaranteed by rdata_svcparam_to_string */ + + if ((val_len % IP6ADDRLEN) == 0) { + if (inet_ntop(AF_INET6, data, ip_str, sizeof(ip_str)) == NULL) + return 0; /* wireformat error, incorrect size or inet family */ + + buffer_printf(output, "=%s", ip_str); + data += IP6ADDRLEN / sizeof(uint16_t); + + while ((val_len -= IP6ADDRLEN) > 0) { + if (inet_ntop(AF_INET6, data, ip_str, sizeof(ip_str)) == NULL) + return 0; /* wireformat error, incorrect size or inet family */ + + buffer_printf(output, ",%s", ip_str); + data += IP6ADDRLEN / sizeof(uint16_t); + } + return 1; + } else + return 0; +} + +static int +rdata_svcparam_mandatory_to_string(buffer_type *output, uint16_t val_len, + uint16_t *data) +{ + assert(val_len > 0); /* Guaranteed by rdata_svcparam_to_string */ + + if (val_len % sizeof(uint16_t)) + return 0; /* wireformat error, val_len must be multiple of shorts */ + buffer_write_u8(output, '='); + buffer_print_svcparamkey(output, ntohs(*data)); + data += 1; + + while ((val_len -= sizeof(uint16_t))) { + buffer_write_u8(output, ','); + buffer_print_svcparamkey(output, ntohs(*data)); + data += 1; + } + + return 1; +} + +static int +rdata_svcparam_ech_to_string(buffer_type *output, uint16_t val_len, + uint16_t *data) +{ + int length; + + assert(val_len > 0); /* Guaranteed by rdata_svcparam_to_string */ + + buffer_write_u8(output, '='); + + buffer_reserve(output, val_len * 2 + 1); + length = __b64_ntop((uint8_t*) data, val_len, + (char *) buffer_current(output), val_len * 2); + if (length > 0) { + buffer_skip(output, length); + } + + return length != -1; +} + +static int +rdata_svcparam_alpn_to_string(buffer_type *output, uint16_t val_len, + uint16_t *data) +{ + uint8_t *dp = (void *)data; + + assert(val_len > 0); /* Guaranteed by rdata_svcparam_to_string */ + + buffer_write_u8(output, '='); + buffer_write_u8(output, '"'); + while (val_len) { + uint8_t i, str_len = *dp++; + + if (str_len > --val_len) + return 0; + + for (i = 0; i < str_len; i++) { + if (dp[i] == '"' || dp[i] == '\\') + buffer_printf(output, "\\\\\\%c", dp[i]); + + else if (dp[i] == ',') + buffer_printf(output, "\\\\%c", dp[i]); + + else if (!isprint(dp[i])) + buffer_printf(output, "\\%03u", (unsigned) dp[i]); + + else + buffer_write_u8(output, dp[i]); + } + dp += str_len; + if ((val_len -= str_len)) + buffer_write_u8(output, ','); + } + buffer_write_u8(output, '"'); + return 1; +} + +static int +rdata_svcparam_to_string(buffer_type *output, rdata_atom_type rdata, + rr_type* ATTR_UNUSED(rr)) +{ + uint16_t size = rdata_atom_size(rdata); + uint16_t* data = (uint16_t *)rdata_atom_data(rdata); + uint16_t svcparamkey, val_len; + uint8_t* dp; + size_t i; + + if (size < 4) + return 0; + svcparamkey = ntohs(data[0]); + + buffer_print_svcparamkey(output, svcparamkey); + val_len = ntohs(data[1]); + if (size != val_len + 4) + return 0; /* wireformat error */ + if (!val_len) { + /* Some SvcParams MUST have values */ + switch (svcparamkey) { + case SVCB_KEY_ALPN: + case SVCB_KEY_PORT: + case SVCB_KEY_IPV4HINT: + case SVCB_KEY_IPV6HINT: + case SVCB_KEY_MANDATORY: + return 0; + default: + return 1; + } + } + switch (svcparamkey) { + case SVCB_KEY_PORT: + return rdata_svcparam_port_to_string(output, val_len, data+2); + case SVCB_KEY_IPV4HINT: + return rdata_svcparam_ipv4hint_to_string(output, val_len, data+2); + case SVCB_KEY_IPV6HINT: + return rdata_svcparam_ipv6hint_to_string(output, val_len, data+2); + case SVCB_KEY_MANDATORY: + return rdata_svcparam_mandatory_to_string(output, val_len, data+2); + case SVCB_KEY_NO_DEFAULT_ALPN: + return 0; /* wireformat error, should not have a value */ + case SVCB_KEY_ALPN: + return rdata_svcparam_alpn_to_string(output, val_len, data+2); + case SVCB_KEY_ECH: + return rdata_svcparam_ech_to_string(output, val_len, data+2); + default: + buffer_write(output, "=\"", 2); + dp = (void*) (data + 2); + + for (i = 0; i < val_len; i++) { + if (dp[i] == '"' || dp[i] == '\\') + buffer_printf(output, "\\%c", dp[i]); + + else if (!isprint(dp[i])) + buffer_printf(output, "\\%03u", (unsigned) dp[i]); + + else + buffer_write_u8(output, dp[i]); + } + buffer_write_u8(output, '"'); + break; + } + return 1; +} + static int rdata_unknown_to_string(buffer_type *output, rdata_atom_type rdata, rr_type* ATTR_UNUSED(rr)) @@ -683,6 +905,7 @@ static rdata_to_string_type rdata_to_string_table[RDATA_ZF_UNKNOWN + 1] = { rdata_eui64_to_string, rdata_long_text_to_string, rdata_tag_to_string, + rdata_svcparam_to_string, rdata_unknown_to_string }; @@ -801,6 +1024,13 @@ rdata_wireformat_to_rdata_atoms(region_type *region, break; } break; + case RDATA_WF_SVCPARAM: + length = 4; + if (buffer_position(packet) + 4 <= end) { + length += + read_uint16(buffer_current(packet) + 2); + } + break; } if (is_domain) { @@ -933,4 +1163,3 @@ print_rdata(buffer_type *output, rrtype_descriptor_type *descriptor, return 1; } - diff --git a/usr.sbin/nsd/rdata.h b/usr.sbin/nsd/rdata.h index 0da8eab6ec0..457d940eca0 100644 --- a/usr.sbin/nsd/rdata.h +++ b/usr.sbin/nsd/rdata.h @@ -19,6 +19,7 @@ extern lookup_table_type dns_certificate_types[]; extern lookup_table_type dns_algorithms[]; +extern const char *svcparamkey_strs[]; int rdata_atom_to_string(buffer_type *output, rdata_zoneformat_type type, rdata_atom_type rdata, rr_type *rr); diff --git a/usr.sbin/nsd/remote.c b/usr.sbin/nsd/remote.c index 4cea3122113..81ec53f5eb8 100644 --- a/usr.sbin/nsd/remote.c +++ b/usr.sbin/nsd/remote.c @@ -2163,6 +2163,157 @@ do_del_tsig(RES* ssl, xfrd_state_type* xfrd, char* arg) { send_ok(ssl); } +/* returns `0` on failure */ +static int +cookie_secret_file_dump(RES* ssl, nsd_type const* nsd) { + char const* secret_file = nsd->options->cookie_secret_file; + char secret_hex[NSD_COOKIE_SECRET_SIZE * 2 + 1]; + FILE* f; + size_t i; + assert( secret_file != NULL ); + + /* open write only and truncate */ + if((f = fopen(secret_file, "w")) == NULL ) { + (void)ssl_printf(ssl, "unable to open cookie secret file %s: %s", + secret_file, strerror(errno)); + return 0; + } + for(i = 0; i < nsd->cookie_count; i++) { + struct cookie_secret const* cs = &nsd->cookie_secrets[i]; + ssize_t const len = hex_ntop(cs->cookie_secret, NSD_COOKIE_SECRET_SIZE, + secret_hex, sizeof(secret_hex)); + (void)len; /* silence unused variable warning with -DNDEBUG */ + assert( len == NSD_COOKIE_SECRET_SIZE * 2 ); + secret_hex[NSD_COOKIE_SECRET_SIZE * 2] = '\0'; + fprintf(f, "%s\n", secret_hex); + } + explicit_bzero(secret_hex, sizeof(secret_hex)); + fclose(f); + return 1; +} + +static void +do_activate_cookie_secret(RES* ssl, xfrd_state_type* xrfd, char* arg) { + nsd_type* nsd = xrfd->nsd; + (void)arg; + + if(nsd->cookie_count <= 1 ) { + (void)ssl_printf(ssl, "error: no staging cookie secret to activate\n"); + return; + } + if(!nsd->options->cookie_secret_file || !nsd->options->cookie_secret_file[0]) { + (void)ssl_printf(ssl, "error: no cookie secret file configured\n"); + return; + } + if(!cookie_secret_file_dump(ssl, nsd)) { + (void)ssl_printf(ssl, "error: writing to cookie secret file: \"%s\"\n", + nsd->options->cookie_secret_file); + return; + } + activate_cookie_secret(nsd); + (void)cookie_secret_file_dump(ssl, nsd); + task_new_activate_cookie_secret(xfrd->nsd->task[xfrd->nsd->mytask], + xfrd->last_task); + xfrd_set_reload_now(xfrd); + send_ok(ssl); +} + +static void +do_drop_cookie_secret(RES* ssl, xfrd_state_type* xrfd, char* arg) { + nsd_type* nsd = xrfd->nsd; + (void)arg; + + if(nsd->cookie_count <= 1 ) { + (void)ssl_printf(ssl, "error: can not drop the currently active cookie secret\n"); + return; + } + if(!nsd->options->cookie_secret_file || !nsd->options->cookie_secret_file[0]) { + (void)ssl_printf(ssl, "error: no cookie secret file configured\n"); + return; + } + if(!cookie_secret_file_dump(ssl, nsd)) { + (void)ssl_printf(ssl, "error: writing to cookie secret file: \"%s\"\n", + nsd->options->cookie_secret_file); + return; + } + drop_cookie_secret(nsd); + (void)cookie_secret_file_dump(ssl, nsd); + task_new_drop_cookie_secret(xfrd->nsd->task[xfrd->nsd->mytask], + xfrd->last_task); + xfrd_set_reload_now(xfrd); + send_ok(ssl); +} + +static void +do_add_cookie_secret(RES* ssl, xfrd_state_type* xrfd, char* arg) { + nsd_type* nsd = xrfd->nsd; + uint8_t secret[NSD_COOKIE_SECRET_SIZE]; + + if(*arg == '\0') { + (void)ssl_printf(ssl, "error: missing argument (cookie_secret)\n"); + return; + } + if(strlen(arg) != 32) { + explicit_bzero(arg, strlen(arg)); + (void)ssl_printf(ssl, "invalid cookie secret: invalid argument length\n"); + (void)ssl_printf(ssl, "please provide a 128bit hex encoded secret\n"); + return; + } + if(hex_pton(arg, secret, NSD_COOKIE_SECRET_SIZE) != NSD_COOKIE_SECRET_SIZE ) { + explicit_bzero(secret, NSD_COOKIE_SECRET_SIZE); + explicit_bzero(arg, strlen(arg)); + (void)ssl_printf(ssl, "invalid cookie secret: parse error\n"); + (void)ssl_printf(ssl, "please provide a 128bit hex encoded secret\n"); + return; + } + if(!nsd->options->cookie_secret_file || !nsd->options->cookie_secret_file[0]) { + explicit_bzero(secret, NSD_COOKIE_SECRET_SIZE); + explicit_bzero(arg, strlen(arg)); + (void)ssl_printf(ssl, "error: no cookie secret file configured\n"); + return; + } + if(!cookie_secret_file_dump(ssl, nsd)) { + explicit_bzero(secret, NSD_COOKIE_SECRET_SIZE); + explicit_bzero(arg, strlen(arg)); + (void)ssl_printf(ssl, "error: writing to cookie secret file: \"%s\"\n", + nsd->options->cookie_secret_file); + return; + } + add_cookie_secret(nsd, secret); + explicit_bzero(secret, NSD_COOKIE_SECRET_SIZE); + (void)cookie_secret_file_dump(ssl, nsd); + task_new_add_cookie_secret(xfrd->nsd->task[xfrd->nsd->mytask], + xfrd->last_task, arg); + explicit_bzero(arg, strlen(arg)); + xfrd_set_reload_now(xfrd); + send_ok(ssl); +} + +static void +do_print_cookie_secrets(RES* ssl, xfrd_state_type* xrfd, char* arg) { + nsd_type* nsd = xrfd->nsd; + char secret_hex[NSD_COOKIE_SECRET_SIZE * 2 + 1]; + int i; + (void)arg; + + /* (void)ssl_printf(ssl, "cookie_secret_count=%zu\n", nsd->cookie_count); */ + for(i = 0; (size_t)i < nsd->cookie_count; i++) { + struct cookie_secret const* cs = &nsd->cookie_secrets[i]; + ssize_t const len = hex_ntop(cs->cookie_secret, NSD_COOKIE_SECRET_SIZE, + secret_hex, sizeof(secret_hex)); + (void)len; /* silence unused variable warning with -DNDEBUG */ + assert( len == NSD_COOKIE_SECRET_SIZE * 2 ); + secret_hex[NSD_COOKIE_SECRET_SIZE * 2] = '\0'; + if (i == 0) + (void)ssl_printf(ssl, "active : %s\n", secret_hex); + else if (nsd->cookie_count == 2) + (void)ssl_printf(ssl, "staging: %s\n", secret_hex); + else + (void)ssl_printf(ssl, "staging[%d]: %s\n", i, secret_hex); + } + explicit_bzero(secret_hex, sizeof(secret_hex)); +} + /** check for name with end-of-string, space or tab after it */ static int cmdcmp(char* p, const char* cmd, size_t len) @@ -2226,6 +2377,14 @@ execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd, struct rc_state* rs) do_assoc_tsig(ssl, rc->xfrd, skipwhite(p+10)); } else if(cmdcmp(p, "del_tsig", 8)) { do_del_tsig(ssl, rc->xfrd, skipwhite(p+8)); + } else if(cmdcmp(p, "add_cookie_secret", 17)) { + do_add_cookie_secret(ssl, rc->xfrd, skipwhite(p+17)); + } else if(cmdcmp(p, "drop_cookie_secret", 18)) { + do_drop_cookie_secret(ssl, rc->xfrd, skipwhite(p+18)); + } else if(cmdcmp(p, "print_cookie_secrets", 20)) { + do_print_cookie_secrets(ssl, rc->xfrd, skipwhite(p+20)); + } else if(cmdcmp(p, "activate_cookie_secret", 22)) { + do_activate_cookie_secret(ssl, rc->xfrd, skipwhite(p+22)); } else { (void)ssl_printf(ssl, "error unknown command '%s'\n", p); } diff --git a/usr.sbin/nsd/server.c b/usr.sbin/nsd/server.c index 52e6ecaadf4..c01f022a1c1 100644 --- a/usr.sbin/nsd/server.c +++ b/usr.sbin/nsd/server.c @@ -95,16 +95,22 @@ static void log_addr(const char* descr, #ifdef INET6 - struct sockaddr_storage* addr, + struct sockaddr_storage* addr #else - struct sockaddr_in* addr, + struct sockaddr_in* addr #endif - short family) + ) { char str_buf[64]; if(verbosity < 6) return; - if(family == AF_INET) { + if( +#ifdef INET6 + addr->ss_family == AF_INET +#else + addr->sin_family == AF_INET +#endif + ) { struct sockaddr_in* s = (struct sockaddr_in*)addr; inet_ntop(AF_INET, &s->sin_addr.s_addr, str_buf, sizeof(str_buf)); VERBOSITY(6, (LOG_INFO, "%s: address is: %s, port is: %d", descr, str_buf, ntohs(s->sin_port))); @@ -863,10 +869,10 @@ set_nonblock(struct nsd_socket *sock) return 1; } +#ifdef INET6 static int set_ipv6_v6only(struct nsd_socket *sock) { -#ifdef INET6 #ifdef IPV6_V6ONLY int on = 1; const char *socktype = @@ -881,16 +887,19 @@ set_ipv6_v6only(struct nsd_socket *sock) log_msg(LOG_ERR, "setsockopt(..., IPV6_V6ONLY, ...) failed for %s: %s", socktype, strerror(errno)); return -1; +#else + (void)sock; #endif /* IPV6_V6ONLY */ -#endif /* INET6 */ return 0; } +#endif /* INET6 */ +#ifdef INET6 static int set_ipv6_use_min_mtu(struct nsd_socket *sock) { -#if defined(INET6) && (defined(IPV6_USE_MIN_MTU) || defined(IPV6_MTU)) +#if defined(IPV6_USE_MIN_MTU) || defined(IPV6_MTU) #if defined(IPV6_USE_MIN_MTU) /* There is no fragmentation of IPv6 datagrams during forwarding in the * network. Therefore we do not send UDP datagrams larger than the @@ -923,6 +932,7 @@ set_ipv6_use_min_mtu(struct nsd_socket *sock) return 0; } +#endif /* INET6 */ static int set_ipv4_no_pmtu_disc(struct nsd_socket *sock) @@ -2291,6 +2301,18 @@ server_reload(struct nsd *nsd, region_type* server_region, netio_type* netio, /* listen for the signals of failed children again */ sigaction(SIGCHLD, &old_sigchld, NULL); +#ifdef USE_DNSTAP + if (nsd->dt_collector) { + int *swap_fd_send; + DEBUG(DEBUG_IPC,1, (LOG_INFO, "reload: swap dnstap collector pipes")); + /* Swap fd_send with fd_swap so old serve child and new serve + * childs will not write to the same pipe ends simultaneously */ + swap_fd_send = nsd->dt_collector_fd_send; + nsd->dt_collector_fd_send = nsd->dt_collector_fd_swap; + nsd->dt_collector_fd_swap = swap_fd_send; + + } +#endif /* Start new child processes */ if (server_start_children(nsd, server_region, netio, &nsd-> xfrd_listener->fd) != 0) { @@ -2483,6 +2505,43 @@ server_main(struct nsd *nsd) log_msg(LOG_ERR, "problems sending reloadpid to xfrd: %s", strerror(errno)); } +#ifdef USE_DNSTAP + } else if(nsd->dt_collector && child_pid == nsd->dt_collector->dt_pid) { + log_msg(LOG_WARNING, + "dnstap-collector %d terminated with status %d", + (int) child_pid, status); + if(nsd->dt_collector) { + dt_collector_close(nsd->dt_collector, nsd); + dt_collector_destroy(nsd->dt_collector, nsd); + nsd->dt_collector = NULL; + } + /* Only respawn a crashed (or exited) + * dnstap-collector when not reloading, + * to not induce a reload during a + * reload (which would seriously + * disrupt nsd procedures and lead to + * unpredictable results)! + * + * This will *leave* a dnstap-collector + * process terminated, but because + * signalling of the reload process to + * the main process to respawn in this + * situation will be cumbersome, and + * because this situation is so + * specific (and therefore hopefully + * extremely rare or non-existing at + * all), plus the fact that we are left + * with a perfectly function NSD + * (besides not logging dnstap + * messages), I consider it acceptable + * to leave this unresolved. + */ + if(reload_pid == -1 && nsd->options->dnstap_enable) { + nsd->dt_collector = dt_collector_create(nsd); + dt_collector_start(nsd->dt_collector, nsd); + nsd->mode = NSD_RELOAD_REQ; + } +#endif } else if(status != 0) { /* check for status, because we get * the old-servermain because reload @@ -2743,23 +2802,25 @@ server_main(struct nsd *nsd) } static query_state_type -server_process_query(struct nsd *nsd, struct query *query) +server_process_query(struct nsd *nsd, struct query *query, uint32_t *now_p) { - return query_process(query, nsd); + return query_process(query, nsd, now_p); } static query_state_type -server_process_query_udp(struct nsd *nsd, struct query *query) +server_process_query_udp(struct nsd *nsd, struct query *query, uint32_t *now_p) { #ifdef RATELIMIT - if(query_process(query, nsd) != QUERY_DISCARDED) { - if(rrl_process_query(query)) + if(query_process(query, nsd, now_p) != QUERY_DISCARDED) { + if(query->edns.cookie_status != COOKIE_VALID + && query->edns.cookie_status != COOKIE_VALID_REUSE + && rrl_process_query(query)) return rrl_slip(query); else return QUERY_PROCESSED; } return QUERY_DISCARDED; #else - return query_process(query, nsd); + return query_process(query, nsd, now_p); #endif } @@ -2870,8 +2931,8 @@ add_tcp_handler( data->tls_accept = 1; if(verbosity >= 2) { char buf[48]; - addrport2str((struct sockaddr_storage*)&sock->addr.ai_addr, buf, sizeof(buf)); - VERBOSITY(2, (LOG_NOTICE, "setup TCP for TLS service on interface %s", buf)); + addrport2str((void*)(struct sockaddr_storage*)&sock->addr.ai_addr, buf, sizeof(buf)); + VERBOSITY(4, (LOG_NOTICE, "setup TCP for TLS service on interface %s", buf)); } } else { data->tls_accept = 0; @@ -3113,7 +3174,13 @@ service_remaining_tcp(struct nsd* nsd) if(nsd->current_tcp_count == 0 || tcp_active_list == NULL) return; VERBOSITY(4, (LOG_INFO, "service remaining TCP connections")); - +#ifdef USE_DNSTAP + /* remove dnstap collector, we cannot write there because the new + * child process is using the file descriptor, or the child + * process after that. */ + dt_collector_destroy(nsd->dt_collector, nsd); + nsd->dt_collector = NULL; +#endif /* setup event base */ event_base = nsd_child_event_base(); if(!event_base) { @@ -3287,12 +3354,37 @@ nsd_sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, int flags) } #endif /* HAVE_SENDMMSG */ +static int +port_is_zero( +#ifdef INET6 + struct sockaddr_storage *addr +#else + struct sockaddr_in *addr +#endif + ) +{ +#ifdef INET6 + if(addr->ss_family == AF_INET6) { + return (((struct sockaddr_in6 *)addr)->sin6_port) == 0; + } else if(addr->ss_family == AF_INET) { + return (((struct sockaddr_in *)addr)->sin_port) == 0; + } + return 0; +#else + if(addr->sin_family == AF_INET) { + return addr->sin_port == 0; + } + return 0; +#endif +} + static void handle_udp(int fd, short event, void* arg) { struct udp_handler_data *data = (struct udp_handler_data *) arg; int received, sent, recvcount, i; struct query *q; + uint32_t now = 0; if (!(event & EV_READ)) { return; @@ -3345,14 +3437,14 @@ handle_udp(int fd, short event, void* arg) /* * sending UDP-query with server address (local) and client address to dnstap process */ - log_addr("query from client", &q->addr, data->socket->addr.ai_family); - log_addr("to server (local)", &data->socket->addr.ai_addr, data->socket->addr.ai_family); - dt_collector_submit_auth_query(data->nsd, &data->socket->addr.ai_addr, &q->addr, q->addrlen, + log_addr("query from client", &q->addr); + log_addr("to server (local)", (void*)&data->socket->addr.ai_addr); + dt_collector_submit_auth_query(data->nsd, (void*)&data->socket->addr.ai_addr, &q->addr, q->addrlen, q->tcp, q->packet); #endif /* USE_DNSTAP */ /* Process and answer the query... */ - if (server_process_query_udp(data->nsd, q) != QUERY_DISCARDED) { + if (server_process_query_udp(data->nsd, q, &now) != QUERY_DISCARDED) { if (RCODE(q->packet) == RCODE_OK && !AA(q->packet)) { STATUP(data->nsd, nona); ZTATUP(data->nsd, q->zone, nona); @@ -3367,7 +3459,7 @@ handle_udp(int fd, short event, void* arg) #endif /* Add EDNS0 and TSIG info if necessary. */ - query_add_optional(q, data->nsd); + query_add_optional(q, data->nsd, &now); buffer_flip(q->packet); iovecs[i].iov_len = buffer_remaining(q->packet); @@ -3384,9 +3476,9 @@ handle_udp(int fd, short event, void* arg) /* * sending UDP-response with server address (local) and client address to dnstap process */ - log_addr("from server (local)", &data->socket->addr.ai_addr, data->socket->addr.ai_family); - log_addr("response to client", &q->addr, data->socket->addr.ai_family); - dt_collector_submit_auth_response(data->nsd, &data->socket->addr.ai_addr, + log_addr("from server (local)", (void*)&data->socket->addr.ai_addr); + log_addr("response to client", &q->addr); + dt_collector_submit_auth_response(data->nsd, (void*)&data->socket->addr.ai_addr, &q->addr, q->addrlen, q->tcp, q->packet, q->zone); #endif /* USE_DNSTAP */ @@ -3445,6 +3537,19 @@ handle_udp(int fd, short event, void* arg) } errno = errstore; } + if(errno == EINVAL) { + /* skip the invalid argument entry, + * send the remaining packets in the list */ + if(!(port_is_zero((void*)&queries[i]->addr) && + verbosity < 3)) { + const char* es = strerror(errno); + char a[64]; + addrport2str((void*)&queries[i]->addr, a, sizeof(a)); + log_msg(LOG_ERR, "sendmmsg skip invalid argument [0]=%s count=%d failed: %s", a, (int)(recvcount-i), es); + } + i += 1; + continue; + } /* don't log transient network full errors, unless * on higher verbosity */ if(!(errno == ENOBUFS && verbosity < 1) && @@ -3454,7 +3559,7 @@ handle_udp(int fd, short event, void* arg) errno != EAGAIN) { const char* es = strerror(errno); char a[64]; - addrport2str(&queries[i]->addr, a, sizeof(a)); + addrport2str((void*)&queries[i]->addr, a, sizeof(a)); log_msg(LOG_ERR, "sendmmsg [0]=%s count=%d failed: %s", a, (int)(recvcount-i), es); } #ifdef BIND8_STATS @@ -3539,6 +3644,7 @@ handle_tcp_reading(int fd, short event, void* arg) ssize_t received; struct event_base* ev_base; struct timeval timeout; + uint32_t now = 0; if ((event & EV_TIMEOUT)) { /* Connection timed out. */ @@ -3691,12 +3797,12 @@ handle_tcp_reading(int fd, short event, void* arg) /* * and send TCP-query with found address (local) and client address to dnstap process */ - log_addr("query from client", &data->query->addr, data->query->addr.ss_family); - log_addr("to server (local)", &data->socket->addr.ai_addr, data->query->addr.ss_family); - dt_collector_submit_auth_query(data->nsd, &data->socket->addr.ai_addr, &data->query->addr, + log_addr("query from client", &data->query->addr); + log_addr("to server (local)", (void*)&data->socket->addr.ai_addr); + dt_collector_submit_auth_query(data->nsd, (void*)&data->socket->addr.ai_addr, &data->query->addr, data->query->addrlen, data->query->tcp, data->query->packet); #endif /* USE_DNSTAP */ - data->query_state = server_process_query(data->nsd, data->query); + data->query_state = server_process_query(data->nsd, data->query, &now); if (data->query_state == QUERY_DISCARDED) { /* Drop the packet and the entire connection... */ STATUP(data->nsd, dropped); @@ -3726,7 +3832,7 @@ handle_tcp_reading(int fd, short event, void* arg) #endif #endif /* USE_ZONE_STATS */ - query_add_optional(data->query, data->nsd); + query_add_optional(data->query, data->nsd, &now); /* Switch to the tcp write handler. */ buffer_flip(data->query->packet); @@ -3744,9 +3850,9 @@ handle_tcp_reading(int fd, short event, void* arg) /* * sending TCP-response with found (earlier) address (local) and client address to dnstap process */ - log_addr("from server (local)", &data->socket->addr.ai_addr, data->query->addr.ss_family); - log_addr("response to client", &data->query->addr, data->query->addr.ss_family); - dt_collector_submit_auth_response(data->nsd, &data->socket->addr.ai_addr, &data->query->addr, + log_addr("from server (local)", (void*)&data->socket->addr.ai_addr); + log_addr("response to client", &data->query->addr); + dt_collector_submit_auth_response(data->nsd, (void*)&data->socket->addr.ai_addr, &data->query->addr, data->query->addrlen, data->query->tcp, data->query->packet, data->query->zone); #endif /* USE_DNSTAP */ @@ -3776,6 +3882,7 @@ handle_tcp_writing(int fd, short event, void* arg) struct query *q = data->query; struct timeval timeout; struct event_base* ev_base; + uint32_t now = 0; if ((event & EV_TIMEOUT)) { /* Connection timed out. */ @@ -3879,7 +3986,7 @@ handle_tcp_writing(int fd, short event, void* arg) buffer_clear(q->packet); data->query_state = query_axfr(data->nsd, q); if (data->query_state != QUERY_PROCESSED) { - query_add_optional(data->query, data->nsd); + query_add_optional(data->query, data->nsd, &now); /* Reset data. */ buffer_flip(q->packet); @@ -4029,6 +4136,7 @@ handle_tls_reading(int fd, short event, void* arg) { struct tcp_handler_data *data = (struct tcp_handler_data *) arg; ssize_t received; + uint32_t now = 0; if ((event & EV_TIMEOUT)) { /* Connection timed out. */ @@ -4179,12 +4287,12 @@ handle_tls_reading(int fd, short event, void* arg) /* * and send TCP-query with found address (local) and client address to dnstap process */ - log_addr("query from client", &data->query->addr, data->query->addr.ss_family); - log_addr("to server (local)", &data->socket->addr.ai_addr, data->query->addr.ss_family); - dt_collector_submit_auth_query(data->nsd, &data->socket->addr.ai_addr, &data->query->addr, + log_addr("query from client", &data->query->addr); + log_addr("to server (local)", (void*)&data->socket->addr.ai_addr); + dt_collector_submit_auth_query(data->nsd, (void*)&data->socket->addr.ai_addr, &data->query->addr, data->query->addrlen, data->query->tcp, data->query->packet); #endif /* USE_DNSTAP */ - data->query_state = server_process_query(data->nsd, data->query); + data->query_state = server_process_query(data->nsd, data->query, &now); if (data->query_state == QUERY_DISCARDED) { /* Drop the packet and the entire connection... */ STATUP(data->nsd, dropped); @@ -4214,7 +4322,7 @@ handle_tls_reading(int fd, short event, void* arg) #endif #endif /* USE_ZONE_STATS */ - query_add_optional(data->query, data->nsd); + query_add_optional(data->query, data->nsd, &now); /* Switch to the tcp write handler. */ buffer_flip(data->query->packet); @@ -4232,9 +4340,9 @@ handle_tls_reading(int fd, short event, void* arg) /* * sending TCP-response with found (earlier) address (local) and client address to dnstap process */ - log_addr("from server (local)", &data->socket->addr.ai_addr, data->query->addr.ss_family); - log_addr("response to client", &data->query->addr, data->query->addr.ss_family); - dt_collector_submit_auth_response(data->nsd, &data->socket->addr.ai_addr, &data->query->addr, + log_addr("from server (local)", (void*)&data->socket->addr.ai_addr); + log_addr("response to client", &data->query->addr); + dt_collector_submit_auth_response(data->nsd, (void*)&data->socket->addr.ai_addr, &data->query->addr, data->query->addrlen, data->query->tcp, data->query->packet, data->query->zone); #endif /* USE_DNSTAP */ @@ -4257,6 +4365,7 @@ handle_tls_writing(int fd, short event, void* arg) * TCP length in front of the packet, like writev. */ static buffer_type* global_tls_temp_buffer = NULL; buffer_type* write_buffer; + uint32_t now = 0; if ((event & EV_TIMEOUT)) { /* Connection timed out. */ @@ -4341,7 +4450,7 @@ handle_tls_writing(int fd, short event, void* arg) buffer_clear(q->packet); data->query_state = query_axfr(data->nsd, q); if (data->query_state != QUERY_PROCESSED) { - query_add_optional(data->query, data->nsd); + query_add_optional(data->query, data->nsd, &now); /* Reset data. */ buffer_flip(q->packet); diff --git a/usr.sbin/nsd/util.c b/usr.sbin/nsd/util.c index eac94ccbd43..45954236309 100644 --- a/usr.sbin/nsd/util.c +++ b/usr.sbin/nsd/util.c @@ -36,6 +36,7 @@ #include "namedb.h" #include "rdata.h" #include "zonec.h" +#include "nsd.h" #ifdef USE_MMAP_ALLOC #include @@ -1234,3 +1235,59 @@ int set_cpu_affinity(cpuset_t *set) } #endif #endif /* HAVE_CPUSET_T */ + +void add_cookie_secret(struct nsd* nsd, uint8_t* secret) +{ + /* New cookie secret becomes the staging secret (position 1) + * unless there is no active cookie yet, then it becomes the active + * secret. If the NSD_COOKIE_HISTORY_SIZE > 2 then all staging cookies + * are moved one position down. + */ + if(nsd->cookie_count == 0) { + memcpy( nsd->cookie_secrets->cookie_secret + , secret, NSD_COOKIE_SECRET_SIZE); + nsd->cookie_count = 1; + explicit_bzero(secret, NSD_COOKIE_SECRET_SIZE); + return; + } +#if NSD_COOKIE_HISTORY_SIZE > 2 + memmove( &nsd->cookie_secrets[2], &nsd->cookie_secrets[1] + , sizeof(struct cookie_secret) * (NSD_COOKIE_HISTORY_SIZE - 2)); +#endif + memcpy( nsd->cookie_secrets[1].cookie_secret + , secret, NSD_COOKIE_SECRET_SIZE); + nsd->cookie_count = nsd->cookie_count < NSD_COOKIE_HISTORY_SIZE + ? nsd->cookie_count + 1 : NSD_COOKIE_HISTORY_SIZE; + explicit_bzero(secret, NSD_COOKIE_SECRET_SIZE); +} + +void activate_cookie_secret(struct nsd* nsd) +{ + uint8_t active_secret[NSD_COOKIE_SECRET_SIZE]; + /* The staging secret becomes the active secret. + * The active secret becomes a staging secret. + * If the NSD_COOKIE_HISTORY_SIZE > 2 then all staging secrets are moved + * one position up and the previously active secret becomes the last + * staging secret. + */ + if(nsd->cookie_count < 2) + return; + memcpy( active_secret, nsd->cookie_secrets[0].cookie_secret + , NSD_COOKIE_SECRET_SIZE); + memmove( &nsd->cookie_secrets[0], &nsd->cookie_secrets[1] + , sizeof(struct cookie_secret) * (NSD_COOKIE_HISTORY_SIZE - 1)); + memcpy( nsd->cookie_secrets[nsd->cookie_count - 1].cookie_secret + , active_secret, NSD_COOKIE_SECRET_SIZE); + explicit_bzero(active_secret, NSD_COOKIE_SECRET_SIZE); +} + +void drop_cookie_secret(struct nsd* nsd) +{ + /* Drops a staging cookie secret. If there are more than one, it will + * drop the last staging secret. */ + if(nsd->cookie_count < 2) + return; + explicit_bzero( nsd->cookie_secrets[nsd->cookie_count - 1].cookie_secret + , NSD_COOKIE_SECRET_SIZE); + nsd->cookie_count -= 1; +} diff --git a/usr.sbin/nsd/util.h b/usr.sbin/nsd/util.h index d0b942869a2..332d5d30d56 100644 --- a/usr.sbin/nsd/util.h +++ b/usr.sbin/nsd/util.h @@ -17,6 +17,7 @@ struct rr; struct buffer; struct region; +struct nsd; #ifdef HAVE_SYSLOG_H # include @@ -440,4 +441,14 @@ int number_of_cpus(void); int set_cpu_affinity(cpuset_t *set); #endif +/* Add a cookie secret. If there are no secrets yet, the secret will become + * the active secret. Otherwise it will become the staging secret. + * Active secrets are used to both verify and create new DNS Cookies. + * Staging secrets are only used to verify DNS Cookies. */ +void add_cookie_secret(struct nsd* nsd, uint8_t* secret); +/* Makes the staging cookie secret active and the active secret staging. */ +void activate_cookie_secret(struct nsd* nsd); +/* Drop a cookie secret. Drops the staging secret. An active secret will not + * be dropped. */ +void drop_cookie_secret(struct nsd* nsd); #endif /* _UTIL_H_ */ diff --git a/usr.sbin/nsd/xfrd-tcp.c b/usr.sbin/nsd/xfrd-tcp.c index 4e3a6a48c28..8668ed0981a 100644 --- a/usr.sbin/nsd/xfrd-tcp.c +++ b/usr.sbin/nsd/xfrd-tcp.c @@ -24,6 +24,107 @@ #include "xfrd.h" #include "xfrd-disk.h" #include "util.h" +#ifdef HAVE_TLS_1_3 +#include +#include +#endif + +#ifdef HAVE_TLS_1_3 +void log_crypto_err(const char* str); /* in server.c */ + +static SSL_CTX* +create_ssl_context() +{ + SSL_CTX *ctx; + ctx = SSL_CTX_new(TLS_client_method()); + if (!ctx) { + log_msg(LOG_ERR, "xfrd tls: Unable to create SSL ctxt"); + } + else if (SSL_CTX_set_default_verify_paths(ctx) != 1) { + SSL_CTX_free(ctx); + log_msg(LOG_ERR, "xfrd tls: Unable to set default SSL verify paths"); + return NULL; + } + /* Only trust 1.3 as per the specification */ + else if (!SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION)) { + SSL_CTX_free(ctx); + log_msg(LOG_ERR, "xfrd tls: Unable to set minimum TLS version 1.3"); + return NULL; + } + return ctx; +} + +static int +tls_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) +{ + int err = X509_STORE_CTX_get_error(ctx); + int depth = X509_STORE_CTX_get_error_depth(ctx); + + // report the specific cert error here - will need custom verify code if + // SPKI pins are supported + if (!preverify_ok) + log_msg(LOG_ERR, "xfrd tls: TLS verify failed - (%d) depth: %d error: %s", + err, + depth, + X509_verify_cert_error_string(err)); + return preverify_ok; +} + +static int +setup_ssl(struct xfrd_tcp_pipeline* tp, struct xfrd_tcp_set* tcp_set, + const char* auth_domain_name) +{ + if (!tcp_set->ssl_ctx) { + log_msg(LOG_ERR, "xfrd tls: No TLS CTX, cannot set up XFR-over-TLS"); + return 0; + } + DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: setting up TLS for tls_auth domain name %s", + auth_domain_name)); + tp->ssl = SSL_new((SSL_CTX*)tcp_set->ssl_ctx); + if(!tp->ssl) { + log_msg(LOG_ERR, "xfrd tls: Unable to create TLS object"); + return 0; + } + SSL_set_connect_state(tp->ssl); + (void)SSL_set_mode(tp->ssl, SSL_MODE_AUTO_RETRY); + if(!SSL_set_fd(tp->ssl, tp->tcp_w->fd)) { + log_msg(LOG_ERR, "xfrd tls: Unable to set TLS fd"); + SSL_free(tp->ssl); + tp->ssl = NULL; + return 0; + } + + SSL_set_verify(tp->ssl, SSL_VERIFY_PEER, tls_verify_callback); + if(!SSL_set1_host(tp->ssl, auth_domain_name)) { + log_msg(LOG_ERR, "xfrd tls: TLS setting of hostname %s failed", + auth_domain_name); + SSL_free(tp->ssl); + tp->ssl = NULL; + return 0; + } + return 1; +} + +static int +ssl_handshake(struct xfrd_tcp_pipeline* tp) +{ + int ret; + + ERR_clear_error(); + ret = SSL_do_handshake(tp->ssl); + if(ret == 1) { + DEBUG(DEBUG_XFRD, 1, (LOG_INFO, "xfrd: TLS handshake successful")); + tp->handshake_done = 1; + return 1; + } + tp->handshake_want = SSL_get_error(tp->ssl, ret); + if(tp->handshake_want == SSL_ERROR_WANT_READ + || tp->handshake_want == SSL_ERROR_WANT_WRITE) + return 1; + + return 0; +} +#endif /* sort tcppipe, first on IP address, for an IPaddresss, sort on num_unused */ static int @@ -34,21 +135,21 @@ xfrd_pipe_cmp(const void* a, const void* b) int r; if(x == y) return 0; - if(y->ip_len != x->ip_len) + if(y->key.ip_len != x->key.ip_len) /* subtraction works because nonnegative and small numbers */ - return (int)y->ip_len - (int)x->ip_len; - r = memcmp(&x->ip, &y->ip, x->ip_len); + return (int)y->key.ip_len - (int)x->key.ip_len; + r = memcmp(&x->key.ip, &y->key.ip, x->key.ip_len); if(r != 0) return r; /* sort that num_unused is sorted ascending, */ - if(x->num_unused != y->num_unused) { - return (x->num_unused < y->num_unused) ? -1 : 1; + if(x->key.num_unused != y->key.num_unused) { + return (x->key.num_unused < y->key.num_unused) ? -1 : 1; } /* different pipelines are different still, even with same numunused*/ return (uintptr_t)x < (uintptr_t)y ? -1 : 1; } -struct xfrd_tcp_set* xfrd_tcp_set_create(struct region* region) +struct xfrd_tcp_set* xfrd_tcp_set_create(struct region* region, const char *tls_cert_bundle) { int i; struct xfrd_tcp_set* tcp_set = region_alloc(region, @@ -57,6 +158,20 @@ struct xfrd_tcp_set* xfrd_tcp_set_create(struct region* region) tcp_set->tcp_count = 0; tcp_set->tcp_waiting_first = 0; tcp_set->tcp_waiting_last = 0; +#ifdef HAVE_TLS_1_3 + /* Set up SSL context */ + tcp_set->ssl_ctx = create_ssl_context(); + if (tcp_set->ssl_ctx == NULL) + log_msg(LOG_ERR, "xfrd: XFR-over-TLS not available"); + + else if (tls_cert_bundle && tls_cert_bundle[0] && SSL_CTX_load_verify_locations( + tcp_set->ssl_ctx, tls_cert_bundle, NULL) != 1) { + log_msg(LOG_ERR, "xfrd tls: Unable to set the certificate bundle file %s", + tls_cert_bundle); + } +#else + log_msg(LOG_INFO, "xfrd: No TLS 1.3 support - XFR-over-TLS not available"); +#endif for(i=0; itcp_state[i] = xfrd_tcp_pipeline_create(region); tcp_set->pipetree = rbtree_create(region, &xfrd_pipe_cmp); @@ -69,7 +184,7 @@ xfrd_tcp_pipeline_create(region_type* region) int i; struct xfrd_tcp_pipeline* tp = (struct xfrd_tcp_pipeline*) region_alloc_zero(region, sizeof(*tp)); - tp->num_unused = ID_PIPE_NUM; + tp->key.num_unused = ID_PIPE_NUM; assert(sizeof(tp->unused)/sizeof(tp->unused[0]) == ID_PIPE_NUM); for(i=0; iunused[i] = (uint16_t)i; @@ -142,7 +257,12 @@ xfrd_acl_sockaddr_to(acl_options_type* acl, struct sockaddr_storage *to) xfrd_acl_sockaddr_to(acl_options_type* acl, struct sockaddr_in *to) #endif /* INET6 */ { +#ifdef HAVE_TLS_1_3 + unsigned int port = acl->port?acl->port:(acl->tls_auth_options? + (unsigned)atoi(TLS_PORT):(unsigned)atoi(TCP_PORT)); +#else unsigned int port = acl->port?acl->port:(unsigned)atoi(TCP_PORT); +#endif #ifdef INET6 return xfrd_acl_sockaddr(acl, port, to); #else @@ -215,12 +335,7 @@ pipeline_find(struct xfrd_tcp_set* set, xfrd_zone_type* zone) /* smaller buf than a full pipeline with 64kb ID array, only need * the front part with the key info, this front part contains the * members that the compare function uses. */ - enum { keysize = sizeof(struct xfrd_tcp_pipeline) - - ID_PIPE_NUM*(sizeof(struct xfrd_zone*) + sizeof(uint16_t)) }; - /* void* type for alignment of the struct, - * divide the keysize by ptr-size and then add one to round up */ - void* buf[ (keysize / sizeof(void*)) + 1 ]; - struct xfrd_tcp_pipeline* key = (struct xfrd_tcp_pipeline*)buf; + struct xfrd_tcp_pipeline_key k, *key=&k; key->node.key = key; key->ip_len = xfrd_acl_sockaddr_to(zone->master, &key->ip); key->num_unused = ID_PIPE_NUM; @@ -233,12 +348,12 @@ pipeline_find(struct xfrd_tcp_set* set, xfrd_zone_type* zone) return NULL; r = (struct xfrd_tcp_pipeline*)sme->key; /* <= key pointed at, is the master correct ? */ - if(r->ip_len != key->ip_len) + if(r->key.ip_len != key->ip_len) return NULL; - if(memcmp(&r->ip, &key->ip, key->ip_len) != 0) + if(memcmp(&r->key.ip, &key->ip, key->ip_len) != 0) return NULL; /* correct master, is there a slot free for this transfer? */ - if(r->num_unused == 0) + if(r->key.num_unused == 0) return NULL; return r; } @@ -286,14 +401,14 @@ tcp_pipe_sendlist_popfirst(struct xfrd_tcp_pipeline* tp, xfrd_zone_type* zone) static void tcp_pipe_id_remove(struct xfrd_tcp_pipeline* tp, xfrd_zone_type* zone) { - assert(tp->num_unused < ID_PIPE_NUM && tp->num_unused >= 0); + assert(tp->key.num_unused < ID_PIPE_NUM && tp->key.num_unused >= 0); assert(tp->id[zone->query_id] == zone); tp->id[zone->query_id] = NULL; - tp->unused[tp->num_unused] = zone->query_id; + tp->unused[tp->key.num_unused] = zone->query_id; /* must remove and re-add for sort order in tree */ - (void)rbtree_delete(xfrd->tcp_set->pipetree, &tp->node); - tp->num_unused++; - (void)rbtree_insert(xfrd->tcp_set->pipetree, &tp->node); + (void)rbtree_delete(xfrd->tcp_set->pipetree, &tp->key.node); + tp->key.num_unused++; + (void)rbtree_insert(xfrd->tcp_set->pipetree, &tp->key.node); } /* stop the tcp pipe (and all its zones need to retry) */ @@ -301,8 +416,8 @@ static void xfrd_tcp_pipe_stop(struct xfrd_tcp_pipeline* tp) { int i, conn = -1; - assert(tp->num_unused < ID_PIPE_NUM); /* at least one 'in-use' */ - assert(ID_PIPE_NUM - tp->num_unused > tp->num_skip); /* at least one 'nonskip' */ + assert(tp->key.num_unused < ID_PIPE_NUM); /* at least one 'in-use' */ + assert(ID_PIPE_NUM - tp->key.num_unused > tp->key.num_skip); /* at least one 'nonskip' */ /* need to retry for all the zones connected to it */ /* these could use different lists and go to a different nextmaster*/ for(i=0; ihandler); memset(&tp->handler, 0, sizeof(tp->handler)); event_set(&tp->handler, fd, EV_PERSIST|EV_TIMEOUT|EV_READ| - (tp->tcp_send_first?EV_WRITE:0), xfrd_handle_tcp_pipe, tp); +#ifdef HAVE_TLS_1_3 + ( tp->ssl + ? ( tp->handshake_done ? ( tp->tcp_send_first ? EV_WRITE : 0 ) + : tp->handshake_want == SSL_ERROR_WANT_WRITE ? EV_WRITE : 0 ) + : tp->tcp_send_first ? EV_WRITE : 0 ), +#else + ( tp->tcp_send_first ? EV_WRITE : 0 ), +#endif + xfrd_handle_tcp_pipe, tp); if(event_base_set(xfrd->event_base, &tp->handler) != 0) log_msg(LOG_ERR, "xfrd tcp: event_base_set failed"); if(event_add(&tp->handler, &tv) != 0) @@ -372,16 +495,16 @@ pipeline_setup_new_zone(struct xfrd_tcp_set* set, struct xfrd_tcp_pipeline* tp, { /* assign the ID */ int idx; - assert(tp->num_unused > 0); + assert(tp->key.num_unused > 0); /* we pick a random ID, even though it is TCP anyway */ - idx = random_generate(tp->num_unused); + idx = random_generate(tp->key.num_unused); zone->query_id = tp->unused[idx]; - tp->unused[idx] = tp->unused[tp->num_unused-1]; + tp->unused[idx] = tp->unused[tp->key.num_unused-1]; tp->id[zone->query_id] = zone; /* decrement unused counter, and fixup tree */ - (void)rbtree_delete(set->pipetree, &tp->node); - tp->num_unused--; - (void)rbtree_insert(set->pipetree, &tp->node); + (void)rbtree_delete(set->pipetree, &tp->key.node); + tp->key.num_unused--; + (void)rbtree_insert(set->pipetree, &tp->key.node); /* add to sendlist, at end */ zone->tcp_send_next = NULL; @@ -437,9 +560,9 @@ xfrd_tcp_obtain(struct xfrd_tcp_set* set, xfrd_zone_type* zone) return; } /* ip and ip_len set by tcp_open */ - tp->node.key = tp; - tp->num_unused = ID_PIPE_NUM; - tp->num_skip = 0; + tp->key.node.key = tp; + tp->key.num_unused = ID_PIPE_NUM; + tp->key.num_skip = 0; tp->tcp_send_first = NULL; tp->tcp_send_last = NULL; memset(tp->id, 0, sizeof(tp->id)); @@ -448,7 +571,7 @@ xfrd_tcp_obtain(struct xfrd_tcp_set* set, xfrd_zone_type* zone) } /* insert into tree */ - (void)rbtree_insert(set->pipetree, &tp->node); + (void)rbtree_insert(set->pipetree, &tp->key.node); xfrd_deactivate_zone(zone); xfrd_unset_timer(zone); pipeline_setup_new_zone(set, tp, zone); @@ -552,7 +675,7 @@ xfrd_tcp_open(struct xfrd_tcp_set* set, struct xfrd_tcp_pipeline* tp, #endif } - tp->ip_len = xfrd_acl_sockaddr_to(zone->master, &tp->ip); + tp->key.ip_len = xfrd_acl_sockaddr_to(zone->master, &tp->key.ip); /* bind it */ if (!xfrd_bind_local_interface(fd, zone->zone_options->pattern-> @@ -562,7 +685,7 @@ xfrd_tcp_open(struct xfrd_tcp_set* set, struct xfrd_tcp_pipeline* tp, return 0; } - conn = connect(fd, (struct sockaddr*)&tp->ip, tp->ip_len); + conn = connect(fd, (struct sockaddr*)&tp->key.ip, tp->key.ip_len); if (conn == -1 && errno != EINPROGRESS) { log_msg(LOG_ERR, "xfrd: connect %s failed: %s", zone->master->ip_address_spec, strerror(errno)); @@ -573,12 +696,66 @@ xfrd_tcp_open(struct xfrd_tcp_set* set, struct xfrd_tcp_pipeline* tp, tp->tcp_r->fd = fd; tp->tcp_w->fd = fd; + /* Check if an tls_auth name is configured which means we should try to + establish an SSL connection */ + if (zone->master->tls_auth_options && + zone->master->tls_auth_options->auth_domain_name) { +#ifdef HAVE_TLS_1_3 + if (!setup_ssl(tp, set, zone->master->tls_auth_options->auth_domain_name)) { + log_msg(LOG_ERR, "xfrd: Cannot setup TLS on pipeline for %s to %s", + zone->apex_str, zone->master->ip_address_spec); + close(fd); + xfrd_set_refresh_now(zone); + return 0; + } + tp->handshake_done = 0; + if(!ssl_handshake(tp)) { + if(tp->handshake_want == SSL_ERROR_SYSCALL) { + log_msg(LOG_ERR, "xfrd: TLS handshake failed " + "for %s to %s: %s", zone->apex_str, + zone->master->ip_address_spec, + strerror(errno)); + + } else if(tp->handshake_want == SSL_ERROR_SSL) { + char errmsg[1024]; + snprintf(errmsg, sizeof(errmsg), "xfrd: " + "TLS handshake failed for %s to %s", + zone->apex_str, + zone->master->ip_address_spec); + log_crypto_err(errmsg); + } else { + log_msg(LOG_ERR, "xfrd: TLS handshake failed " + "for %s to %s with %d", zone->apex_str, + zone->master->ip_address_spec, + tp->handshake_want); + } + close(fd); + xfrd_set_refresh_now(zone); + return 0; + } +#else + log_msg(LOG_ERR, "xfrd: TLS 1.3 is not available, XFR-over-TLS is " + "not supported for %s to %s", + zone->apex_str, zone->master->ip_address_spec); + close(fd); + xfrd_set_refresh_now(zone); + return 0; +#endif + } + /* set the tcp pipe event */ if(tp->handler_added) event_del(&tp->handler); memset(&tp->handler, 0, sizeof(tp->handler)); - event_set(&tp->handler, fd, EV_PERSIST|EV_TIMEOUT|EV_READ|EV_WRITE, - xfrd_handle_tcp_pipe, tp); + event_set(&tp->handler, fd, EV_PERSIST|EV_TIMEOUT|EV_READ| +#ifdef HAVE_TLS_1_3 + ( !tp->ssl + || tp->handshake_done + || tp->handshake_want == SSL_ERROR_WANT_WRITE ? EV_WRITE : 0), +#else + EV_WRITE, +#endif + xfrd_handle_tcp_pipe, tp); if(event_base_set(xfrd->event_base, &tp->handler) != 0) log_msg(LOG_ERR, "xfrd tcp: event_base_set failed"); tv.tv_sec = set->tcp_timeout; @@ -616,7 +793,7 @@ xfrd_tcp_setup_write_packet(struct xfrd_tcp_pipeline* tp, xfrd_zone_type* zone) xfrd_setup_packet(tcp->packet, TYPE_IXFR, CLASS_IN, zone->apex, zone->query_id); zone->query_type = TYPE_IXFR; - NSCOUNT_SET(tcp->packet, 1); + NSCOUNT_SET(tcp->packet, 1); xfrd_write_soa_buffer(tcp->packet, zone->apex, &zone->soa_disk); } /* old transfer needs to be removed still? */ @@ -641,6 +818,79 @@ tcp_conn_ready_for_reading(struct xfrd_tcp* tcp) buffer_clear(tcp->packet); } +#ifdef HAVE_TLS_1_3 +static int +conn_write_ssl(struct xfrd_tcp* tcp, SSL* ssl) +{ + int request_length; + ssize_t sent; + + if(tcp->total_bytes < sizeof(tcp->msglen)) { + uint16_t sendlen = htons(tcp->msglen); + // send + request_length = sizeof(tcp->msglen) - tcp->total_bytes; + ERR_clear_error(); + sent = SSL_write(ssl, (const char*)&sendlen + tcp->total_bytes, + request_length); + switch(SSL_get_error(ssl,sent)) { + case SSL_ERROR_NONE: + break; + default: + log_msg(LOG_ERR, "xfrd: generic write problem with tls"); + } + + if(sent == -1) { + if(errno == EAGAIN || errno == EINTR) { + /* write would block, try later */ + return 0; + } else { + return -1; + } + } + + tcp->total_bytes += sent; + if(sent > (ssize_t)sizeof(tcp->msglen)) + buffer_skip(tcp->packet, sent-sizeof(tcp->msglen)); + if(tcp->total_bytes < sizeof(tcp->msglen)) { + /* incomplete write, resume later */ + return 0; + } + assert(tcp->total_bytes >= sizeof(tcp->msglen)); + } + + assert(tcp->total_bytes < tcp->msglen + sizeof(tcp->msglen)); + + request_length = buffer_remaining(tcp->packet); + ERR_clear_error(); + sent = SSL_write(ssl, buffer_current(tcp->packet), request_length); + switch(SSL_get_error(ssl,sent)) { + case SSL_ERROR_NONE: + break; + default: + log_msg(LOG_ERR, "xfrd: generic write problem with tls"); + } + if(sent == -1) { + if(errno == EAGAIN || errno == EINTR) { + /* write would block, try later */ + return 0; + } else { + return -1; + } + } + + buffer_skip(tcp->packet, sent); + tcp->total_bytes += sent; + + if(tcp->total_bytes < tcp->msglen + sizeof(tcp->msglen)) { + /* more to write when socket becomes writable again */ + return 0; + } + + assert(tcp->total_bytes == tcp->msglen + sizeof(tcp->msglen)); + return 1; +} +#endif + int conn_write(struct xfrd_tcp* tcp) { ssize_t sent; @@ -737,7 +987,32 @@ xfrd_tcp_write(struct xfrd_tcp_pipeline* tp, xfrd_zone_type* zone) return; } } - ret = conn_write(tcp); +#ifdef HAVE_TLS_1_3 + if (tp->ssl) { + if(tp->handshake_done) { + ret = conn_write_ssl(tcp, tp->ssl); + + } else if(ssl_handshake(tp)) { + tcp_pipe_reset_timeout(tp); /* reschedule */ + return; + + } else { + if(tp->handshake_want == SSL_ERROR_SYSCALL) { + log_msg(LOG_ERR, "xfrd: TLS handshake failed: %s", + strerror(errno)); + + } else if(tp->handshake_want == SSL_ERROR_SSL) { + log_crypto_err("xfrd: TLS handshake failed"); + } else { + log_msg(LOG_ERR, "xfrd: TLS handshake failed " + "with value: %d", tp->handshake_want); + } + xfrd_tcp_pipe_stop(tp); + return; + } + } else +#endif + ret = conn_write(tcp); if(ret == -1) { log_msg(LOG_ERR, "xfrd: failed writing tcp %s", strerror(errno)); xfrd_tcp_pipe_stop(tp); @@ -759,7 +1034,12 @@ xfrd_tcp_write(struct xfrd_tcp_pipeline* tp, xfrd_zone_type* zone) /* setup to write for this zone */ xfrd_tcp_setup_write_packet(tp, tp->tcp_send_first); /* attempt to write for this zone (if success, continue loop)*/ - ret = conn_write(tcp); +#ifdef HAVE_TLS_1_3 + if (tp->ssl) + ret = conn_write_ssl(tcp, tp->ssl); + else +#endif + ret = conn_write(tcp); if(ret == -1) { log_msg(LOG_ERR, "xfrd: failed writing tcp %s", strerror(errno)); xfrd_tcp_pipe_stop(tp); @@ -777,6 +1057,107 @@ xfrd_tcp_write(struct xfrd_tcp_pipeline* tp, xfrd_zone_type* zone) tcp_pipe_reset_timeout(tp); } +#ifdef HAVE_TLS_1_3 +static int +conn_read_ssl(struct xfrd_tcp* tcp, SSL* ssl) +{ + ssize_t received; + /* receive leading packet length bytes */ + if(tcp->total_bytes < sizeof(tcp->msglen)) { + ERR_clear_error(); + received = SSL_read(ssl, + (char*) &tcp->msglen + tcp->total_bytes, + sizeof(tcp->msglen) - tcp->total_bytes); + if (received <= 0) { + int err = SSL_get_error(ssl, received); + if(err == SSL_ERROR_WANT_READ && errno == EAGAIN) { + return 0; + } + if(err == SSL_ERROR_ZERO_RETURN) { + /* EOF */ + return 0; + } + log_msg(LOG_ERR, "ssl_read returned error %d with received %zd", err, received); + } + if(received == -1) { + if(errno == EAGAIN || errno == EINTR) { + /* read would block, try later */ + return 0; + } else { +#ifdef ECONNRESET + if (verbosity >= 2 || errno != ECONNRESET) +#endif /* ECONNRESET */ + log_msg(LOG_ERR, "tls read sz: %s", strerror(errno)); + return -1; + } + } else if(received == 0) { + /* EOF */ + return -1; + } + tcp->total_bytes += received; + if(tcp->total_bytes < sizeof(tcp->msglen)) { + /* not complete yet, try later */ + return 0; + } + + assert(tcp->total_bytes == sizeof(tcp->msglen)); + tcp->msglen = ntohs(tcp->msglen); + + if(tcp->msglen == 0) { + buffer_set_limit(tcp->packet, tcp->msglen); + return 1; + } + if(tcp->msglen > buffer_capacity(tcp->packet)) { + log_msg(LOG_ERR, "buffer too small, dropping connection"); + return 0; + } + buffer_set_limit(tcp->packet, tcp->msglen); + } + + assert(buffer_remaining(tcp->packet) > 0); + ERR_clear_error(); + + received = SSL_read(ssl, buffer_current(tcp->packet), + buffer_remaining(tcp->packet)); + + if (received <= 0) { + int err = SSL_get_error(ssl, received); + if(err == SSL_ERROR_ZERO_RETURN) { + /* EOF */ + return 0; + } + log_msg(LOG_ERR, "ssl_read returned error %d with received %zd", err, received); + } + if(received == -1) { + if(errno == EAGAIN || errno == EINTR) { + /* read would block, try later */ + return 0; + } else { +#ifdef ECONNRESET + if (verbosity >= 2 || errno != ECONNRESET) +#endif /* ECONNRESET */ + log_msg(LOG_ERR, "tcp read %s", strerror(errno)); + return -1; + } + } else if(received == 0) { + /* EOF */ + return -1; + } + + tcp->total_bytes += received; + buffer_skip(tcp->packet, received); + + if(buffer_remaining(tcp->packet) > 0) { + /* not complete yet, wait for more */ + return 0; + } + + /* completed */ + assert(buffer_position(tcp->packet) == tcp->msglen); + return 1; +} +#endif + int conn_read(struct xfrd_tcp* tcp) { @@ -861,9 +1242,34 @@ xfrd_tcp_read(struct xfrd_tcp_pipeline* tp) struct xfrd_tcp* tcp = tp->tcp_r; int ret; enum xfrd_packet_result pkt_result; +#ifdef HAVE_TLS_1_3 + if(tp->ssl) { + if(tp->handshake_done) { + ret = conn_read_ssl(tcp, tp->ssl); - ret = conn_read(tcp); + } else if(ssl_handshake(tp)) { + tcp_pipe_reset_timeout(tp); /* reschedule */ + return; + + } else { + if(tp->handshake_want == SSL_ERROR_SYSCALL) { + log_msg(LOG_ERR, "xfrd: TLS handshake failed: %s", + strerror(errno)); + + } else if(tp->handshake_want == SSL_ERROR_SSL) { + log_crypto_err("xfrd: TLS handshake failed"); + } else { + log_msg(LOG_ERR, "xfrd: TLS handshake failed " + "with value: %d", tp->handshake_want); + } + xfrd_tcp_pipe_stop(tp); + return; + } + } else +#endif + ret = conn_read(tcp); if(ret == -1) { + log_msg(LOG_ERR, "xfrd: failed writing tcp %s", strerror(errno)); xfrd_tcp_pipe_stop(tp); return; } @@ -901,7 +1307,7 @@ xfrd_tcp_read(struct xfrd_tcp_pipeline* tp) case xfrd_packet_newlease: /* set to skip if more packets with this ID */ tp->id[zone->query_id] = TCP_NULL_SKIP; - tp->num_skip++; + tp->key.num_skip++; /* fall through to remove zone from tp */ /* fallthrough */ case xfrd_packet_transfer: @@ -924,7 +1330,7 @@ xfrd_tcp_read(struct xfrd_tcp_pipeline* tp) default: /* set to skip if more packets with this ID */ tp->id[zone->query_id] = TCP_NULL_SKIP; - tp->num_skip++; + tp->key.num_skip++; xfrd_tcp_release(xfrd->tcp_set, zone); /* query next server */ xfrd_make_request(zone); @@ -950,10 +1356,10 @@ xfrd_tcp_release(struct xfrd_tcp_set* set, xfrd_zone_type* zone) if(tp->id[zone->query_id] != TCP_NULL_SKIP) tcp_pipe_id_remove(tp, zone); DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: released tcp pipe now %d unused", - tp->num_unused)); + tp->key.num_unused)); /* if pipe was full, but no more, then see if waiting element is * for the same master, and can fill the unused ID */ - if(tp->num_unused == 1 && set->tcp_waiting_first) { + if(tp->key.num_unused == 1 && set->tcp_waiting_first) { #ifdef INET6 struct sockaddr_storage to; #else @@ -961,7 +1367,7 @@ xfrd_tcp_release(struct xfrd_tcp_set* set, xfrd_zone_type* zone) #endif socklen_t to_len = xfrd_acl_sockaddr_to( set->tcp_waiting_first->master, &to); - if(to_len == tp->ip_len && memcmp(&to, &tp->ip, to_len) == 0) { + if(to_len == tp->key.ip_len && memcmp(&to, &tp->key.ip, to_len) == 0) { /* use this connection for the waiting zone */ zone = set->tcp_waiting_first; assert(zone->tcp_conn == -1); @@ -977,7 +1383,7 @@ xfrd_tcp_release(struct xfrd_tcp_set* set, xfrd_zone_type* zone) } /* if all unused, or only skipped leftover, close the pipeline */ - if(tp->num_unused >= ID_PIPE_NUM || tp->num_skip >= ID_PIPE_NUM - tp->num_unused) + if(tp->key.num_unused >= ID_PIPE_NUM || tp->key.num_skip >= ID_PIPE_NUM - tp->key.num_unused) xfrd_tcp_pipe_release(set, tp, conn); } @@ -991,6 +1397,16 @@ xfrd_tcp_pipe_release(struct xfrd_tcp_set* set, struct xfrd_tcp_pipeline* tp, event_del(&tp->handler); tp->handler_added = 0; +#ifdef HAVE_TLS_1_3 + /* close SSL */ + if (tp->ssl) { + DEBUG(DEBUG_XFRD, 1, (LOG_INFO, "xfrd: Shutting down TLS")); + SSL_shutdown(tp->ssl); + SSL_free(tp->ssl); + tp->ssl = NULL; + } +#endif + /* fd in tcp_r and tcp_w is the same, close once */ if(tp->tcp_r->fd != -1) close(tp->tcp_r->fd); @@ -998,7 +1414,7 @@ xfrd_tcp_pipe_release(struct xfrd_tcp_set* set, struct xfrd_tcp_pipeline* tp, tp->tcp_w->fd = -1; /* remove from pipetree */ - (void)rbtree_delete(xfrd->tcp_set->pipetree, &tp->node); + (void)rbtree_delete(xfrd->tcp_set->pipetree, &tp->key.node); /* a waiting zone can use the free tcp slot (to another server) */ /* if that zone fails to set-up or connect, we try to start the next @@ -1024,9 +1440,9 @@ xfrd_tcp_pipe_release(struct xfrd_tcp_set* set, struct xfrd_tcp_pipeline* tp, } /* re-init this tcppipe */ /* ip and ip_len set by tcp_open */ - tp->node.key = tp; - tp->num_unused = ID_PIPE_NUM; - tp->num_skip = 0; + tp->key.node.key = tp; + tp->key.num_unused = ID_PIPE_NUM; + tp->key.num_skip = 0; tp->tcp_send_first = NULL; tp->tcp_send_last = NULL; memset(tp->id, 0, sizeof(tp->id)); @@ -1035,7 +1451,7 @@ xfrd_tcp_pipe_release(struct xfrd_tcp_set* set, struct xfrd_tcp_pipeline* tp, } /* insert into tree */ - (void)rbtree_insert(set->pipetree, &tp->node); + (void)rbtree_insert(set->pipetree, &tp->key.node); /* setup write */ xfrd_unset_timer(zone); pipeline_setup_new_zone(set, tp, zone); diff --git a/usr.sbin/nsd/xfrd-tcp.h b/usr.sbin/nsd/xfrd-tcp.h index fd6918768d9..60d3e23965b 100644 --- a/usr.sbin/nsd/xfrd-tcp.h +++ b/usr.sbin/nsd/xfrd-tcp.h @@ -11,6 +11,10 @@ #define XFRD_TCP_H #include "xfrd.h" +#ifdef HAVE_TLS_1_3 +#include +#endif + struct buffer; struct xfrd_zone; @@ -35,6 +39,10 @@ struct xfrd_tcp_set { int tcp_timeout; /* rbtree with pipelines sorted by master */ rbtree_type* pipetree; +#ifdef HAVE_TLS_1_3 + /* XoT: SSL context */ + SSL_CTX* ssl_ctx; +#endif /* double linked list of zones waiting for a TCP connection */ struct xfrd_zone *tcp_waiting_first, *tcp_waiting_last; }; @@ -70,13 +78,10 @@ struct xfrd_tcp { #define ID_PIPE_NUM 65536 /** - * Structure to keep track of a pipelined set of queries on - * an open tcp connection. The queries may be answered with - * interleaved answer packets, the ID number disambiguates. - * Sorted by the master IP address so you can use lookup with - * smaller-or-equal to find the tcp connection most suitable. + * The tcp pipeline key structure. By ip_len, ip, num_unused and unique by + * pointer value. */ -struct xfrd_tcp_pipeline { +struct xfrd_tcp_pipeline_key { /* the rbtree node, sorted by IP and nr of unused queries */ rbnode_type node; /* destination IP address */ @@ -93,6 +98,19 @@ struct xfrd_tcp_pipeline { int num_unused; /* number of skip-set IDs (these are 'in-use') */ int num_skip; +}; + +/** + * Structure to keep track of a pipelined set of queries on + * an open tcp connection. The queries may be answered with + * interleaved answer packets, the ID number disambiguates. + * Sorted by the master IP address so you can use lookup with + * smaller-or-equal to find the tcp connection most suitable. + */ +struct xfrd_tcp_pipeline { + /* the key information for the tcp pipeline, in its own + * struct so it can be referenced on its own for comparison funcs */ + struct xfrd_tcp_pipeline_key key; int handler_added; /* the event handler for this pipe (it'll disambiguate by ID) */ @@ -105,6 +123,17 @@ struct xfrd_tcp_pipeline { struct xfrd_tcp* tcp_w; /* once a byte has been written, handshake complete */ int connection_established; +#ifdef HAVE_TLS_1_3 + /* XoT: SSL object */ + SSL *ssl; + /* XoT: if SSL handshake is not done, handshake_want indicates the + * last error. This may be SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE + * when the handshake is still in progress. + */ + int handshake_want; + /* XoT: 1 if the SSL handshake has succeeded, 0 otherwise */ + int handshake_done; +#endif /* list of queries that want to send, first to get write event, * if NULL, no write event interest */ @@ -125,7 +154,7 @@ struct xfrd_tcp_pipeline { }; /* create set of tcp connections */ -struct xfrd_tcp_set* xfrd_tcp_set_create(struct region* region); +struct xfrd_tcp_set* xfrd_tcp_set_create(struct region* region, const char *tls_cert_bundle); /* init tcp state */ struct xfrd_tcp* xfrd_tcp_create(struct region* region, size_t bufsize); diff --git a/usr.sbin/nsd/xfrd.c b/usr.sbin/nsd/xfrd.c index 8d2b1091452..6f5768f792d 100644 --- a/usr.sbin/nsd/xfrd.c +++ b/usr.sbin/nsd/xfrd.c @@ -196,7 +196,7 @@ xfrd_init(int socket, struct nsd* nsd, int shortsoa, int reload_active, daemon_remote_attach(xfrd->nsd->rc, xfrd); #endif - xfrd->tcp_set = xfrd_tcp_set_create(xfrd->region); + xfrd->tcp_set = xfrd_tcp_set_create(xfrd->region, nsd->options->tls_cert_bundle); xfrd->tcp_set->tcp_timeout = nsd->tcp_timeout; #if !defined(HAVE_ARC4RANDOM) && !defined(HAVE_GETRANDOM) srandom((unsigned long) getpid() * (unsigned long) time(NULL)); @@ -465,6 +465,7 @@ xfrd_clean_pending_tasks(struct nsd* nsd, udb_base* u) void xfrd_init_slave_zone(xfrd_state_type* xfrd, struct zone_options* zone_opt) { + int num, num_xot; xfrd_zone_type *xzone; xzone = (xfrd_zone_type*)region_alloc(xfrd->region, sizeof(xfrd_zone_type)); @@ -506,6 +507,16 @@ xfrd_init_slave_zone(xfrd_state_type* xfrd, struct zone_options* zone_opt) /* set refreshing anyway, if we have data it may be old */ xfrd_set_refresh_now(xzone); + /*Check all or none of acls use XoT*/ + num = 0; + num_xot = 0; + for (; xzone->master != NULL; xzone->master = xzone->master->next, num++) { + if (xzone->master->tls_auth_options != NULL) num_xot++; + } + if (num_xot != 0 && num != num_xot) + log_msg(LOG_WARNING, "Some but not all request-xfrs for %s have XFR-over-TLS configured", + xzone->apex_str); + xzone->node.key = xzone->apex; rbtree_insert(xfrd->zones, (rbnode_type*)xzone); } diff --git a/usr.sbin/nsd/zonec.c b/usr.sbin/nsd/zonec.c index 39e9b3876c2..bebd9b43e5f 100644 --- a/usr.sbin/nsd/zonec.c +++ b/usr.sbin/nsd/zonec.c @@ -46,6 +46,8 @@ #define ILNP_MAXDIGITS 4 #define ILNP_NUMGROUPS 4 +#define SVCB_MAX_COMMA_SEPARATED_VALUES 1000 + const dname_type *error_dname; domain_type *error_domain; @@ -753,6 +755,443 @@ zparser_conv_nsec(region_type *region, return r; } +static uint16_t +svcbparam_lookup_key(const char *key, size_t key_len) +{ + char buf[64]; + char *endptr; + unsigned long int key_value; + + if (key_len >= 4 && key_len <= 8 && !strncmp(key, "key", 3)) { + memcpy(buf, key + 3, key_len - 3); + buf[key_len - 3] = 0; + key_value = strtoul(buf, &endptr, 10); + if (endptr > buf /* digits seen */ + && *endptr == 0 /* no non-digit chars after digits */ + && key_value <= 65535) /* no overflow */ + return key_value; + + } else switch (key_len) { + case sizeof("mandatory")-1: + if (!strncmp(key, "mandatory", sizeof("mandatory")-1)) + return SVCB_KEY_MANDATORY; + if (!strncmp(key, "echconfig", sizeof("echconfig")-1)) + return SVCB_KEY_ECH; /* allow "echconfig as well as "ech" */ + break; + + case sizeof("alpn")-1: + if (!strncmp(key, "alpn", sizeof("alpn")-1)) + return SVCB_KEY_ALPN; + if (!strncmp(key, "port", sizeof("port")-1)) + return SVCB_KEY_PORT; + break; + + case sizeof("no-default-alpn")-1: + if (!strncmp( key , "no-default-alpn" + , sizeof("no-default-alpn")-1)) + return SVCB_KEY_NO_DEFAULT_ALPN; + break; + + case sizeof("ipv4hint")-1: + if (!strncmp(key, "ipv4hint", sizeof("ipv4hint")-1)) + return SVCB_KEY_IPV4HINT; + if (!strncmp(key, "ipv6hint", sizeof("ipv6hint")-1)) + return SVCB_KEY_IPV6HINT; + break; + case sizeof("ech")-1: + if (!strncmp(key, "ech", sizeof("ech")-1)) + return SVCB_KEY_ECH; + break; + default: + break; + } + if (key_len > sizeof(buf) - 1) + zc_error_prev_line("Unknown SvcParamKey"); + else { + memcpy(buf, key, key_len); + buf[key_len] = 0; + zc_error_prev_line("Unknown SvcParamKey: %s", buf); + } + /* Although the returned value might be used by the caller, + * the parser has erred, so the zone will not be loaded. + */ + return -1; +} + +static uint16_t * +zparser_conv_svcbparam_port_value(region_type *region, const char *val) +{ + unsigned long int port; + char *endptr; + uint16_t *r; + + port = strtoul(val, &endptr, 10); + if (endptr > val /* digits seen */ + && *endptr == 0 /* no non-digit chars after digits */ + && port <= 65535) { /* no overflow */ + + r = alloc_rdata(region, 3 * sizeof(uint16_t)); + r[1] = htons(SVCB_KEY_PORT); + r[2] = htons(sizeof(uint16_t)); + r[3] = htons(port); + return r; + } + zc_error_prev_line("Could not parse port SvcParamValue: \"%s\"", val); + return NULL; +} + +static uint16_t * +zparser_conv_svcbparam_ipv4hint_value(region_type *region, const char *val) +{ + uint16_t *r; + int count; + char ip_str[INET_ADDRSTRLEN+1]; + char *next_ip_str; + uint32_t *ip_wire_dst; + size_t i; + + for (i = 0, count = 1; val[i]; i++) { + if (val[i] == ',') + count += 1; + if (count > SVCB_MAX_COMMA_SEPARATED_VALUES) { + zc_error_prev_line("Too many IPV4 addresses in ipv4hint"); + return NULL; + } + } + + /* count == number of comma's in val + 1, so the actual number of IPv4 + * addresses in val + */ + r = alloc_rdata(region, 2 * sizeof(uint16_t) + IP4ADDRLEN * count); + r[1] = htons(SVCB_KEY_IPV4HINT); + r[2] = htons(IP4ADDRLEN * count); + ip_wire_dst = (void *)&r[3]; + + while (count) { + if (!(next_ip_str = strchr(val, ','))) { + if (inet_pton(AF_INET, val, ip_wire_dst) != 1) + break; + + assert(count == 1); + + } else if (next_ip_str - val >= (int)sizeof(ip_str)) + break; + + else { + memcpy(ip_str, val, next_ip_str - val); + ip_str[next_ip_str - val] = 0; + if (inet_pton(AF_INET, ip_str, ip_wire_dst) != 1) { + val = ip_str; /* to use in error reporting below */ + break; + } + + val = next_ip_str + 1; + } + ip_wire_dst++; + count--; + } + if (count) + zc_error_prev_line("Could not parse ipv4hint SvcParamValue: %s", val); + + return r; +} + +static uint16_t * +zparser_conv_svcbparam_ipv6hint_value(region_type *region, const char *val) +{ + uint16_t *r; + int i, count; + char ip6_str[INET6_ADDRSTRLEN+1]; + char *next_ip6_str; + uint8_t *ipv6_wire_dst; + + for (i = 0, count = 1; val[i]; i++) { + if (val[i] == ',') + count += 1; + if (count > SVCB_MAX_COMMA_SEPARATED_VALUES) { + zc_error_prev_line("Too many IPV6 addresses in ipv6hint"); + return NULL; + } + } + + /* count == number of comma's in val + 1 + * so actually the number of IPv6 addresses in val + */ + r = alloc_rdata(region, 2 * sizeof(uint16_t) + IP6ADDRLEN * count); + r[1] = htons(SVCB_KEY_IPV6HINT); + r[2] = htons(IP6ADDRLEN * count); + ipv6_wire_dst = (void *)&r[3]; + + while (count) { + if (!(next_ip6_str = strchr(val, ','))) { + if ((inet_pton(AF_INET6, val, ipv6_wire_dst) != 1)) + break; + + assert(count == 1); + + } else if (next_ip6_str - val >= (int)sizeof(ip6_str)) + break; + + else { + memcpy(ip6_str, val, next_ip6_str - val); + ip6_str[next_ip6_str - val] = 0; + if (inet_pton(AF_INET6, ip6_str, ipv6_wire_dst) != 1) { + val = ip6_str; /* for error reporting below */ + break; + } + + val = next_ip6_str + 1; /* skip the comma */ + } + ipv6_wire_dst += IP6ADDRLEN; + count--; + } + if (count) + zc_error_prev_line("Could not parse ipv6hint SvcParamValue: %s", val); + + return r; +} + +static int +network_uint16_cmp(const void *a, const void *b) +{ + return ((int)read_uint16(a)) - ((int)read_uint16(b)); +} + +static uint16_t * +zparser_conv_svcbparam_mandatory_value(region_type *region, + const char *val, size_t val_len) +{ + uint16_t *r; + size_t i, count; + char* next_key; + uint16_t* key_dst; + + for (i = 0, count = 1; val[i]; i++) { + if (val[i] == ',') + count += 1; + if (count > SVCB_MAX_COMMA_SEPARATED_VALUES) { + zc_error_prev_line("Too many keys in mandatory"); + return NULL; + } + } + + r = alloc_rdata(region, (2 + count) * sizeof(uint16_t)); + r[1] = htons(SVCB_KEY_MANDATORY); + r[2] = htons(sizeof(uint16_t) * count); + key_dst = (void *)&r[3]; + + for(;;) { + if (!(next_key = strchr(val, ','))) { + *key_dst = htons(svcbparam_lookup_key(val, val_len)); + break; + } else { + *key_dst = htons(svcbparam_lookup_key(val, next_key - val)); + } + + val_len -= next_key - val + 1; + val = next_key + 1; /* skip the comma */ + key_dst += 1; + } + + /* In draft-ietf-dnsop-svcb-https-04 Section 7: + * + * In wire format, the keys are represented by their numeric + * values in network byte order, concatenated in ascending order. + */ + qsort((void *)&r[3], count, sizeof(uint16_t), network_uint16_cmp); + + return r; +} + +static uint16_t * +zparser_conv_svcbparam_ech_value(region_type *region, const char *b64) +{ + uint8_t buffer[B64BUFSIZE]; + uint16_t *r = NULL; + int wire_len; + + if(strcmp(b64, "0") == 0) { + /* single 0 represents empty buffer */ + return alloc_rdata(region, 0); + } + wire_len = __b64_pton(b64, buffer, B64BUFSIZE); + if (wire_len == -1) { + zc_error_prev_line("invalid base64 data in ech"); + } else { + r = alloc_rdata(region, 2 * sizeof(uint16_t) + wire_len); + r[1] = htons(SVCB_KEY_ECH); + r[2] = htons(wire_len); + memcpy(&r[3], buffer, wire_len); + } + + return r; +} + +static const char* parse_alpn_next_unescaped_comma(const char *val) +{ + while (*val) { + /* Only return when the comma is not escaped*/ + if (*val == '\\'){ + ++val; + if (!*val) + break; + } else if (*val == ',') + return val; + + val++; + } + return NULL; +} + +static size_t +parse_alpn_copy_unescaped(uint8_t *dst, const char *src, size_t len) +{ + uint8_t *orig_dst = dst; + + while (len) { + if (*src == '\\') { + src++; + len--; + if (!len) + break; + } + *dst++ = *src++; + len--; + } + return (size_t)(dst - orig_dst); +} + +static uint16_t * +zparser_conv_svcbparam_alpn_value(region_type *region, + const char *val, size_t val_len) +{ + uint8_t unescaped_dst[65536]; + uint8_t *dst = unescaped_dst; + const char *next_str; + size_t str_len; + size_t dst_len; + uint16_t *r = NULL; + + if (val_len > sizeof(unescaped_dst)) { + zc_error_prev_line("invalid alpn"); + return r; + } + while (val_len) { + size_t dst_len; + + str_len = (next_str = parse_alpn_next_unescaped_comma(val)) + ? (size_t)(next_str - val) : val_len; + + if (str_len > 255) { + zc_error_prev_line("alpn strings need to be" + " smaller than 255 chars"); + return r; + } + dst_len = parse_alpn_copy_unescaped(dst + 1, val, str_len); + *dst++ = dst_len; + dst += dst_len; + + if (!next_str) + break; + + /* skip the comma for the next iteration */ + val_len -= next_str - val + 1; + val = next_str + 1; + } + dst_len = dst - unescaped_dst; + r = alloc_rdata(region, 2 * sizeof(uint16_t) + dst_len); + r[1] = htons(SVCB_KEY_ALPN); + r[2] = htons(dst_len); + memcpy(&r[3], unescaped_dst, dst_len); + return r; +} + +static uint16_t * +zparser_conv_svcbparam_key_value(region_type *region, + const char *key, size_t key_len, const char *val, size_t val_len) +{ + uint16_t svcparamkey = svcbparam_lookup_key(key, key_len); + uint16_t *r; + + switch (svcparamkey) { + case SVCB_KEY_PORT: + return zparser_conv_svcbparam_port_value(region, val); + case SVCB_KEY_IPV4HINT: + return zparser_conv_svcbparam_ipv4hint_value(region, val); + case SVCB_KEY_IPV6HINT: + return zparser_conv_svcbparam_ipv6hint_value(region, val); + case SVCB_KEY_MANDATORY: + return zparser_conv_svcbparam_mandatory_value(region, val, val_len); + case SVCB_KEY_NO_DEFAULT_ALPN: + if(zone_is_slave(parser->current_zone->opts)) + zc_warning_prev_line("no-default-alpn should not have a value"); + else + zc_error_prev_line("no-default-alpn should not have a value"); + break; + case SVCB_KEY_ECH: + return zparser_conv_svcbparam_ech_value(region, val); + case SVCB_KEY_ALPN: + return zparser_conv_svcbparam_alpn_value(region, val, val_len); + default: + break; + } + r = alloc_rdata(region, 2 * sizeof(uint16_t) + val_len); + r[1] = htons(svcparamkey); + r[2] = htons(val_len); + memcpy(r + 3, val, val_len); + return r; +} + +uint16_t * +zparser_conv_svcbparam(region_type *region, const char *key, size_t key_len + , const char *val, size_t val_len) +{ + const char *eq; + uint16_t *r; + uint16_t svcparamkey; + + /* Form ="" (or at least with quoted value) */ + if (val && val_len) { + /* Does key end with '=' */ + if (key_len && key[key_len - 1] == '=') + return zparser_conv_svcbparam_key_value( + region, key, key_len - 1, val, val_len); + + zc_error_prev_line( "SvcParam syntax error in param: %s\"%s\"" + , key, val); + } + assert(val == NULL); + if ((eq = memchr(key, '=', key_len))) { + size_t new_key_len = eq - key; + + if (key_len - new_key_len - 1 > 0) + return zparser_conv_svcbparam_key_value(region, + key, new_key_len, eq+1, key_len - new_key_len - 1); + key_len = new_key_len; + } + /* Some SvcParamKeys require values */ + svcparamkey = svcbparam_lookup_key(key, key_len); + switch (svcparamkey) { + case SVCB_KEY_MANDATORY: + case SVCB_KEY_ALPN: + case SVCB_KEY_PORT: + case SVCB_KEY_IPV4HINT: + case SVCB_KEY_IPV6HINT: + if(zone_is_slave(parser->current_zone->opts)) + zc_warning_prev_line("value expected for SvcParam: %s", key); + else + zc_error_prev_line("value expected for SvcParam: %s", key); + break; + default: + break; + } + /* SvcParam is only a SvcParamKey */ + r = alloc_rdata(region, 2 * sizeof(uint16_t)); + r[1] = htons(svcparamkey); + r[2] = 0; + return r; +} + /* Parse an int terminated in the specified range. */ static int parse_int(const char *str, @@ -1215,6 +1654,147 @@ zadd_rdata_txt_clean_wireformat() } } +static int +svcparam_key_cmp(const void *a, const void *b) +{ + return ((int)read_uint16(rdata_atom_data(*(rdata_atom_type *)a))) + - ((int)read_uint16(rdata_atom_data(*(rdata_atom_type *)b))); +} + +void +zadd_rdata_svcb_check_wireformat() +{ + size_t i; + uint8_t paramkeys[65536]; + int prev_key = - 1; + int key = 0; + size_t size; + uint16_t *mandatory_values; + + if (parser->current_rr.rdata_count <= 2) { + if (!parser->error_occurred) + zc_error_prev_line("invalid SVCB or HTTPS rdata"); + return; + } else for (i = 2; i < parser->current_rr.rdata_count; i++) { + if (parser->current_rr.rdatas[i].data == NULL + || rdata_atom_data(parser->current_rr.rdatas[i]) == NULL + || rdata_atom_size(parser->current_rr.rdatas[i]) < 4) { + if (!parser->error_occurred) + zc_error_prev_line("invalid SVCB or HTTPS rdata"); + return; + } + } + /* After this point, all rdatas do have data larger than 4 bytes. + * So we may assume a uint16_t SVCB key followed by uint16_t length + * in each rdata in the remainder of this function. + */ + memset(paramkeys, 0, sizeof(paramkeys)); + /* + * In draft-ietf-dnsop-svcb-https-04 Section 7: + * In wire format, the keys are represented by their numeric values in + * network byte order, concatenated in ascending order. + * + * svcparam_key_cmp assumes the rdatas to have a SVCB key, which is + * safe because we checked. + * + */ + qsort( (void *)&parser->current_rr.rdatas[2] + , parser->current_rr.rdata_count - 2 + , sizeof(rdata_atom_type) + , svcparam_key_cmp + ); + + for (i = 2; i < parser->current_rr.rdata_count; i++) { + assert(parser->current_rr.rdatas[i].data); + assert(rdata_atom_data(parser->current_rr.rdatas[i])); + assert(rdata_atom_size(parser->current_rr.rdatas[i]) >= sizeof(uint16_t)); + + key = read_uint16(rdata_atom_data(parser->current_rr.rdatas[i])); + + /* In draft-ietf-dnsop-svcb-https-04 Section 7: + * + * Keys (...) MUST NOT appear more than once. + * + * If they key has already been seen, we have a duplicate + */ + if (!paramkeys[key]) + /* keep track of keys that are present */ + paramkeys[key] = 1; + + else if (key < SVCPARAMKEY_COUNT) { + if(zone_is_slave(parser->current_zone->opts)) + zc_warning_prev_line( + "Duplicate key found: %s", + svcparamkey_strs[key]); + else { + zc_error_prev_line( + "Duplicate key found: %s", + svcparamkey_strs[key]); + } + } else if(zone_is_slave(parser->current_zone->opts)) + zc_warning_prev_line( + "Duplicate key found: key%d", key); + else + zc_error_prev_line( + "Duplicate key found: key%d", key); + } + /* Checks when a mandatory key is present */ + if (!paramkeys[SVCB_KEY_MANDATORY]) + return; + + size = rdata_atom_size(parser->current_rr.rdatas[2]); + assert(size >= 4); + mandatory_values = (void*)rdata_atom_data(parser->current_rr.rdatas[2]); + mandatory_values += 2; /* skip the key type and length */ + + if (size % 2) + zc_error_prev_line("mandatory rdata must be a multiple of shorts"); + + else for (i = 0; i < (size - 4)/2; i++) { + key = ntohs(mandatory_values[i]); + + if (paramkeys[key]) + ; /* pass */ + + else if (key < SVCPARAMKEY_COUNT) { + if(zone_is_slave(parser->current_zone->opts)) + zc_warning_prev_line("mandatory SvcParamKey: %s is missing " + "the record", svcparamkey_strs[key]); + else + zc_error_prev_line("mandatory SvcParamKey: %s is missing " + "the record", svcparamkey_strs[key]); + } else { + if(zone_is_slave(parser->current_zone->opts)) + zc_warning_prev_line("mandatory SvcParamKey: key%d is missing " + "the record", key); + else + zc_error_prev_line("mandatory SvcParamKey: key%d is missing " + "the record", key); + } + + /* In draft-ietf-dnsop-svcb-https-04 Section 8 + * automatically mandatory MUST NOT appear in its own value-list + */ + if (key == SVCB_KEY_MANDATORY) { + if(zone_is_slave(parser->current_zone->opts)) + zc_warning_prev_line("mandatory MUST not be included" + " as mandatory parameter"); + else + zc_error_prev_line("mandatory MUST not be included" + " as mandatory parameter"); + } + if (key == prev_key) { + if(zone_is_slave(parser->current_zone->opts)) + zc_warning_prev_line("Keys inSvcParam mandatory " + "MUST NOT appear more than once."); + else + zc_error_prev_line("Keys in SvcParam mandatory " + "MUST NOT appear more than once."); + } + prev_key = key; + } +} + void zadd_rdata_domain(domain_type *domain) { @@ -1409,6 +1989,16 @@ process_rr(void) zc_error_prev_line("maximum rdata length exceeds %d octets", MAX_RDLENGTH); return 0; } + + /* We cannot print invalid owner names, + * so error on that before it is used in printing other errors. + */ + if (rr->owner == error_domain + || domain_dname(rr->owner) == error_dname) { + zc_error_prev_line("invalid owner name"); + return 0; + } + /* we have the zone already */ assert(zone); if (rr->type == TYPE_SOA) { diff --git a/usr.sbin/nsd/zonec.h b/usr.sbin/nsd/zonec.h index 97bd0f2e92e..7c30a90b390 100644 --- a/usr.sbin/nsd/zonec.h +++ b/usr.sbin/nsd/zonec.h @@ -111,6 +111,8 @@ uint16_t *zparser_conv_algorithm(region_type *region, const char *algstr); uint16_t *zparser_conv_certificate_type(region_type *region, const char *typestr); uint16_t *zparser_conv_apl_rdata(region_type *region, char *str); +uint16_t *zparser_conv_svcbparam(region_type *region, + const char *key, size_t key_len, const char *value, size_t value_len); void parse_unknown_rdata(uint16_t type, uint16_t *wireformat); @@ -118,6 +120,7 @@ uint32_t zparser_ttl2int(const char *ttlstr, int* error); void zadd_rdata_wireformat(uint16_t *data); void zadd_rdata_txt_wireformat(uint16_t *data, int first); void zadd_rdata_txt_clean_wireformat(void); +void zadd_rdata_svcb_check_wireformat(void); void zadd_rdata_domain(domain_type *domain); void set_bitnsec(uint8_t bits[NSEC_WINDOW_COUNT][NSEC_WINDOW_BITS_SIZE], diff --git a/usr.sbin/nsd/zparser.y b/usr.sbin/nsd/zparser.y index 89aca35211a..79cd6101862 100644 --- a/usr.sbin/nsd/zparser.y +++ b/usr.sbin/nsd/zparser.y @@ -68,7 +68,7 @@ nsec3_add_params(const char* hash_algo_str, const char* flag_str, %token T_AXFR T_MAILB T_MAILA T_DS T_DLV T_SSHFP T_RRSIG T_NSEC T_DNSKEY %token T_SPF T_NSEC3 T_IPSECKEY T_DHCID T_NSEC3PARAM T_TLSA T_URI %token T_NID T_L32 T_L64 T_LP T_EUI48 T_EUI64 T_CAA T_CDS T_CDNSKEY -%token T_OPENPGPKEY T_CSYNC T_ZONEMD T_AVC T_SMIMEA +%token T_OPENPGPKEY T_CSYNC T_ZONEMD T_AVC T_SMIMEA T_SVCB T_HTTPS /* other tokens */ %token DOLLAR_TTL DOLLAR_ORIGIN NL SP @@ -85,7 +85,7 @@ nsec3_add_params(const char* hash_algo_str, const char* flag_str, %type rel_dname label %type wire_dname wire_abs_dname wire_rel_dname wire_label %type str concatenated_str_seq str_sp_seq str_dot_seq -%type unquoted_dotted_str dotted_str +%type unquoted_dotted_str dotted_str svcparam svcparams %type nxt_seq nsec_more %type rdata_unknown @@ -707,6 +707,10 @@ type_and_rdata: | T_CSYNC sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } | T_ZONEMD sp rdata_zonemd | T_ZONEMD sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_SVCB sp rdata_svcb + | T_SVCB sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_HTTPS sp rdata_svcb + | T_HTTPS sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } | T_URI sp rdata_uri | T_URI sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } | T_UTYPE sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } @@ -1169,6 +1173,35 @@ rdata_zonemd: str sp str sp str sp str_sp_seq trail } ; +svcparam: dotted_str QSTR + { + zadd_rdata_wireformat(zparser_conv_svcbparam( + parser->region, $1.str, $1.len, $2.str, $2.len)); + } + | dotted_str + { + zadd_rdata_wireformat(zparser_conv_svcbparam( + parser->region, $1.str, $1.len, NULL, 0)); + } + ; +svcparams: svcparam + | svcparams sp svcparam + ; +/* draft-ietf-dnsop-svcb-https */ +rdata_svcb_base: str sp dname + { + /* SvcFieldPriority */ + zadd_rdata_wireformat(zparser_conv_short(parser->region, $1.str)); + /* SvcDomainName */ + zadd_rdata_domain($3); + }; +rdata_svcb: rdata_svcb_base sp svcparams trail + { + zadd_rdata_svcb_check_wireformat(); + } + | rdata_svcb_base trail + ; + rdata_unknown: URR sp str sp str_sp_seq trail { /* $2 is the number of octets, currently ignored */ -- 2.20.1