From 18e776127a3b3d7fda82c97b5002f76e772cc3a0 Mon Sep 17 00:00:00 2001 From: florian Date: Thu, 16 Aug 2018 17:56:18 +0000 Subject: [PATCH] update to 4.1.24 OK sthen --- usr.sbin/nsd/config.h.in | 6 + usr.sbin/nsd/configlexer.lex | 1 + usr.sbin/nsd/configparser.y | 22 +- usr.sbin/nsd/configure | 60 ++- usr.sbin/nsd/configure.ac | 14 +- usr.sbin/nsd/difffile.c | 4 +- usr.sbin/nsd/dns.c | 7 +- usr.sbin/nsd/dns.h | 1 + usr.sbin/nsd/nsd-checkconf.8.in | 2 +- usr.sbin/nsd/nsd-checkconf.c | 2 + usr.sbin/nsd/nsd-checkzone.8.in | 2 +- usr.sbin/nsd/nsd-control.8.in | 2 +- usr.sbin/nsd/nsd-control.c | 123 ++++-- usr.sbin/nsd/nsd.8.in | 4 +- usr.sbin/nsd/nsd.conf.5.in | 13 +- usr.sbin/nsd/nsd.conf.sample.in | 5 + usr.sbin/nsd/nsec3.c | 160 +++++--- usr.sbin/nsd/nsec3.h | 2 +- usr.sbin/nsd/options.c | 13 + usr.sbin/nsd/options.h | 5 + usr.sbin/nsd/remote.c | 651 +++++++++++++++++++++----------- usr.sbin/nsd/remote.h | 9 + usr.sbin/nsd/server.c | 6 +- usr.sbin/nsd/xfrd.c | 20 + usr.sbin/nsd/zparser.y | 17 +- 25 files changed, 800 insertions(+), 351 deletions(-) diff --git a/usr.sbin/nsd/config.h.in b/usr.sbin/nsd/config.h.in index d3470836f26..eded09dd6b3 100644 --- a/usr.sbin/nsd/config.h.in +++ b/usr.sbin/nsd/config.h.in @@ -317,6 +317,9 @@ /* Define to 1 if you have the `strtol' function. */ #undef HAVE_STRTOL +/* Define to 1 if `sun_len' is a member of `struct sockaddr_un'. */ +#undef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN + /* Define to 1 if `st_mtimensec' is a member of `struct stat'. */ #undef HAVE_STRUCT_STAT_ST_MTIMENSEC @@ -350,6 +353,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TYPES_H +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_UN_H + /* Define to 1 if you have that is POSIX.1 compatible. */ #undef HAVE_SYS_WAIT_H diff --git a/usr.sbin/nsd/configlexer.lex b/usr.sbin/nsd/configlexer.lex index 1f2628d7695..7fd4f17363f 100644 --- a/usr.sbin/nsd/configlexer.lex +++ b/usr.sbin/nsd/configlexer.lex @@ -203,6 +203,7 @@ interface{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_IP_ADDRESS;} ip-transparent{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_IP_TRANSPARENT;} ip-freebind{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_IP_FREEBIND;} debug-mode{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DEBUG_MODE;} +use-systemd{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_USE_SYSTEMD;} hide-version{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_HIDE_VERSION;} ip4-only{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_IP4_ONLY;} ip6-only{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_IP6_ONLY;} diff --git a/usr.sbin/nsd/configparser.y b/usr.sbin/nsd/configparser.y index b9bf8200f99..547518f88c6 100644 --- a/usr.sbin/nsd/configparser.y +++ b/usr.sbin/nsd/configparser.y @@ -72,6 +72,7 @@ extern config_parser_state_type* cfg_parser; %token VAR_MAX_REFRESH_TIME VAR_MIN_REFRESH_TIME %token VAR_MAX_RETRY_TIME VAR_MIN_RETRY_TIME %token VAR_MULTI_MASTER_CHECK VAR_MINIMAL_RESPONSES VAR_REFUSE_ANY +%token VAR_USE_SYSTEMD %% toplevelvars: /* empty */ | toplevelvars toplevelvar ; @@ -103,7 +104,7 @@ content_server: server_ip_address | server_ip_transparent | server_debug_mode | server_zonefiles_check | server_do_ip4 | server_do_ip6 | server_zonefiles_write | server_log_time_ascii | server_round_robin | server_reuseport | server_version | server_ip_freebind | - server_minimal_responses | server_refuse_any; + server_minimal_responses | server_refuse_any | server_use_systemd; server_ip_address: VAR_IP_ADDRESS STRING { OUTYY(("P(server_ip_address:%s)\n", $2)); @@ -150,6 +151,14 @@ server_debug_mode: VAR_DEBUG_MODE STRING else cfg_parser->opt->debug_mode = (strcmp($2, "yes")==0); } ; +server_use_systemd: VAR_USE_SYSTEMD STRING + { + OUTYY(("P(server_use_systemd:%s)\n", $2)); + if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0) + yyerror("expected yes or no."); + else cfg_parser->opt->use_systemd = (strcmp($2, "yes")==0); + } + ; server_verbosity: VAR_VERBOSITY STRING { OUTYY(("P(server_verbosity:%s)\n", $2)); @@ -550,11 +559,18 @@ rc_control_port: VAR_CONTROL_PORT STRING ; rc_control_interface: VAR_CONTROL_INTERFACE STRING { + ip_address_option_type* last = NULL; ip_address_option_type* o = (ip_address_option_type*)region_alloc( cfg_parser->opt->region, sizeof(ip_address_option_type)); OUTYY(("P(control_interface:%s)\n", $2)); - o->next = cfg_parser->opt->control_interface; - cfg_parser->opt->control_interface = o; + /* append at end */ + last = cfg_parser->opt->control_interface; + while(last && last->next) + last = last->next; + if(last == NULL) + cfg_parser->opt->control_interface = o; + else last->next = o; + o->next = NULL; o->address = region_strdup(cfg_parser->opt->region, $2); } ; diff --git a/usr.sbin/nsd/configure b/usr.sbin/nsd/configure index 79f500f50fd..13da401ab9b 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.1.23. +# Generated by GNU Autoconf 2.69 for NSD 4.1.24. # # Report bugs to . # @@ -580,8 +580,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='NSD' PACKAGE_TARNAME='nsd' -PACKAGE_VERSION='4.1.23' -PACKAGE_STRING='NSD 4.1.23' +PACKAGE_VERSION='4.1.24' +PACKAGE_STRING='NSD 4.1.24' PACKAGE_BUGREPORT='nsd-bugs@nlnetlabs.nl' PACKAGE_URL='' @@ -734,6 +734,7 @@ enable_minimal_responses enable_mmap enable_radix_tree enable_packed +enable_systemd ' ac_precious_vars='build_alias host_alias @@ -1286,7 +1287,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.1.23 to adapt to many kinds of systems. +\`configure' configures NSD 4.1.24 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1347,7 +1348,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of NSD 4.1.23:";; + short | recursive ) echo "Configuration of NSD 4.1.24:";; esac cat <<\_ACEOF @@ -1386,6 +1387,7 @@ Optional Features: less memory, but uses some more CPU. --enable-packed Enable packed structure alignment, uses less memory, but unaligned reads. + --enable-systemd compile with systemd support Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] @@ -1496,7 +1498,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -NSD configure 4.1.23 +NSD configure 4.1.24 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2205,7 +2207,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.1.23, which was +It was created by NSD $as_me 4.1.24, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -6036,7 +6038,7 @@ $as_echo "#define HAVE_SYS_WAIT_H 1" >>confdefs.h fi -for ac_header in time.h arpa/inet.h signal.h string.h strings.h fcntl.h limits.h netinet/in.h netinet/tcp.h stddef.h sys/param.h sys/socket.h syslog.h unistd.h sys/select.h stdarg.h stdint.h netdb.h sys/bitypes.h tcpd.h glob.h grp.h endian.h +for ac_header in time.h arpa/inet.h signal.h string.h strings.h fcntl.h limits.h netinet/in.h netinet/tcp.h stddef.h sys/param.h sys/socket.h sys/un.h syslog.h unistd.h sys/select.h stdarg.h stdint.h netdb.h sys/bitypes.h tcpd.h glob.h grp.h endian.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" @@ -7221,6 +7223,22 @@ cat >>confdefs.h <<_ACEOF _ACEOF +fi + +ac_fn_c_check_member "$LINENO" "struct sockaddr_un" "sun_len" "ac_cv_member_struct_sockaddr_un_sun_len" " +$ac_includes_default +#ifdef HAVE_SYS_UN_H +#include +#endif + +" +if test "x$ac_cv_member_struct_sockaddr_un_sun_len" = xyes; then : + +cat >>confdefs.h <<_ACEOF +#define HAVE_STRUCT_SOCKADDR_UN_SUN_LEN 1 +_ACEOF + + fi @@ -9238,6 +9256,28 @@ fi ;; esac +# Include systemd.m4 - begin +# macros for configuring systemd +# Copyright 2015, Sami Kerola, CloudFlare. +# BSD licensed. +# Check whether --enable-systemd was given. +if test "${enable_systemd+set}" = set; then : + enableval=$enable_systemd; +else + enable_systemd=no +fi + +have_systemd=no +if test "x$enable_systemd" != xno; then : + + + as_fn_error $? "systemd enabled but need pkg-config to configure for it, also, run aclocal before autoconf, or run autoreconf to include pkgconfig macros" "$LINENO" 5 + + +fi + +# Include systemd.m4 - end + @@ -9784,7 +9824,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.1.23, which was +This file was extended by NSD $as_me 4.1.24, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -9846,7 +9886,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.1.23 +NSD config.status 4.1.24 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 c17501cb5a0..80307c594c2 100644 --- a/usr.sbin/nsd/configure.ac +++ b/usr.sbin/nsd/configure.ac @@ -4,7 +4,7 @@ dnl sinclude(acx_nlnetlabs.m4) -AC_INIT(NSD,4.1.23,nsd-bugs@nlnetlabs.nl) +AC_INIT(NSD,4.1.24,nsd-bugs@nlnetlabs.nl) AC_CONFIG_HEADER([config.h]) CFLAGS="$CFLAGS" @@ -415,7 +415,7 @@ fi # Checks for header files. AC_HEADER_STDC AC_HEADER_SYS_WAIT -AC_CHECK_HEADERS([time.h arpa/inet.h signal.h string.h strings.h fcntl.h limits.h netinet/in.h netinet/tcp.h stddef.h sys/param.h sys/socket.h syslog.h unistd.h sys/select.h stdarg.h stdint.h netdb.h sys/bitypes.h tcpd.h glob.h grp.h endian.h]) +AC_CHECK_HEADERS([time.h arpa/inet.h signal.h string.h strings.h fcntl.h limits.h netinet/in.h netinet/tcp.h stddef.h sys/param.h sys/socket.h sys/un.h syslog.h unistd.h sys/select.h stdarg.h stdint.h netdb.h sys/bitypes.h tcpd.h glob.h grp.h endian.h]) AC_DEFUN([CHECK_VALIST_DEF], [ @@ -586,6 +586,12 @@ AC_CHECK_TYPE(in_addr_t, [], [AC_DEFINE([in_addr_t], [uint32_t], [in_addr_t])], #endif]) ACX_CHECK_SS_FAMILY AC_CHECK_MEMBERS([struct stat.st_mtimensec, struct stat.st_mtim.tv_nsec]) +AC_CHECK_MEMBERS([struct sockaddr_un.sun_len],,,[ +AC_INCLUDES_DEFAULT +#ifdef HAVE_SYS_UN_H +#include +#endif +]) # Checks for library functions. AC_FUNC_CHOWN @@ -932,6 +938,10 @@ case "$enable_packed" in ;; esac +# Include systemd.m4 - begin +sinclude(systemd.m4) +# Include systemd.m4 - end + AH_BOTTOM([ /* define before includes as it specifies what standard to use. */ #if (defined(HAVE_PSELECT) && !defined (HAVE_PSELECT_PROTO)) \ diff --git a/usr.sbin/nsd/difffile.c b/usr.sbin/nsd/difffile.c index eb53c851342..1fec3a93896 100644 --- a/usr.sbin/nsd/difffile.c +++ b/usr.sbin/nsd/difffile.c @@ -462,7 +462,7 @@ nsec3_delete_rr_trigger(namedb_type* db, rr_type* rr, zone_type* zone, /* clear trees, wipe hashes, wipe precompile */ nsec3_clear_precompile(db, zone); /* pick up new nsec3param (from udb, or avoid deleted rr) */ - nsec3_find_zone_param(db, zone, udbz, rr); + nsec3_find_zone_param(db, zone, udbz, rr, 0); /* if no more NSEC3, done */ if(!zone->nsec3_param) return; @@ -568,7 +568,7 @@ nsec3_add_rr_trigger(namedb_type* db, rr_type* rr, zone_type* zone, prehash_add(db->domains, rr->owner); } else if(!zone->nsec3_param && rr->type == TYPE_NSEC3PARAM) { /* see if this means NSEC3 chain can be used */ - nsec3_find_zone_param(db, zone, udbz, NULL); + nsec3_find_zone_param(db, zone, udbz, NULL, 0); if(!zone->nsec3_param) return; nsec3_zone_trees_create(db->region, zone); diff --git a/usr.sbin/nsd/dns.c b/usr.sbin/nsd/dns.c index 9d8ac8a545c..ad0d9245d49 100644 --- a/usr.sbin/nsd/dns.c +++ b/usr.sbin/nsd/dns.c @@ -277,7 +277,12 @@ static rrtype_descriptor_type rrtype_descriptors[(RRTYPE_DESCRIPTORS_LENGTH+1)] RDATA_WF_BINARY }, /* certificate association data */ { RDATA_ZF_BYTE, RDATA_ZF_BYTE, RDATA_ZF_BYTE, RDATA_ZF_HEX } }, /* 53 */ - { 53, NULL, T_UTYPE, 1, 1, { RDATA_WF_BINARY }, { RDATA_ZF_UNKNOWN } }, + { TYPE_SMIMEA, "SMIMEA", T_SMIMEA, 4, 4, + { RDATA_WF_BYTE, /* usage */ + RDATA_WF_BYTE, /* selector */ + RDATA_WF_BYTE, /* matching type */ + RDATA_WF_BINARY }, /* certificate association data */ + { RDATA_ZF_BYTE, RDATA_ZF_BYTE, RDATA_ZF_BYTE, RDATA_ZF_HEX } }, /* 54 */ { 54, NULL, T_UTYPE, 1, 1, { RDATA_WF_BINARY }, { RDATA_ZF_UNKNOWN } }, /* 55 - HIP [RFC 5205] */ diff --git a/usr.sbin/nsd/dns.h b/usr.sbin/nsd/dns.h index 54dddefbfd9..7caea75eebe 100644 --- a/usr.sbin/nsd/dns.h +++ b/usr.sbin/nsd/dns.h @@ -136,6 +136,7 @@ typedef enum nsd_rc nsd_rc_type; #define TYPE_NSEC3 50 /* NSEC3, secure denial, prevents zonewalking */ #define TYPE_NSEC3PARAM 51 /* NSEC3PARAM at zone apex nsec3 parameters */ #define TYPE_TLSA 52 /* RFC 6698 */ +#define TYPE_SMIMEA 53 /* RFC 8162 */ #define TYPE_CDS 59 /* RFC 7344 */ #define TYPE_CDNSKEY 60 /* RFC 7344 */ #define TYPE_OPENPGPKEY 61 /* RFC 7929 */ diff --git a/usr.sbin/nsd/nsd-checkconf.8.in b/usr.sbin/nsd/nsd-checkconf.8.in index bc85aa865a6..c9e43529bc3 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" "Jun 11, 2018" "NLnet Labs" "nsd 4.1.22" +.TH "nsd\-checkconf" "8" "Aug 13, 2018" "NLnet Labs" "nsd 4.1.24" .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved. .\" See LICENSE for the license. .SH "NAME" diff --git a/usr.sbin/nsd/nsd-checkconf.c b/usr.sbin/nsd/nsd-checkconf.c index a31e3db0cec..41d520fa22f 100644 --- a/usr.sbin/nsd/nsd-checkconf.c +++ b/usr.sbin/nsd/nsd-checkconf.c @@ -364,6 +364,7 @@ config_print_zone(nsd_options_type* opt, const char* k, int s, const char *o, SERV_GET_BIN(do_ip4, o); SERV_GET_BIN(do_ip6, o); SERV_GET_BIN(reuseport, o); + SERV_GET_BIN(use_systemd, o); SERV_GET_BIN(hide_version, o); SERV_GET_BIN(zonefiles_check, o); SERV_GET_BIN(log_time_ascii, o); @@ -480,6 +481,7 @@ config_test_print_server(nsd_options_type* opt) printf("\tip-transparent: %s\n", opt->ip_transparent?"yes":"no"); printf("\tip-freebind: %s\n", opt->ip_freebind?"yes":"no"); printf("\treuseport: %s\n", opt->reuseport?"yes":"no"); + printf("\tuse-systemd: %s\n", opt->use_systemd?"yes":"no"); printf("\tdo-ip4: %s\n", opt->do_ip4?"yes":"no"); printf("\tdo-ip6: %s\n", opt->do_ip6?"yes":"no"); printf("\thide-version: %s\n", opt->hide_version?"yes":"no"); diff --git a/usr.sbin/nsd/nsd-checkzone.8.in b/usr.sbin/nsd/nsd-checkzone.8.in index a84afdba5a4..3e9d2e447de 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" "Jun 11, 2018" "NLnet Labs" "nsd 4.1.22" +.TH "nsd\-checkzone" "8" "Aug 13, 2018" "NLnet Labs" "nsd 4.1.24" .\" 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 fd0b2579e06..5041fba4e3d 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" "Jun 11, 2018" "NLnet Labs" "nsd 4.1.22" +.TH "nsd\-control" "8" "Aug 13, 2018" "NLnet Labs" "nsd 4.1.24" .\" Copyright (c) 2011, NLnet Labs. All rights reserved. .\" See LICENSE for the license. .SH "NAME" diff --git a/usr.sbin/nsd/nsd-control.c b/usr.sbin/nsd/nsd-control.c index da19f02abaa..0a10f652f41 100644 --- a/usr.sbin/nsd/nsd-control.c +++ b/usr.sbin/nsd/nsd-control.c @@ -56,6 +56,9 @@ #ifdef HAVE_OPENSSL_RAND_H #include #endif +#ifdef HAVE_SYS_UN_H +#include +#endif #include "util.h" #include "tsig.h" #include "options.h" @@ -111,6 +114,8 @@ setup_ctx(struct nsd_options* cfg) char* s_cert, *c_key, *c_cert; SSL_CTX* ctx; + if(!options_remote_is_address(cfg)) + return NULL; s_cert = cfg->server_cert_file; c_key = cfg->control_key_file; c_cert = cfg->control_cert_file; @@ -154,6 +159,7 @@ contact_server(const char* svr, struct nsd_options* cfg, int statuscmd) socklen_t addrlen; int fd; int port = cfg->control_port; + int addrfamily = 0; /* use svr or a config entry */ if(!svr) { if(cfg->control_interface) { @@ -181,7 +187,19 @@ contact_server(const char* svr, struct nsd_options* cfg, int statuscmd) exit(1); } } - if(strchr(svr, ':')) { + if(svr[0] == '/') { +#ifdef HAVE_SYS_UN_H + struct sockaddr_un* usock = (struct sockaddr_un *) &addr; + usock->sun_family = AF_LOCAL; +#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN + usock->sun_len = (unsigned)sizeof(usock); +#endif + (void)strlcpy(usock->sun_path, svr, sizeof(usock->sun_path)); + addrlen = (socklen_t)sizeof(struct sockaddr_un); + addrfamily = AF_LOCAL; + port = 0; +#endif + } else if(strchr(svr, ':')) { struct sockaddr_in6 sa; addrlen = (socklen_t)sizeof(struct sockaddr_in6); memset(&sa, 0, addrlen); @@ -192,6 +210,7 @@ contact_server(const char* svr, struct nsd_options* cfg, int statuscmd) exit(1); } memcpy(&addr, &sa, addrlen); + addrfamily = AF_INET6; } else { /* ip4 */ struct sockaddr_in sa; addrlen = (socklen_t)sizeof(struct sockaddr_in); @@ -203,17 +222,21 @@ contact_server(const char* svr, struct nsd_options* cfg, int statuscmd) exit(1); } memcpy(&addr, &sa, addrlen); + addrfamily = AF_INET; } - fd = socket(strchr(svr, ':')?AF_INET6:AF_INET, SOCK_STREAM, 0); + fd = socket(addrfamily, SOCK_STREAM, 0); if(fd == -1) { fprintf(stderr, "socket: %s\n", strerror(errno)); exit(1); } if(connect(fd, (struct sockaddr*)&addr, addrlen) < 0) { - fprintf(stderr, "error: connect (%s@%d): %s\n", svr, port, - strerror(errno)); - if(errno == ECONNREFUSED && statuscmd) { + int err = errno; + if(!port) fprintf(stderr, "error: connect (%s): %s\n", svr, + strerror(err)); + else fprintf(stderr, "error: connect (%s@%d): %s\n", svr, port, + strerror(err)); + if(err == ECONNREFUSED && statuscmd) { printf("nsd is stopped\n"); exit(3); } @@ -230,6 +253,7 @@ setup_ssl(SSL_CTX* ctx, int fd) X509* x; int r; + if(!ctx) return NULL; ssl = SSL_new(ctx); if(!ssl) ssl_err("could not SSL_new"); @@ -257,58 +281,93 @@ setup_ssl(SSL_CTX* ctx, int fd) return ssl; } +/** read from ssl or fd, fatalexit on error, 0 EOF, 1 success */ +static int +remote_read(SSL* ssl, int fd, char* buf, size_t len) +{ + if(ssl) { + int r; + ERR_clear_error(); + if((r = SSL_read(ssl, buf, (int)len-1)) <= 0) { + if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) { + /* EOF */ + return 0; + } + ssl_err("could not SSL_read"); + } + buf[r] = 0; + } else { + ssize_t rr = read(fd, buf, len-1); + if(rr <= 0) { + if(rr == 0) { + /* EOF */ + return 0; + } + fprintf(stderr, "could not read: %s\n", + strerror(errno)); + exit(1); + } + buf[rr] = 0; + } + return 1; +} + +/** write to ssl or fd, fatalexit on error */ +static void +remote_write(SSL* ssl, int fd, const char* buf, size_t len) +{ + if(ssl) { + if(SSL_write(ssl, buf, (int)len) <= 0) + ssl_err("could not SSL_write"); + } else { + if(write(fd, buf, len) < (ssize_t)len) { + fprintf(stderr, "could not write: %s\n", + strerror(errno)); + exit(1); + } + } +} + /** send stdin to server */ static void -send_file(SSL* ssl, FILE* in, char* buf, size_t sz) +send_file(SSL* ssl, int fd, FILE* in, char* buf, size_t sz) { char e[] = {0x04, 0x0a}; while(fgets(buf, (int)sz, in)) { - if(SSL_write(ssl, buf, (int)strlen(buf)) <= 0) - ssl_err("could not SSL_write contents"); + remote_write(ssl, fd, buf, strlen(buf)); } /* send end-of-file marker */ - if(SSL_write(ssl, e, (int)sizeof(e)) <= 0) - ssl_err("could not SSL_write end-of-file marker"); + remote_write(ssl, fd, e, sizeof(e)); } /** send command and display result */ static int -go_cmd(SSL* ssl, int argc, char* argv[]) +go_cmd(SSL* ssl, int fd, int argc, char* argv[]) { char pre[10]; const char* space=" "; const char* newline="\n"; int was_error = 0, first_line = 1; - int r, i; + int i; char buf[1024]; snprintf(pre, sizeof(pre), "NSDCT%d ", NSD_CONTROL_VERSION); - if(SSL_write(ssl, pre, (int)strlen(pre)) <= 0) - ssl_err("could not SSL_write"); + remote_write(ssl, fd, pre, strlen(pre)); for(i=0; iregion); return ret; } diff --git a/usr.sbin/nsd/nsd.8.in b/usr.sbin/nsd/nsd.8.in index a71212e09b6..7095c74c39f 100644 --- a/usr.sbin/nsd/nsd.8.in +++ b/usr.sbin/nsd/nsd.8.in @@ -1,9 +1,9 @@ -.TH "NSD" "8" "Jun 11, 2018" "NLnet Labs" "NSD 4.1.22" +.TH "NSD" "8" "Aug 13, 2018" "NLnet Labs" "NSD 4.1.24" .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved. .\" See LICENSE for the license. .SH "NAME" .B nsd -\- Name Server Daemon (NSD) version 4.1.22. +\- Name Server Daemon (NSD) version 4.1.24. .SH "SYNOPSIS" .B nsd .RB [ \-4 ] diff --git a/usr.sbin/nsd/nsd.conf.5.in b/usr.sbin/nsd/nsd.conf.5.in index 329de7a6646..df39b874641 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" "Jun 11, 2018" "NLnet Labs" "nsd 4.1.22" +.TH "nsd.conf" "5" "Aug 13, 2018" "NLnet Labs" "nsd 4.1.24" .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved. .\" See LICENSE for the license. .SH "NAME" @@ -189,6 +189,10 @@ If set to yes it does not fork and stays in the foreground, which can be helpful for commandline debugging, but is also used by certain server supervisor processes to ascertain that the server is running. .TP +.B use\-systemd: \fI +Enable or disable systemd readiness signalling. +Default is no. +.TP .B do\-ip4:\fR If yes, NSD listens to IPv4 connections. Default yes. .TP @@ -457,6 +461,13 @@ NSD will bind to the listed addresses to service control requests Use 0.0.0.0 and ::0 to service the wildcard interface. If none are given NSD listens to the localhost 127.0.0.1 and ::1 interfaces for control, if control is enabled with control\-enable. +.IP +With an absolute path, a unix local named pipe is used for control. The +file is created with user and group that is configured and access bits +are set to allow members of the group access. Further access can be +controlled by setting permissions on the directory containing the control +socket file. The key and cert files are not used when control is via the +named pipe, because access control is via file and directory permission. .TP .B control\-port:\fR The port number for remote control service. 8952 by default. diff --git a/usr.sbin/nsd/nsd.conf.sample.in b/usr.sbin/nsd/nsd.conf.sample.in index 7cb2e719b46..187e422448e 100644 --- a/usr.sbin/nsd/nsd.conf.sample.in +++ b/usr.sbin/nsd/nsd.conf.sample.in @@ -36,6 +36,9 @@ server: # enable debug mode, does not fork daemon process into the background. # debug-mode: no + # use systemd for readiness signalling. + # use-systemd: no + # listen on IPv4 connections # do-ip4: yes @@ -184,6 +187,8 @@ remote-control: # control-enable: no # what interfaces are listened to for control, default is on localhost. + # with an absolute path, a unix local named pipe is used for control + # (and key and cert files are not needed, use directory permissions). # control-interface: 127.0.0.1 # control-interface: ::1 diff --git a/usr.sbin/nsd/nsec3.c b/usr.sbin/nsd/nsec3.c index e1fe4ae8d4f..5e66d40104b 100644 --- a/usr.sbin/nsd/nsec3.c +++ b/usr.sbin/nsd/nsec3.c @@ -181,7 +181,7 @@ nsec3_has_soa(rr_type* rr) } static rr_type* -check_apex_soa(namedb_type* namedb, zone_type *zone) +check_apex_soa(namedb_type* namedb, zone_type *zone, int nolog) { uint8_t h[NSEC3_HASH_LEN]; domain_type* domain; @@ -195,19 +195,23 @@ check_apex_soa(namedb_type* namedb, zone_type *zone) hashed_apex = nsec3_b32_create(tmpregion, zone, h); domain = domain_table_find(namedb->domains, hashed_apex); if(!domain) { - log_msg(LOG_ERR, "%s NSEC3PARAM entry has no hash(apex).", - domain_to_string(zone->apex)); - log_msg(LOG_ERR, "hash(apex)= %s", - dname_to_string(hashed_apex, NULL)); + if(!nolog) { + log_msg(LOG_ERR, "%s NSEC3PARAM entry has no hash(apex).", + domain_to_string(zone->apex)); + log_msg(LOG_ERR, "hash(apex)= %s", + dname_to_string(hashed_apex, NULL)); + } region_destroy(tmpregion); return NULL; } nsec3_rrset = domain_find_rrset(domain, zone, TYPE_NSEC3); if(!nsec3_rrset) { - log_msg(LOG_ERR, "%s NSEC3PARAM entry: hash(apex) has no NSEC3 RRset.", - domain_to_string(zone->apex)); - log_msg(LOG_ERR, "hash(apex)= %s", - dname_to_string(hashed_apex, NULL)); + if(!nolog) { + log_msg(LOG_ERR, "%s NSEC3PARAM entry: hash(apex) has no NSEC3 RRset.", + domain_to_string(zone->apex)); + log_msg(LOG_ERR, "hash(apex)= %s", + dname_to_string(hashed_apex, NULL)); + } region_destroy(tmpregion); return NULL; } @@ -217,16 +221,78 @@ check_apex_soa(namedb_type* namedb, zone_type *zone) return &nsec3_rrset->rrs[j]; } } - log_msg(LOG_ERR, "%s NSEC3PARAM entry: hash(apex) NSEC3 has no SOA flag.", - domain_to_string(zone->apex)); - log_msg(LOG_ERR, "hash(apex)= %s", - dname_to_string(hashed_apex, NULL)); + if(!nolog) { + log_msg(LOG_ERR, "%s NSEC3PARAM entry: hash(apex) NSEC3 has no SOA flag.", + domain_to_string(zone->apex)); + log_msg(LOG_ERR, "hash(apex)= %s", + dname_to_string(hashed_apex, NULL)); + } region_destroy(tmpregion); return NULL; } +static void +nsec3param_to_str(struct rr* rr, char* str, size_t buflen) +{ + rdata_atom_type* rd = rr->rdatas; + size_t len; + len = snprintf(str, buflen, "%u %u %u ", + (unsigned)rdata_atom_data(rd[0])[0], + (unsigned)rdata_atom_data(rd[1])[0], + (unsigned)read_uint16(rdata_atom_data(rd[2]))); + if(rdata_atom_data(rd[3])[0] == 0) { + if(buflen > len + 2) + str[len++] = '-'; + } else { + len += hex_ntop(rdata_atom_data(rd[3])+1, + rdata_atom_data(rd[3])[0], str+len, buflen-len-1); + } + if(buflen > len + 1) + str[len] = 0; +} + static struct rr* -udb_zone_find_nsec3param(udb_base* udb, udb_ptr* uz, struct zone* z) +db_find_nsec3param(struct namedb* db, struct zone* z, struct rr* avoid_rr, + int checkchain) +{ + unsigned i; + rrset_type* rrset = domain_find_rrset(z->apex, z, TYPE_NSEC3PARAM); + if(!rrset) /* no NSEC3PARAM in mem */ + return NULL; + /* find first nsec3param we can support (SHA1, no flags) */ + for(i=0; irr_count; i++) { + rdata_atom_type* rd = rrset->rrs[i].rdatas; + /* do not use the RR that is going to be deleted (in IXFR) */ + if(&rrset->rrs[i] == avoid_rr) continue; + if(rrset->rrs[i].rdata_count < 4) continue; + if(rdata_atom_data(rd[0])[0] == NSEC3_SHA1_HASH && + rdata_atom_data(rd[1])[0] == 0) { + if(checkchain) { + z->nsec3_param = &rrset->rrs[i]; + if(!check_apex_soa(db, z, 1)) { + char str[MAX_RDLENGTH*2+16]; + nsec3param_to_str(z->nsec3_param, + str, sizeof(str)); + VERBOSITY(1, (LOG_WARNING, "zone %s NSEC3PARAM %s has broken chain, ignoring", domain_to_string(z->apex), str)); + continue; /* don't use broken chain */ + } + } + if(2 <= verbosity) { + char str[MAX_RDLENGTH*2+16]; + nsec3param_to_str(&rrset->rrs[i], str, + sizeof(str)); + VERBOSITY(2, (LOG_INFO, "rehash of zone %s with parameters %s", + domain_to_string(z->apex), str)); + } + return &rrset->rrs[i]; + } + } + return NULL; +} + +static struct rr* +udb_zone_find_nsec3param(struct namedb* db, udb_base* udb, udb_ptr* uz, + struct zone* z, int checkchain) { udb_ptr urr; unsigned i; @@ -253,62 +319,31 @@ udb_zone_find_nsec3param(udb_base* udb, udb_ptr* uz, struct zone* z) memcmp(RR(&urr)->wire+5, rdata_atom_data(rd[3])+1, rdata_atom_data(rd[3])[0]) == 0) { udb_ptr_unlink(&urr, udb); - return &rrset->rrs[i]; - } - } - udb_ptr_unlink(&urr, udb); - return NULL; -} - -static struct rr* -db_find_nsec3param(struct zone* z, struct rr* avoid_rr) -{ - unsigned i; - rrset_type* rrset = domain_find_rrset(z->apex, z, TYPE_NSEC3PARAM); - if(!rrset) /* no NSEC3PARAM in mem */ - return NULL; - /* find first nsec3param we can support (SHA1, no flags) */ - for(i=0; irr_count; i++) { - rdata_atom_type* rd = rrset->rrs[i].rdatas; - /* do not use the RR that is going to be deleted (in IXFR) */ - if(&rrset->rrs[i] == avoid_rr) continue; - if(rrset->rrs[i].rdata_count < 4) continue; - if(rdata_atom_data(rd[0])[0] == NSEC3_SHA1_HASH && - rdata_atom_data(rd[1])[0] == 0) { - if(2 <= verbosity) { - char str[MAX_RDLENGTH*2+16]; - char* p; - p = str+snprintf(str, sizeof(str), "%u %u %u ", - (unsigned)rdata_atom_data(rd[0])[0], - (unsigned)rdata_atom_data(rd[1])[0], - (unsigned)read_uint16(rdata_atom_data(rd[2]))); - if(rdata_atom_data(rd[3])[0] == 0) - *p++ = '-'; - else { - p += hex_ntop(rdata_atom_data(rd[3])+1, - rdata_atom_data(rd[3])[0], p, - sizeof(str)-strlen(str)-1); - } - *p = 0; - VERBOSITY(2, (LOG_INFO, "rehash of zone %s with parameters %s", - domain_to_string(z->apex), str)); + if(checkchain) { + z->nsec3_param = &rrset->rrs[i]; + if(!check_apex_soa(db, z, 1)) + return db_find_nsec3param(db, z, + NULL, checkchain); } return &rrset->rrs[i]; } } + udb_ptr_unlink(&urr, udb); return NULL; } void nsec3_find_zone_param(struct namedb* db, struct zone* zone, udb_ptr* z, - struct rr* avoid_rr) + struct rr* avoid_rr, int checkchain) { /* get nsec3param RR from udb */ if(db->udb) - zone->nsec3_param = udb_zone_find_nsec3param(db->udb, z, zone); + zone->nsec3_param = udb_zone_find_nsec3param(db, db->udb, + z, zone, checkchain); /* no db, get from memory, avoid using the rr that is going to be * deleted, avoid_rr */ - else zone->nsec3_param = db_find_nsec3param(zone, avoid_rr); + else zone->nsec3_param = db_find_nsec3param(db, zone, avoid_rr, + checkchain); } /* check params ok for one RR */ @@ -646,8 +681,8 @@ prehash_zone_complete(struct namedb* db, struct zone* zone) udb_ptr_init(&udbz, db->udb); /* zero the ptr */ } } - nsec3_find_zone_param(db, zone, &udbz, NULL); - if(!zone->nsec3_param || !check_apex_soa(db, zone)) { + nsec3_find_zone_param(db, zone, &udbz, NULL, 1); + if(!zone->nsec3_param || !check_apex_soa(db, zone, 0)) { zone->nsec3_param = NULL; zone->nsec3_last = NULL; if(db->udb) @@ -844,6 +879,13 @@ void prehash_zone(struct namedb* db, struct zone* zone) prehash_clear(db->domains); return; } + if(!check_apex_soa(db, zone, 1)) { + /* the zone fails apex soa check, prehash complete may + * detect other valid chains */ + prehash_clear(db->domains); + prehash_zone_complete(db, zone); + return; + } /* process prehash list */ for(d = db->domains->prehash_list; d; d = d->nsec3->prehash_next) { process_prehash_domain(d, zone); @@ -851,7 +893,7 @@ void prehash_zone(struct namedb* db, struct zone* zone) /* clear prehash list */ prehash_clear(db->domains); - if(!check_apex_soa(db, zone)) { + if(!check_apex_soa(db, zone, 0)) { zone->nsec3_param = NULL; zone->nsec3_last = NULL; } diff --git a/usr.sbin/nsd/nsec3.h b/usr.sbin/nsd/nsec3.h index 663776f220b..c4283999144 100644 --- a/usr.sbin/nsd/nsec3.h +++ b/usr.sbin/nsd/nsec3.h @@ -92,7 +92,7 @@ int nsec3_condition_hash(struct domain* d, struct zone* z); int nsec3_condition_dshash(struct domain* d, struct zone* z); /* set nsec3param for this zone or NULL if no NSEC3 available */ void nsec3_find_zone_param(struct namedb* db, struct zone* zone, - struct udb_ptr* z, struct rr* avoid_rr); + struct udb_ptr* z, struct rr* avoid_rr, int checkchain); /* hash domain and wcchild, and lookup nsec3 in tree, and precompile */ void nsec3_precompile_domain(struct namedb* db, struct domain* domain, struct zone* zone, struct region* tmpregion); diff --git a/usr.sbin/nsd/options.c b/usr.sbin/nsd/options.c index a74218b345c..eaca70890cb 100644 --- a/usr.sbin/nsd/options.c +++ b/usr.sbin/nsd/options.c @@ -78,6 +78,7 @@ nsd_options_create(region_type* region) opt->port = UDP_PORT; /* deprecated? opt->port = TCP_PORT; */ opt->reuseport = 0; + opt->use_systemd = 0; opt->statistics = 0; opt->chroot = 0; opt->username = USER; @@ -2050,3 +2051,15 @@ unsigned getzonestatid(struct nsd_options* opt, struct zone_options* zopt) return 0; #endif /* USE_ZONE_STATS */ } + +/** check if config turns on IP-address interface with certificates or a + * named pipe without certificates. */ +int +options_remote_is_address(struct nsd_options* cfg) +{ + if(!cfg->control_enable) return 0; + if(!cfg->control_interface) return 1; + if(!cfg->control_interface->address) return 1; + if(cfg->control_interface->address[0] == 0) return 1; + return (cfg->control_interface->address[0] != '/'); +} diff --git a/usr.sbin/nsd/options.h b/usr.sbin/nsd/options.h index fddfa8d4239..1fef3102813 100644 --- a/usr.sbin/nsd/options.h +++ b/usr.sbin/nsd/options.h @@ -96,6 +96,7 @@ struct nsd_options { int minimal_responses; int refuse_any; int reuseport; + int use_systemd; /** remote control section. enable toggle. */ int control_enable; @@ -341,6 +342,10 @@ unsigned getzonestatid(struct nsd_options* opt, struct zone_options* zopt); /* create string, same options as zonefile but no chroot changes */ const char* config_cook_string(struct zone_options* zone, const char* input); +/** check if config for remote control turns on IP-address interface + * with certificates or a named pipe without certificates. */ +int options_remote_is_address(struct nsd_options* cfg); + #if defined(HAVE_SSL) /* tsig must be inited, adds all keys in options to tsig. */ void key_options_tsig_add(struct nsd_options* opt); diff --git a/usr.sbin/nsd/remote.c b/usr.sbin/nsd/remote.c index 73b774275d2..a9d24b01180 100644 --- a/usr.sbin/nsd/remote.c +++ b/usr.sbin/nsd/remote.c @@ -82,8 +82,14 @@ #ifdef HAVE_SYS_TYPES_H # include #endif +#ifdef HAVE_SYS_STAT_H +# include +#endif #ifdef HAVE_NETDB_H -#include +# include +#endif +#ifdef HAVE_SYS_UN_H +# include #endif /** number of seconds timeout on incoming remote control handshake */ @@ -114,6 +120,8 @@ struct rc_state { enum { rc_none, rc_hs_read, rc_hs_write } shake_state; /** the ssl state */ SSL* ssl; + /** file descriptor */ + int fd; /** the rc this is part of */ struct daemon_remote* rc; /** stats list next item */ @@ -140,6 +148,8 @@ struct daemon_remote { struct xfrd_state* xfrd; /** commpoints for accepting remote control connections */ struct acceptlist* accept_list; + /* if certificates are used */ + int use_cert; /** number of active commpoints that are handling remote control */ int active; /** max active commpoints */ @@ -154,32 +164,43 @@ struct daemon_remote { SSL_CTX* ctx; }; +/** + * Connection to print to, either SSL or plain over fd + */ +struct remote_stream { + /** SSL structure, nonNULL if using SSL */ + SSL* ssl; + /** file descriptor for plain transfer */ + int fd; +}; +typedef struct remote_stream RES; + /** * Print fixed line of text over ssl connection in blocking mode - * @param ssl: print to + * @param res: print to * @param text: the text. * @return false on connection failure. */ -static int ssl_print_text(SSL* ssl, const char* text); +static int ssl_print_text(RES* res, const char* text); /** * printf style printing to the ssl connection - * @param ssl: the SSL connection to print to. Blocking. + * @param res: the RES connection to print to. Blocking. * @param format: printf style format string. * @return success or false on a network failure. */ -static int ssl_printf(SSL* ssl, const char* format, ...) +static int ssl_printf(RES* res, const char* format, ...) ATTR_FORMAT(printf, 2, 3); /** * Read until \n is encountered - * If SSL signals EOF, the string up to then is returned (without \n). - * @param ssl: the SSL connection to read from. blocking. + * If stream signals EOF, the string up to then is returned (without \n). + * @param res: the RES connection to read from. blocking. * @param buf: buffer to read to. * @param max: size of buffer. * @return false on connection failure. */ -static int ssl_read_line(SSL* ssl, char* buf, size_t max); +static int ssl_read_line(RES* res, char* buf, size_t max); /** perform the accept of a new remote control connection */ static void @@ -226,11 +247,57 @@ timeval_subtract(struct timeval* d, const struct timeval* end, } #endif /* BIND8_STATS */ -struct daemon_remote* -daemon_remote_create(struct nsd_options* cfg) +static int +remote_setup_ctx(struct daemon_remote* rc, struct nsd_options* cfg) { char* s_cert; char* s_key; + rc->ctx = SSL_CTX_new(SSLv23_server_method()); + if(!rc->ctx) { + log_crypto_err("could not SSL_CTX_new"); + return 0; + } + /* no SSLv2, SSLv3 because has defects */ + if((SSL_CTX_set_options(rc->ctx, SSL_OP_NO_SSLv2) & SSL_OP_NO_SSLv2) + != SSL_OP_NO_SSLv2){ + log_crypto_err("could not set SSL_OP_NO_SSLv2"); + return 0; + } + if((SSL_CTX_set_options(rc->ctx, SSL_OP_NO_SSLv3) & SSL_OP_NO_SSLv3) + != SSL_OP_NO_SSLv3){ + log_crypto_err("could not set SSL_OP_NO_SSLv3"); + return 0; + } + s_cert = cfg->server_cert_file; + s_key = cfg->server_key_file; + VERBOSITY(2, (LOG_INFO, "setup SSL certificates")); + if (!SSL_CTX_use_certificate_file(rc->ctx,s_cert,SSL_FILETYPE_PEM)) { + log_msg(LOG_ERR, "Error for server-cert-file: %s", s_cert); + log_crypto_err("Error in SSL_CTX use_certificate_file"); + return 0; + } + if(!SSL_CTX_use_PrivateKey_file(rc->ctx,s_key,SSL_FILETYPE_PEM)) { + log_msg(LOG_ERR, "Error for server-key-file: %s", s_key); + log_crypto_err("Error in SSL_CTX use_PrivateKey_file"); + return 0; + } + if(!SSL_CTX_check_private_key(rc->ctx)) { + log_msg(LOG_ERR, "Error for server-key-file: %s", s_key); + log_crypto_err("Error in SSL_CTX check_private_key"); + return 0; + } + if(!SSL_CTX_load_verify_locations(rc->ctx, s_cert, NULL)) { + log_crypto_err("Error setting up SSL_CTX verify locations"); + return 0; + } + SSL_CTX_set_client_CA_list(rc->ctx, SSL_load_client_CA_file(s_cert)); + SSL_CTX_set_verify(rc->ctx, SSL_VERIFY_PEER, NULL); + return 1; +} + +struct daemon_remote* +daemon_remote_create(struct nsd_options* cfg) +{ struct daemon_remote* rc = (struct daemon_remote*)xalloc_zero( sizeof(*rc)); rc->max_active = 10; @@ -268,56 +335,27 @@ daemon_remote_create(struct nsd_options* cfg) log_msg(LOG_WARNING, "warning: no entropy, seeding openssl PRNG with time"); } - rc->ctx = SSL_CTX_new(SSLv23_server_method()); - if(!rc->ctx) { - log_crypto_err("could not SSL_CTX_new"); - free(rc); - return NULL; - } - /* no SSLv2, SSLv3 because has defects */ - if((SSL_CTX_set_options(rc->ctx, SSL_OP_NO_SSLv2) & SSL_OP_NO_SSLv2) - != SSL_OP_NO_SSLv2){ - log_crypto_err("could not set SSL_OP_NO_SSLv2"); - daemon_remote_delete(rc); - return NULL; - } - if((SSL_CTX_set_options(rc->ctx, SSL_OP_NO_SSLv3) & SSL_OP_NO_SSLv3) - != SSL_OP_NO_SSLv3){ - log_crypto_err("could not set SSL_OP_NO_SSLv3"); - daemon_remote_delete(rc); - return NULL; - } - s_cert = cfg->server_cert_file; - s_key = cfg->server_key_file; - VERBOSITY(2, (LOG_INFO, "setup SSL certificates")); - if (!SSL_CTX_use_certificate_file(rc->ctx,s_cert,SSL_FILETYPE_PEM)) { - log_msg(LOG_ERR, "Error for server-cert-file: %s", s_cert); - log_crypto_err("Error in SSL_CTX use_certificate_file"); - goto setup_error; - } - if(!SSL_CTX_use_PrivateKey_file(rc->ctx,s_key,SSL_FILETYPE_PEM)) { - log_msg(LOG_ERR, "Error for server-key-file: %s", s_key); - log_crypto_err("Error in SSL_CTX use_PrivateKey_file"); - goto setup_error; - } - if(!SSL_CTX_check_private_key(rc->ctx)) { - log_msg(LOG_ERR, "Error for server-key-file: %s", s_key); - log_crypto_err("Error in SSL_CTX check_private_key"); - goto setup_error; - } - if(!SSL_CTX_load_verify_locations(rc->ctx, s_cert, NULL)) { - log_crypto_err("Error setting up SSL_CTX verify locations"); - setup_error: - daemon_remote_delete(rc); - return NULL; + if(options_remote_is_address(cfg)) { + if(!remote_setup_ctx(rc, cfg)) { + daemon_remote_delete(rc); + return NULL; + } + rc->use_cert = 1; + } else { + struct ip_address_option* o; + rc->ctx = NULL; + rc->use_cert = 0; + for(o = cfg->control_interface; o; o = o->next) { + if(o->address && o->address[0] != '/') + log_msg(LOG_WARNING, "control-interface %s is not using TLS, but plain transfer, because first control-interface in config file is a local socket (starts with a /).", o->address); + } } - SSL_CTX_set_client_CA_list(rc->ctx, SSL_load_client_CA_file(s_cert)); - SSL_CTX_set_verify(rc->ctx, SSL_VERIFY_PEER, NULL); /* and try to open the ports */ if(!daemon_remote_open_ports(rc, cfg)) { log_msg(LOG_ERR, "could not open remote control port"); - goto setup_error; + daemon_remote_delete(rc); + return NULL; } if(gettimeofday(&rc->boot_time, NULL) == -1) @@ -432,7 +470,8 @@ create_tcp_accept_sock(struct addrinfo* addr, int* noproto) * @return false on failure. */ static int -add_open(struct daemon_remote* rc, const char* ip, int nr, int noproto_is_err) +add_open(struct daemon_remote* rc, struct nsd_options* cfg, const char* ip, + int nr, int noproto_is_err) { struct addrinfo hints; struct addrinfo* res; @@ -443,23 +482,49 @@ add_open(struct daemon_remote* rc, const char* ip, int nr, int noproto_is_err) snprintf(port, sizeof(port), "%d", nr); port[sizeof(port)-1]=0; memset(&hints, 0, sizeof(hints)); - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; - if((r = getaddrinfo(ip, port, &hints, &res)) != 0 || !res) { - log_msg(LOG_ERR, "control interface %s:%s getaddrinfo: %s %s", - ip?ip:"default", port, gai_strerror(r), + + if(ip[0] == '/') { + /* This looks like a local socket */ + fd = create_local_accept_sock(ip, &noproto); + /* + * Change socket ownership and permissions so users other + * than root can access it provided they are in the same + * group as the user we run as. + */ + if(fd != -1) { +#ifdef HAVE_CHOWN + if (cfg->username && cfg->username[0] && + nsd.uid != (uid_t)-1) { + if(chown(ip, nsd.uid, nsd.gid) == -1) + VERBOSITY(2, (LOG_INFO, "cannot chown %u.%u %s: %s", + (unsigned)nsd.uid, (unsigned)nsd.gid, + ip, strerror(errno))); + } + chmod(ip, (mode_t)(S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)); +#else + (void)cfg; +#endif + } + } else { + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; + if((r = getaddrinfo(ip, port, &hints, &res)) != 0 || !res) { + log_msg(LOG_ERR, "control interface %s:%s getaddrinfo: %s %s", + ip?ip:"default", port, gai_strerror(r), #ifdef EAI_SYSTEM - r==EAI_SYSTEM?(char*)strerror(errno):"" + r==EAI_SYSTEM?(char*)strerror(errno):"" #else - "" + "" #endif - ); - return 0; + ); + return 0; + } + + /* open fd */ + fd = create_tcp_accept_sock(res, &noproto); + freeaddrinfo(res); } - /* open fd */ - fd = create_tcp_accept_sock(res, &noproto); - freeaddrinfo(res); if(fd == -1 && noproto) { if(!noproto_is_err) return 1; /* return success, but do nothing */ @@ -489,17 +554,17 @@ daemon_remote_open_ports(struct daemon_remote* rc, struct nsd_options* cfg) if(cfg->control_interface) { ip_address_option_type* p; for(p = cfg->control_interface; p; p = p->next) { - if(!add_open(rc, p->address, cfg->control_port, 1)) { + if(!add_open(rc, cfg, p->address, cfg->control_port, 1)) { return 0; } } } else { /* defaults */ - if(cfg->do_ip6 && !add_open(rc, "::1", cfg->control_port, 0)) { + if(cfg->do_ip6 && !add_open(rc, cfg, "::1", cfg->control_port, 0)) { return 0; } if(cfg->do_ip4 && - !add_open(rc, "127.0.0.1", cfg->control_port, 1)) { + !add_open(rc, cfg, "127.0.0.1", cfg->control_port, 1)) { return 0; } } @@ -590,6 +655,7 @@ remote_accept_callback(int fd, short event, void* arg) n->tval.tv_sec = REMOTE_CONTROL_TCP_TIMEOUT; n->tval.tv_usec = 0L; + n->fd = newfd; event_set(&n->c, newfd, EV_PERSIST|EV_TIMEOUT|EV_READ, remote_control_callback, n); @@ -611,22 +677,26 @@ remote_accept_callback(int fd, short event, void* arg) VERBOSITY(2, (LOG_INFO, "new control connection from %s", s)); } - n->shake_state = rc_hs_read; - n->ssl = SSL_new(rc->ctx); - if(!n->ssl) { - log_crypto_err("could not SSL_new"); - event_del(&n->c); - free(n); - goto close_exit; - } - SSL_set_accept_state(n->ssl); - (void)SSL_set_mode(n->ssl, SSL_MODE_AUTO_RETRY); - if(!SSL_set_fd(n->ssl, newfd)) { - log_crypto_err("could not SSL_set_fd"); - event_del(&n->c); - SSL_free(n->ssl); - free(n); - goto close_exit; + if(rc->ctx) { + n->shake_state = rc_hs_read; + n->ssl = SSL_new(rc->ctx); + if(!n->ssl) { + log_crypto_err("could not SSL_new"); + event_del(&n->c); + free(n); + goto close_exit; + } + SSL_set_accept_state(n->ssl); + (void)SSL_set_mode(n->ssl, SSL_MODE_AUTO_RETRY); + if(!SSL_set_fd(n->ssl, newfd)) { + log_crypto_err("could not SSL_set_fd"); + event_del(&n->c); + SSL_free(n->ssl); + free(n); + goto close_exit; + } + } else { + n->ssl = NULL; } n->rc = rc; @@ -684,27 +754,35 @@ clean_point(struct daemon_remote* rc, struct rc_state* s) } static int -ssl_print_text(SSL* ssl, const char* text) +ssl_print_text(RES* res, const char* text) { int r; - if(!ssl) + if(!res) return 0; - ERR_clear_error(); - if((r=SSL_write(ssl, text, (int)strlen(text))) <= 0) { - if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) { - VERBOSITY(2, (LOG_WARNING, "in SSL_write, peer " - "closed connection")); + if(res->ssl) { + ERR_clear_error(); + if((r=SSL_write(res->ssl, text, (int)strlen(text))) <= 0) { + if(SSL_get_error(res->ssl, r) == SSL_ERROR_ZERO_RETURN) { + VERBOSITY(2, (LOG_WARNING, "in SSL_write, peer " + "closed connection")); + return 0; + } + log_crypto_err("could not SSL_write"); + return 0; + } + } else { + if(write_socket(res->fd, text, strlen(text)) <= 0) { + log_msg(LOG_ERR, "could not write: %s", + strerror(errno)); return 0; } - log_crypto_err("could not SSL_write"); - return 0; } return 1; } /** print text over the ssl connection */ static int -ssl_print_vmsg(SSL* ssl, const char* format, va_list args) +ssl_print_vmsg(RES* ssl, const char* format, va_list args) { char msg[1024]; vsnprintf(msg, sizeof(msg), format, args); @@ -713,7 +791,7 @@ ssl_print_vmsg(SSL* ssl, const char* format, va_list args) /** printf style printing to the ssl connection */ static int -ssl_printf(SSL* ssl, const char* format, ...) +ssl_printf(RES* ssl, const char* format, ...) { va_list args; int ret; @@ -724,21 +802,39 @@ ssl_printf(SSL* ssl, const char* format, ...) } static int -ssl_read_line(SSL* ssl, char* buf, size_t max) +ssl_read_line(RES* res, char* buf, size_t max) { int r; size_t len = 0; - if(!ssl) + if(!res) return 0; while(len < max) { - ERR_clear_error(); - if((r=SSL_read(ssl, buf+len, 1)) <= 0) { - if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) { - buf[len] = 0; - return 1; + if(res->ssl) { + ERR_clear_error(); + if((r=SSL_read(res->ssl, buf+len, 1)) <= 0) { + if(SSL_get_error(res->ssl, r) == SSL_ERROR_ZERO_RETURN) { + buf[len] = 0; + return 1; + } + log_crypto_err("could not SSL_read"); + return 0; + } + } else { + while(1) { + ssize_t rr = read(res->fd, buf+len, 1); + if(rr <= 0) { + if(rr == 0) { + buf[len] = 0; + return 1; + } + if(errno == EINTR || errno == EAGAIN) + continue; + log_msg(LOG_ERR, "could not read: %s", + strerror(errno)); + return 0; + } + break; } - log_crypto_err("could not SSL_read"); - return 0; } if(buf[len] == '\n') { /* return string without \n */ @@ -764,14 +860,14 @@ skipwhite(char* str) /** send the OK to the control client */ static void -send_ok(SSL* ssl) +send_ok(RES* ssl) { (void)ssl_printf(ssl, "ok\n"); } /** get zone argument (if any) or NULL, false on error */ static int -get_zone_arg(SSL* ssl, xfrd_state_type* xfrd, char* arg, +get_zone_arg(RES* ssl, xfrd_state_type* xfrd, char* arg, struct zone_options** zo) { const dname_type* dname; @@ -797,7 +893,7 @@ get_zone_arg(SSL* ssl, xfrd_state_type* xfrd, char* arg, /** do the stop command */ static void -do_stop(SSL* ssl, xfrd_state_type* xfrd) +do_stop(RES* ssl, xfrd_state_type* xfrd) { xfrd->need_to_send_shutdown = 1; @@ -810,7 +906,7 @@ do_stop(SSL* ssl, xfrd_state_type* xfrd) /** do the log_reopen command, it only needs reload_now */ static void -do_log_reopen(SSL* ssl, xfrd_state_type* xfrd) +do_log_reopen(RES* ssl, xfrd_state_type* xfrd) { xfrd_set_reload_now(xfrd); send_ok(ssl); @@ -818,7 +914,7 @@ do_log_reopen(SSL* ssl, xfrd_state_type* xfrd) /** do the reload command */ static void -do_reload(SSL* ssl, xfrd_state_type* xfrd, char* arg) +do_reload(RES* ssl, xfrd_state_type* xfrd, char* arg) { struct zone_options* zo; if(!get_zone_arg(ssl, xfrd, arg, &zo)) @@ -831,7 +927,7 @@ do_reload(SSL* ssl, xfrd_state_type* xfrd, char* arg) /** do the write command */ static void -do_write(SSL* ssl, xfrd_state_type* xfrd, char* arg) +do_write(RES* ssl, xfrd_state_type* xfrd, char* arg) { struct zone_options* zo; if(!get_zone_arg(ssl, xfrd, arg, &zo)) @@ -844,7 +940,7 @@ do_write(SSL* ssl, xfrd_state_type* xfrd, char* arg) /** do the notify command */ static void -do_notify(SSL* ssl, xfrd_state_type* xfrd, char* arg) +do_notify(RES* ssl, xfrd_state_type* xfrd, char* arg) { struct zone_options* zo; if(!get_zone_arg(ssl, xfrd, arg, &zo)) @@ -869,7 +965,7 @@ do_notify(SSL* ssl, xfrd_state_type* xfrd, char* arg) /** do the transfer command */ static void -do_transfer(SSL* ssl, xfrd_state_type* xfrd, char* arg) +do_transfer(RES* ssl, xfrd_state_type* xfrd, char* arg) { struct zone_options* zo; xfrd_zone_type* zone; @@ -888,7 +984,7 @@ do_transfer(SSL* ssl, xfrd_state_type* xfrd, char* arg) RBTREE_FOR(zone, xfrd_zone_type*, xfrd->zones) { xfrd_handle_notify_and_start_xfr(zone, NULL); } - ssl_printf(ssl, "ok, %u zones\n", (unsigned)xfrd->zones->count); + ssl_printf(ssl, "ok, %lu zones\n", (unsigned long)xfrd->zones->count); } } @@ -910,7 +1006,7 @@ force_transfer_zone(xfrd_zone_type* zone) /** do the force transfer command */ static void -do_force_transfer(SSL* ssl, xfrd_state_type* xfrd, char* arg) +do_force_transfer(RES* ssl, xfrd_state_type* xfrd, char* arg) { struct zone_options* zo; xfrd_zone_type* zone; @@ -929,12 +1025,12 @@ do_force_transfer(SSL* ssl, xfrd_state_type* xfrd, char* arg) RBTREE_FOR(zone, xfrd_zone_type*, xfrd->zones) { force_transfer_zone(zone); } - ssl_printf(ssl, "ok, %u zones\n", (unsigned)xfrd->zones->count); + ssl_printf(ssl, "ok, %lu zones\n", (unsigned long)xfrd->zones->count); } } static int -print_soa_status(SSL* ssl, const char* str, xfrd_soa_type* soa, time_t acq) +print_soa_status(RES* ssl, const char* str, xfrd_soa_type* soa, time_t acq) { if(acq) { if(!ssl_printf(ssl, " %s: \"%u since %s\"\n", str, @@ -949,7 +1045,7 @@ print_soa_status(SSL* ssl, const char* str, xfrd_soa_type* soa, time_t acq) /** print zonestatus for one domain */ static int -print_zonestatus(SSL* ssl, xfrd_state_type* xfrd, struct zone_options* zo) +print_zonestatus(RES* ssl, xfrd_state_type* xfrd, struct zone_options* zo) { xfrd_zone_type* xz = (xfrd_zone_type*)rbtree_search(xfrd->zones, (const dname_type*)zo->node.key); @@ -1000,8 +1096,8 @@ print_zonestatus(SSL* ssl, xfrd_state_type* xfrd, struct zone_options* zo) xz->soa_notified_acquired)) return 0; } else if(xz->event_added) { - if(!ssl_printf(ssl, "\twait: \"%u sec between attempts\"\n", - (unsigned)xz->timeout.tv_sec)) + if(!ssl_printf(ssl, "\twait: \"%lu sec between attempts\"\n", + (unsigned long)xz->timeout.tv_sec)) return 0; } @@ -1030,7 +1126,7 @@ print_zonestatus(SSL* ssl, xfrd_state_type* xfrd, struct zone_options* zo) /** do the zonestatus command */ static void -do_zonestatus(SSL* ssl, xfrd_state_type* xfrd, char* arg) +do_zonestatus(RES* ssl, xfrd_state_type* xfrd, char* arg) { struct zone_options* zo; if(!get_zone_arg(ssl, xfrd, arg, &zo)) @@ -1047,7 +1143,7 @@ do_zonestatus(SSL* ssl, xfrd_state_type* xfrd, char* arg) /** do the verbosity command */ static void -do_verbosity(SSL* ssl, char* str) +do_verbosity(RES* ssl, char* str) { int val = atoi(str); if(strcmp(str, "") == 0) { @@ -1067,7 +1163,7 @@ do_verbosity(SSL* ssl, char* str) /** find second argument, modifies string */ static int -find_arg2(SSL* ssl, char* arg, char** arg2) +find_arg2(RES* ssl, char* arg, char** arg2) { char* as = strrchr(arg, ' '); if(as) { @@ -1085,7 +1181,7 @@ find_arg2(SSL* ssl, char* arg, char** arg2) /** do the status command */ static void -do_status(SSL* ssl, xfrd_state_type* xfrd) +do_status(RES* ssl, xfrd_state_type* xfrd) { if(!ssl_printf(ssl, "version: %s\n", PACKAGE_VERSION)) return; @@ -1139,7 +1235,7 @@ zonestat_inc_ifneeded(xfrd_state_type* xfrd) /** perform the addzone command for one zone */ static int -perform_addzone(SSL* ssl, xfrd_state_type* xfrd, char* arg) +perform_addzone(RES* ssl, xfrd_state_type* xfrd, char* arg) { const dname_type* dname; struct zone_options* zopt; @@ -1202,7 +1298,7 @@ perform_addzone(SSL* ssl, xfrd_state_type* xfrd, char* arg) /** perform the delzone command for one zone */ static int -perform_delzone(SSL* ssl, xfrd_state_type* xfrd, char* arg) +perform_delzone(RES* ssl, xfrd_state_type* xfrd, char* arg) { const dname_type* dname; struct zone_options* zopt; @@ -1252,7 +1348,7 @@ perform_delzone(SSL* ssl, xfrd_state_type* xfrd, char* arg) /** do the addzone command */ static void -do_addzone(SSL* ssl, xfrd_state_type* xfrd, char* arg) +do_addzone(RES* ssl, xfrd_state_type* xfrd, char* arg) { if(!perform_addzone(ssl, xfrd, arg)) return; @@ -1261,7 +1357,7 @@ do_addzone(SSL* ssl, xfrd_state_type* xfrd, char* arg) /** do the delzone command */ static void -do_delzone(SSL* ssl, xfrd_state_type* xfrd, char* arg) +do_delzone(RES* ssl, xfrd_state_type* xfrd, char* arg) { if(!perform_delzone(ssl, xfrd, arg)) return; @@ -1270,7 +1366,7 @@ do_delzone(SSL* ssl, xfrd_state_type* xfrd, char* arg) /** do the addzones command */ static void -do_addzones(SSL* ssl, xfrd_state_type* xfrd) +do_addzones(RES* ssl, xfrd_state_type* xfrd) { char buf[2048]; int num = 0; @@ -1292,7 +1388,7 @@ do_addzones(SSL* ssl, xfrd_state_type* xfrd) /** do the delzones command */ static void -do_delzones(SSL* ssl, xfrd_state_type* xfrd) +do_delzones(RES* ssl, xfrd_state_type* xfrd) { char buf[2048]; int num = 0; @@ -1670,7 +1766,7 @@ repat_options(xfrd_state_type* xfrd, struct nsd_options* newopt) static void print_ssl_cfg_err(void* arg, const char* str) { - SSL** ssl = (SSL**)arg; + RES** ssl = (RES**)arg; if(!*ssl) return; if(!ssl_printf(*ssl, "%s", str)) *ssl = NULL; /* failed, stop printing */ @@ -1678,7 +1774,7 @@ print_ssl_cfg_err(void* arg, const char* str) /** do the repattern command: reread config file and apply keys, patterns */ static void -do_repattern(SSL* ssl, xfrd_state_type* xfrd) +do_repattern(RES* ssl, xfrd_state_type* xfrd) { region_type* region = region_create(xalloc, free); struct nsd_options* opt; @@ -1718,7 +1814,7 @@ do_repattern(SSL* ssl, xfrd_state_type* xfrd) /** do the serverpid command: printout pid of server process */ static void -do_serverpid(SSL* ssl, xfrd_state_type* xfrd) +do_serverpid(RES* ssl, xfrd_state_type* xfrd) { (void)ssl_printf(ssl, "%u\n", (unsigned)xfrd->reload_pid); } @@ -1732,7 +1828,7 @@ cmdcmp(char* p, const char* cmd, size_t len) /** execute a remote control command */ static void -execute_cmd(struct daemon_remote* rc, SSL* ssl, char* cmd, struct rc_state* rs) +execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd, struct rc_state* rs) { char* p = skipwhite(cmd); /* compare command */ @@ -1781,7 +1877,7 @@ execute_cmd(struct daemon_remote* rc, SSL* ssl, char* cmd, struct rc_state* rs) /** handle remote control request */ static void -handle_req(struct daemon_remote* rc, struct rc_state* s, SSL* ssl) +handle_req(struct daemon_remote* rc, struct rc_state* s, RES* res) { int r; char pre[10]; @@ -1792,12 +1888,27 @@ handle_req(struct daemon_remote* rc, struct rc_state* s, SSL* ssl) } /* try to read magic UBCT[version]_space_ string */ - ERR_clear_error(); - if((r=SSL_read(ssl, magic, (int)sizeof(magic)-1)) <= 0) { - if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) + if(res->ssl) { + ERR_clear_error(); + if((r=SSL_read(res->ssl, magic, (int)sizeof(magic)-1)) <= 0) { + if(SSL_get_error(res->ssl, r) == SSL_ERROR_ZERO_RETURN) + return; + log_crypto_err("could not SSL_read"); return; - log_crypto_err("could not SSL_read"); - return; + } + } else { + while(1) { + ssize_t rr = read(res->fd, magic, sizeof(magic)-1); + if(rr <= 0) { + if(rr == 0) return; + if(errno == EINTR || errno == EAGAIN) + continue; + log_msg(LOG_ERR, "could not read: %s", strerror(errno)); + return; + } + r = (int)rr; + break; + } } magic[7] = 0; if( r != 7 || strncmp(magic, "NSDCT", 5) != 0) { @@ -1807,25 +1918,67 @@ handle_req(struct daemon_remote* rc, struct rc_state* s, SSL* ssl) } /* read the command line */ - if(!ssl_read_line(ssl, buf, sizeof(buf))) { + if(!ssl_read_line(res, buf, sizeof(buf))) { return; } snprintf(pre, sizeof(pre), "NSDCT%d ", NSD_CONTROL_VERSION); if(strcmp(magic, pre) != 0) { VERBOSITY(2, (LOG_INFO, "control connection had bad " "version %s, cmd: %s", magic, buf)); - ssl_printf(ssl, "error version mismatch\n"); + ssl_printf(res, "error version mismatch\n"); return; } VERBOSITY(2, (LOG_INFO, "control cmd: %s", buf)); /* figure out what to do */ - execute_cmd(rc, ssl, buf, s); + execute_cmd(rc, res, buf, s); +} + +/** handle SSL_do_handshake changes to the file descriptor to wait for later */ +static void +remote_handshake_later(struct daemon_remote* rc, struct rc_state* s, int fd, + int r, int r2) +{ + if(r2 == SSL_ERROR_WANT_READ) { + if(s->shake_state == rc_hs_read) { + /* try again later */ + return; + } + s->shake_state = rc_hs_read; + event_del(&s->c); + event_set(&s->c, fd, EV_PERSIST|EV_TIMEOUT|EV_READ, + remote_control_callback, s); + if(event_base_set(xfrd->event_base, &s->c) != 0) + log_msg(LOG_ERR, "remote_accept: cannot set event_base"); + if(event_add(&s->c, &s->tval) != 0) + log_msg(LOG_ERR, "remote_accept: cannot add event"); + return; + } else if(r2 == SSL_ERROR_WANT_WRITE) { + if(s->shake_state == rc_hs_write) { + /* try again later */ + return; + } + s->shake_state = rc_hs_write; + event_del(&s->c); + event_set(&s->c, fd, EV_PERSIST|EV_TIMEOUT|EV_WRITE, + remote_control_callback, s); + if(event_base_set(xfrd->event_base, &s->c) != 0) + log_msg(LOG_ERR, "remote_accept: cannot set event_base"); + if(event_add(&s->c, &s->tval) != 0) + log_msg(LOG_ERR, "remote_accept: cannot add event"); + return; + } else { + if(r == 0) + log_msg(LOG_ERR, "remote control connection closed prematurely"); + log_crypto_err("remote control failed ssl"); + clean_point(rc, s); + } } static void remote_control_callback(int fd, short event, void* arg) { + RES res; struct rc_state* s = (struct rc_state*)arg; struct daemon_remote* rc = s->rc; int r; @@ -1834,51 +1987,22 @@ remote_control_callback(int fd, short event, void* arg) clean_point(rc, s); return; } - /* (continue to) setup the SSL connection */ - ERR_clear_error(); - r = SSL_do_handshake(s->ssl); - if(r != 1) { - int r2 = SSL_get_error(s->ssl, r); - if(r2 == SSL_ERROR_WANT_READ) { - if(s->shake_state == rc_hs_read) { - /* try again later */ - return; - } - s->shake_state = rc_hs_read; - event_del(&s->c); - event_set(&s->c, fd, EV_PERSIST|EV_TIMEOUT|EV_READ, - remote_control_callback, s); - if(event_base_set(xfrd->event_base, &s->c) != 0) - log_msg(LOG_ERR, "remote_accept: cannot set event_base"); - if(event_add(&s->c, &s->tval) != 0) - log_msg(LOG_ERR, "remote_accept: cannot add event"); - return; - } else if(r2 == SSL_ERROR_WANT_WRITE) { - if(s->shake_state == rc_hs_write) { - /* try again later */ - return; - } - s->shake_state = rc_hs_write; - event_del(&s->c); - event_set(&s->c, fd, EV_PERSIST|EV_TIMEOUT|EV_WRITE, - remote_control_callback, s); - if(event_base_set(xfrd->event_base, &s->c) != 0) - log_msg(LOG_ERR, "remote_accept: cannot set event_base"); - if(event_add(&s->c, &s->tval) != 0) - log_msg(LOG_ERR, "remote_accept: cannot add event"); - return; - } else { - if(r == 0) - log_msg(LOG_ERR, "remote control connection closed prematurely"); - log_crypto_err("remote control failed ssl"); - clean_point(rc, s); + if(s->ssl) { + /* (continue to) setup the SSL connection */ + ERR_clear_error(); + r = SSL_do_handshake(s->ssl); + if(r != 1) { + int r2 = SSL_get_error(s->ssl, r); + remote_handshake_later(rc, s, fd, r, r2); return; } + s->shake_state = rc_none; } - s->shake_state = rc_none; /* once handshake has completed, check authentication */ - if(SSL_get_verify_result(s->ssl) == X509_V_OK) { + if (!rc->use_cert) { + VERBOSITY(3, (LOG_INFO, "unauthenticated remote control connection")); + } else if(SSL_get_verify_result(s->ssl) == X509_V_OK) { X509* x = SSL_get_peer_certificate(s->ssl); if(!x) { VERBOSITY(2, (LOG_INFO, "remote control connection " @@ -1896,7 +2020,9 @@ remote_control_callback(int fd, short event, void* arg) } /* if OK start to actually handle the request */ - handle_req(rc, s, s->ssl); + res.ssl = s->ssl; + res.fd = fd; + handle_req(rc, s, &res); if(!s->in_stats_list) { VERBOSITY(3, (LOG_INFO, "remote control operation completed")); @@ -1920,22 +2046,22 @@ opcode2str(int o) /** print long number */ static int -print_longnum(SSL* ssl, char* desc, uint64_t x) +print_longnum(RES* ssl, char* desc, uint64_t x) { if(x > (uint64_t)1024*1024*1024) { /* more than a Gb */ size_t front = (size_t)(x / (uint64_t)1000000); size_t back = (size_t)(x % (uint64_t)1000000); - return ssl_printf(ssl, "%s%u%6.6u\n", desc, - (unsigned)front, (unsigned)back); + return ssl_printf(ssl, "%s%lu%6.6lu\n", desc, + (unsigned long)front, (unsigned long)back); } else { - return ssl_printf(ssl, "%s%u\n", desc, (unsigned)x); + return ssl_printf(ssl, "%s%lu\n", desc, (unsigned long)x); } } /* print one block of statistics. n is name and d is delimiter */ static void -print_stat_block(SSL* ssl, char* n, char* d, struct nsdst* st) +print_stat_block(RES* ssl, char* n, char* d, struct nsdst* st) { const char* rcstr[] = {"NOERROR", "FORMERR", "SERVFAIL", "NXDOMAIN", "NOTIMP", "REFUSED", "YXDOMAIN", "YXRRSET", "NXRRSET", "NOTAUTH", @@ -1947,8 +2073,8 @@ print_stat_block(SSL* ssl, char* n, char* d, struct nsdst* st) if(inhibit_zero && st->qtype[i] == 0 && strncmp(rrtype_to_string(i), "TYPE", 4) == 0) continue; - if(!ssl_printf(ssl, "%s%snum.type.%s=%u\n", n, d, - rrtype_to_string(i), (unsigned)st->qtype[i])) + if(!ssl_printf(ssl, "%s%snum.type.%s=%lu\n", n, d, + rrtype_to_string(i), (unsigned long)st->qtype[i])) return; } @@ -1956,8 +2082,8 @@ print_stat_block(SSL* ssl, char* n, char* d, struct nsdst* st) for(i=0; i<6; i++) { if(inhibit_zero && st->opcode[i] == 0 && i != OPCODE_QUERY) continue; - if(!ssl_printf(ssl, "%s%snum.opcode.%s=%u\n", n, d, - opcode2str(i), (unsigned)st->opcode[i])) + if(!ssl_printf(ssl, "%s%snum.opcode.%s=%lu\n", n, d, + opcode2str(i), (unsigned long)st->opcode[i])) return; } @@ -1965,8 +2091,8 @@ print_stat_block(SSL* ssl, char* n, char* d, struct nsdst* st) for(i=0; i<4; i++) { if(inhibit_zero && st->qclass[i] == 0 && i != CLASS_IN) continue; - if(!ssl_printf(ssl, "%s%snum.class.%s=%u\n", n, d, - rrclass_to_string(i), (unsigned)st->qclass[i])) + if(!ssl_printf(ssl, "%s%snum.class.%s=%lu\n", n, d, + rrclass_to_string(i), (unsigned long)st->qclass[i])) return; } @@ -1975,58 +2101,58 @@ print_stat_block(SSL* ssl, char* n, char* d, struct nsdst* st) if(inhibit_zero && st->rcode[i] == 0 && i > RCODE_YXDOMAIN) /* NSD does not use larger */ continue; - if(!ssl_printf(ssl, "%s%snum.rcode.%s=%u\n", n, d, rcstr[i], - (unsigned)st->rcode[i])) + if(!ssl_printf(ssl, "%s%snum.rcode.%s=%lu\n", n, d, rcstr[i], + (unsigned long)st->rcode[i])) return; } /* edns */ - if(!ssl_printf(ssl, "%s%snum.edns=%u\n", n, d, (unsigned)st->edns)) + if(!ssl_printf(ssl, "%s%snum.edns=%lu\n", n, d, (unsigned long)st->edns)) return; /* ednserr */ - if(!ssl_printf(ssl, "%s%snum.ednserr=%u\n", n, d, - (unsigned)st->ednserr)) + if(!ssl_printf(ssl, "%s%snum.ednserr=%lu\n", n, d, + (unsigned long)st->ednserr)) return; /* qudp */ - if(!ssl_printf(ssl, "%s%snum.udp=%u\n", n, d, (unsigned)st->qudp)) + if(!ssl_printf(ssl, "%s%snum.udp=%lu\n", n, d, (unsigned long)st->qudp)) return; /* qudp6 */ - if(!ssl_printf(ssl, "%s%snum.udp6=%u\n", n, d, (unsigned)st->qudp6)) + if(!ssl_printf(ssl, "%s%snum.udp6=%lu\n", n, d, (unsigned long)st->qudp6)) return; /* ctcp */ - if(!ssl_printf(ssl, "%s%snum.tcp=%u\n", n, d, (unsigned)st->ctcp)) + if(!ssl_printf(ssl, "%s%snum.tcp=%lu\n", n, d, (unsigned long)st->ctcp)) return; /* ctcp6 */ - if(!ssl_printf(ssl, "%s%snum.tcp6=%u\n", n, d, (unsigned)st->ctcp6)) + if(!ssl_printf(ssl, "%s%snum.tcp6=%lu\n", n, d, (unsigned long)st->ctcp6)) return; /* nona */ - if(!ssl_printf(ssl, "%s%snum.answer_wo_aa=%u\n", n, d, - (unsigned)st->nona)) + if(!ssl_printf(ssl, "%s%snum.answer_wo_aa=%lu\n", n, d, + (unsigned long)st->nona)) return; /* rxerr */ - if(!ssl_printf(ssl, "%s%snum.rxerr=%u\n", n, d, (unsigned)st->rxerr)) + if(!ssl_printf(ssl, "%s%snum.rxerr=%lu\n", n, d, (unsigned long)st->rxerr)) return; /* txerr */ - if(!ssl_printf(ssl, "%s%snum.txerr=%u\n", n, d, (unsigned)st->txerr)) + if(!ssl_printf(ssl, "%s%snum.txerr=%lu\n", n, d, (unsigned long)st->txerr)) return; /* number of requested-axfr, number of times axfr served to clients */ - if(!ssl_printf(ssl, "%s%snum.raxfr=%u\n", n, d, (unsigned)st->raxfr)) + if(!ssl_printf(ssl, "%s%snum.raxfr=%lu\n", n, d, (unsigned long)st->raxfr)) return; /* truncated */ - if(!ssl_printf(ssl, "%s%snum.truncated=%u\n", n, d, - (unsigned)st->truncated)) + if(!ssl_printf(ssl, "%s%snum.truncated=%lu\n", n, d, + (unsigned long)st->truncated)) return; /* dropped */ - if(!ssl_printf(ssl, "%s%snum.dropped=%u\n", n, d, - (unsigned)st->dropped)) + if(!ssl_printf(ssl, "%s%snum.dropped=%lu\n", n, d, + (unsigned long)st->dropped)) return; } @@ -2044,7 +2170,7 @@ resize_zonestat(xfrd_state_type* xfrd, size_t num) } static void -zonestat_print(SSL* ssl, xfrd_state_type* xfrd, int clear) +zonestat_print(RES* ssl, xfrd_state_type* xfrd, int clear) { struct zonestatname* n; struct nsdst stat0, stat1; @@ -2086,8 +2212,8 @@ zonestat_print(SSL* ssl, xfrd_state_type* xfrd, int clear) } /* stat0 contains the details that we want to print */ - if(!ssl_printf(ssl, "%s%snum.queries=%u\n", name, ".", - (unsigned)(stat0.qudp + stat0.qudp6 + stat0.ctcp + + if(!ssl_printf(ssl, "%s%snum.queries=%lu\n", name, ".", + (unsigned long)(stat0.qudp + stat0.qudp6 + stat0.ctcp + stat0.ctcp6))) return; print_stat_block(ssl, name, ".", &stat0); @@ -2096,7 +2222,7 @@ zonestat_print(SSL* ssl, xfrd_state_type* xfrd, int clear) #endif /* USE_ZONE_STATS */ static void -print_stats(SSL* ssl, xfrd_state_type* xfrd, struct timeval* now, int clear) +print_stats(RES* ssl, xfrd_state_type* xfrd, struct timeval* now, int clear) { size_t i; stc_type total = 0; @@ -2104,22 +2230,22 @@ print_stats(SSL* ssl, xfrd_state_type* xfrd, struct timeval* now, int clear) /* per CPU and total */ for(i=0; insd->child_count; i++) { - if(!ssl_printf(ssl, "server%d.queries=%u\n", (int)i, - (unsigned)xfrd->nsd->children[i].query_count)) + if(!ssl_printf(ssl, "server%d.queries=%lu\n", (int)i, + (unsigned long)xfrd->nsd->children[i].query_count)) return; total += xfrd->nsd->children[i].query_count; } - if(!ssl_printf(ssl, "num.queries=%u\n", (unsigned)total)) + if(!ssl_printf(ssl, "num.queries=%lu\n", (unsigned long)total)) return; /* time elapsed and uptime (in seconds) */ timeval_subtract(&uptime, now, &xfrd->nsd->rc->boot_time); timeval_subtract(&elapsed, now, &xfrd->nsd->rc->stats_time); - if(!ssl_printf(ssl, "time.boot=%u.%6.6u\n", - (unsigned)uptime.tv_sec, (unsigned)uptime.tv_usec)) + if(!ssl_printf(ssl, "time.boot=%lu.%6.6lu\n", + (unsigned long)uptime.tv_sec, (unsigned long)uptime.tv_usec)) return; - if(!ssl_printf(ssl, "time.elapsed=%u.%6.6u\n", - (unsigned)elapsed.tv_sec, (unsigned)elapsed.tv_usec)) + if(!ssl_printf(ssl, "time.elapsed=%lu.%6.6lu\n", + (unsigned long)elapsed.tv_sec, (unsigned long)elapsed.tv_usec)) return; /* mem info, database on disksize */ @@ -2138,10 +2264,10 @@ print_stats(SSL* ssl, xfrd_state_type* xfrd, struct timeval* now, int clear) print_stat_block(ssl, "", "", &xfrd->nsd->st); /* zone statistics */ - if(!ssl_printf(ssl, "zone.master=%u\n", - (unsigned)(xfrd->notify_zones->count - xfrd->zones->count))) + if(!ssl_printf(ssl, "zone.master=%lu\n", + (unsigned long)(xfrd->notify_zones->count - xfrd->zones->count))) return; - if(!ssl_printf(ssl, "zone.slave=%u\n", (unsigned)xfrd->zones->count)) + if(!ssl_printf(ssl, "zone.slave=%lu\n", (unsigned long)xfrd->zones->count)) return; #ifdef USE_ZONE_STATS zonestat_print(ssl, xfrd, clear); /* per-zone statistics */ @@ -2170,6 +2296,7 @@ clear_stats(xfrd_state_type* xfrd) void daemon_remote_process_stats(struct daemon_remote* rc) { + RES res; struct rc_state* s; struct timeval now; if(!rc) return; @@ -2178,7 +2305,9 @@ daemon_remote_process_stats(struct daemon_remote* rc) /* pop one and give it stats */ while((s = rc->stats_list)) { assert(s->in_stats_list); - print_stats(s->ssl, rc->xfrd, &now, (s->in_stats_list == 1)); + res.ssl = s->ssl; + res.fd = s->fd; + print_stats(&res, rc->xfrd, &now, (s->in_stats_list == 1)); if(s->in_stats_list == 1) { clear_stats(rc->xfrd); rc->stats_time = now; @@ -2191,4 +2320,66 @@ daemon_remote_process_stats(struct daemon_remote* rc) } #endif /* BIND8_STATS */ +int +create_local_accept_sock(const char *path, int* noproto) +{ +#ifdef HAVE_SYS_UN_H + int s; + struct sockaddr_un usock; + + VERBOSITY(3, (LOG_INFO, "creating unix socket %s", path)); +#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN + /* this member exists on BSDs, not Linux */ + usock.sun_len = (unsigned)sizeof(usock); +#endif + usock.sun_family = AF_LOCAL; + /* length is 92-108, 104 on FreeBSD */ + (void)strlcpy(usock.sun_path, path, sizeof(usock.sun_path)); + + if ((s = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) { + log_msg(LOG_ERR, "Cannot create local socket %s (%s)", + path, strerror(errno)); + return -1; + } + + if (unlink(path) && errno != ENOENT) { + /* The socket already exists and cannot be removed */ + log_msg(LOG_ERR, "Cannot remove old local socket %s (%s)", + path, strerror(errno)); + goto err; + } + + if (bind(s, (struct sockaddr *)&usock, + (socklen_t)sizeof(struct sockaddr_un)) == -1) { + log_msg(LOG_ERR, "Cannot bind local socket %s (%s)", + path, strerror(errno)); + goto err; + } + + if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) { + log_msg(LOG_ERR, "Cannot set non-blocking mode"); + goto err; + } + + if (listen(s, TCP_BACKLOG) == -1) { + log_msg(LOG_ERR, "can't listen: %s", strerror(errno)); + goto err; + } + + (void)noproto; /*unused*/ + return s; + +err: + close(s); + return -1; + +#else + (void)use_systemd; + (void)path; + log_msg(LOG_ERR, "Local sockets are not supported"); + *noproto = 1; + return -1; +#endif +} + #endif /* HAVE_SSL */ diff --git a/usr.sbin/nsd/remote.h b/usr.sbin/nsd/remote.h index d3f8b7c35fb..0a8738ab4a9 100644 --- a/usr.sbin/nsd/remote.h +++ b/usr.sbin/nsd/remote.h @@ -99,4 +99,13 @@ void daemon_remote_attach(struct daemon_remote* rc, struct xfrd_state* xfrd); */ void daemon_remote_process_stats(struct daemon_remote* rc); +/** + * Create and bind local listening socket + * @param path: path to the socket. + * @param noproto: on error, this is set true if cause is that local sockets + * are not supported. + * @return: the socket. -1 on error. + */ +int create_local_accept_sock(const char* path, int* noproto); + #endif /* DAEMON_REMOTE_H */ diff --git a/usr.sbin/nsd/server.c b/usr.sbin/nsd/server.c index c434f6fbfb8..637442c494a 100644 --- a/usr.sbin/nsd/server.c +++ b/usr.sbin/nsd/server.c @@ -769,7 +769,8 @@ server_init_ifs(struct nsd *nsd, size_t from, size_t to, int* reuseport_works) #endif /* SO_BINDANY */ } - if (bind(nsd->udp[i].s, (struct sockaddr *) addr->ai_addr, addr->ai_addrlen) != 0) { + if ( + bind(nsd->udp[i].s, (struct sockaddr *) addr->ai_addr, addr->ai_addrlen) != 0) { log_msg(LOG_ERR, "can't bind udp socket: %s", strerror(errno)); return -1; } @@ -905,7 +906,8 @@ server_init_ifs(struct nsd *nsd, size_t from, size_t to, int* reuseport_works) #endif /* SO_BINDANY */ } - if (bind(nsd->tcp[i].s, (struct sockaddr *) addr->ai_addr, addr->ai_addrlen) != 0) { + if( + bind(nsd->tcp[i].s, (struct sockaddr *) addr->ai_addr, addr->ai_addrlen) != 0) { log_msg(LOG_ERR, "can't bind tcp socket: %s", strerror(errno)); return -1; } diff --git a/usr.sbin/nsd/xfrd.c b/usr.sbin/nsd/xfrd.c index 14fec35a28a..4dc87a8074d 100644 --- a/usr.sbin/nsd/xfrd.c +++ b/usr.sbin/nsd/xfrd.c @@ -31,6 +31,10 @@ #include "remote.h" #include "rrl.h" +#ifdef HAVE_SYSTEMD +#include +#endif + #define XFRD_UDP_TIMEOUT 10 /* seconds, before a udp request times out */ #define XFRD_NO_IXFR_CACHE 172800 /* 48h before retrying ixfr's after notimpl */ #define XFRD_LOWERBOUND_REFRESH 1 /* seconds, smallest refresh timeout */ @@ -222,6 +226,10 @@ xfrd_init(int socket, struct nsd* nsd, int shortsoa, int reload_active, xfrd_sigsetup(SIGINT); DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd startup")); +#ifdef HAVE_SYSTEMD + if(xfrd->nsd->options->use_systemd) + sd_notify(0, "READY=1"); +#endif xfrd_main(); } @@ -324,6 +332,10 @@ xfrd_shutdown() xfrd_zone_type* zone; DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd shutdown")); +#ifdef HAVE_SYSTEMD + if(xfrd->nsd->options->use_systemd) + sd_notify(0, "STOPPING=1"); +#endif event_del(&xfrd->ipc_handler); close(xfrd->ipc_handler.ev_fd); /* notifies parent we stop */ zone_list_close(nsd.options); @@ -2503,10 +2515,18 @@ void xfrd_process_task_result(xfrd_state_type* xfrd, struct udb_base* taskudb) * reload, this happens when the reload signal is sent, and thus * the taskudbs are swapped */ task_clear(taskudb); +#ifdef HAVE_SYSTEMD + if(xfrd->nsd->options->use_systemd) + sd_notify(0, "READY=1"); +#endif } void xfrd_set_reload_now(xfrd_state_type* xfrd) { +#ifdef HAVE_SYSTEMD + if(xfrd->nsd->options->use_systemd) + sd_notify(0, "RELOADING=1"); +#endif xfrd->need_to_send_reload = 1; if(!(xfrd->ipc_handler_flags&EV_WRITE)) { ipc_xfrd_set_listening(xfrd, EV_PERSIST|EV_READ|EV_WRITE); diff --git a/usr.sbin/nsd/zparser.y b/usr.sbin/nsd/zparser.y index 831ef39c51e..5f7da86dd5d 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_AVC +%token T_OPENPGPKEY T_CSYNC T_AVC T_SMIMEA /* other tokens */ %token DOLLAR_TTL DOLLAR_ORIGIN NL SP @@ -617,6 +617,8 @@ type_and_rdata: | T_DNSKEY sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } | T_TLSA sp rdata_tlsa | T_TLSA sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_SMIMEA sp rdata_smimea + | T_SMIMEA sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } | T_NID sp rdata_nid | T_NID sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } | T_L32 sp rdata_l32 @@ -943,6 +945,15 @@ rdata_tlsa: STR sp STR sp STR sp str_sp_seq trail } ; +rdata_smimea: STR sp STR sp STR sp str_sp_seq trail + { + zadd_rdata_wireformat(zparser_conv_byte(parser->region, $1.str)); /* usage */ + zadd_rdata_wireformat(zparser_conv_byte(parser->region, $3.str)); /* selector */ + zadd_rdata_wireformat(zparser_conv_byte(parser->region, $5.str)); /* matching type */ + zadd_rdata_wireformat(zparser_conv_hex(parser->region, $7.str, $7.len)); /* ca data */ + } + ; + rdata_dnskey: STR sp STR sp STR sp str_sp_seq trail { zadd_rdata_wireformat(zparser_conv_short(parser->region, $1.str)); /* flags */ @@ -1040,7 +1051,7 @@ rdata_eui64: STR trail ; /* RFC7553 */ -rdata_uri: STR sp STR sp STR trail +rdata_uri: STR sp STR sp dotted_str trail { zadd_rdata_wireformat(zparser_conv_short(parser->region, $1.str)); /* priority */ zadd_rdata_wireformat(zparser_conv_short(parser->region, $3.str)); /* weight */ @@ -1049,7 +1060,7 @@ rdata_uri: STR sp STR sp STR trail ; /* RFC 6844 */ -rdata_caa: STR sp STR sp STR trail +rdata_caa: STR sp STR sp dotted_str trail { zadd_rdata_wireformat(zparser_conv_byte(parser->region, $1.str)); /* Flags */ zadd_rdata_wireformat(zparser_conv_tag(parser->region, $3.str, $3.len)); /* Tag */ -- 2.20.1