Index: nsd-4.1.26/config.h.in =================================================================== --- nsd-4.1.26.orig/config.h.in +++ nsd-4.1.26/config.h.in @@ -550,6 +550,9 @@ /* Define this to enable per-zone statistics gathering. */ #undef USE_ZONE_STATS +/* Define this to enable TCP fast open. */ +#undef USE_TCP_FASTOPEN + /* Define to the NSD version to answer version.server query. */ #undef VERSION Index: nsd-4.1.26/configlexer.lex =================================================================== --- nsd-4.1.26.orig/configlexer.lex +++ nsd-4.1.26/configlexer.lex @@ -291,6 +291,10 @@ min-refresh-time{COLON} { LEXOUT(("v(%s) max-retry-time{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MAX_RETRY_TIME;} min-retry-time{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MIN_RETRY_TIME;} multi-master-check{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MULTI_MASTER_CHECK;} +tls-service-key{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_SERVICE_KEY;} +tls-service-pem{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_SERVICE_PEM;} +tls-port{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_PORT;} +do-starttls{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DO_STARTTLS;} {NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++;} /* Quoted strings. Strip leading and ending quotes */ Index: nsd-4.1.26/configure.ac =================================================================== --- nsd-4.1.26.orig/configure.ac +++ nsd-4.1.26/configure.ac @@ -871,6 +871,7 @@ if test x$HAVE_SSL = x"yes"; then else AC_MSG_WARN([No SSL, therefore remote-control is disabled]) + AC_MSG_WARN([No SSL, therefore TLS is disabled]) fi AC_ARG_ENABLE(nsec3, AC_HELP_STRING([--disable-nsec3], [Disable NSEC3 support])) @@ -953,6 +954,32 @@ dt_DNSTAP([${localstatedir}/run/nsd-dnst sinclude(systemd.m4) # Include systemd.m4 - end +AC_ARG_ENABLE(TObit, AC_HELP_STRING([--enable-TObit], [(Experimental) Enables TO bit during STARTTLS negotiation. NOTE WELL: The TO bit is NOT assigned by IANA - USE FOR TESTING ONLY! DO NOT DEPLOY TO PRODUCTION ENVIRONMENTS!])) +case "$enable_TObit" in + yes) + if test x$HAVE_SSL = x"yes"; then + AC_DEFINE_UNQUOTED([USE_TO_BIT], [], [Define this to enable use of the TO bit.]) + else + AC_MSG_WARN([No SSL, therefore TO bit is disabled]) + fi + ;; + no|*) + ;; +esac + +AC_ARG_ENABLE(tcp-fastopen, AC_HELP_STRING([--enable-tcp-fastopen], [Enable TCP Fast Open])) +case "$enable_tcp_fastopen" in + yes) + AC_CHECK_DECL([TCP_FASTOPEN], [], [AC_MSG_ERROR([TCP Fast Open is not available: please rerun without --enable-tcp-fastopen])], [AC_INCLUDES_DEFAULT +#include + ]) + AC_DEFINE_UNQUOTED([USE_TCP_FASTOPEN], [1], [Define this to enable TCP fast open.]) + ;; + no|*) + ;; +esac + + AH_BOTTOM([ /* define before includes as it specifies what standard to use. */ #if (defined(HAVE_PSELECT) && !defined (HAVE_PSELECT_PROTO)) \ Index: nsd-4.1.26/edns.c =================================================================== --- nsd-4.1.26.orig/edns.c +++ nsd-4.1.26/edns.c @@ -57,6 +57,9 @@ edns_init_record(edns_record_type *edns) edns->maxlen = 0; edns->opt_reserved_space = 0; edns->dnssec_ok = 0; +#ifdef USE_TO_BIT + edns->tls_ok = 0; +#endif edns->nsid = 0; } @@ -146,6 +149,9 @@ edns_parse_record(edns_record_type *edns edns->status = EDNS_OK; edns->maxlen = opt_class; edns->dnssec_ok = opt_flags & DNSSEC_OK_MASK; +#ifdef USE_TO_BIT + edns->tls_ok = opt_flags & TLS_OK_MASK; +#endif return 1; } Index: nsd-4.1.26/edns.h =================================================================== --- nsd-4.1.26.orig/edns.h +++ nsd-4.1.26/edns.h @@ -19,6 +19,10 @@ struct query; #define OPT_HDR 4U /* NSID opt header length */ #define NSID_CODE 3 /* nsid option code */ #define DNSSEC_OK_MASK 0x8000U /* do bit mask */ +#ifdef USE_TO_BIT +#define TLS_OK_MASK 0x4000U /* to bit mask */ +#endif + struct edns_data { Index: nsd-4.1.26/nsd-checkconf.c =================================================================== --- nsd-4.1.26.orig/nsd-checkconf.c +++ nsd-4.1.26/nsd-checkconf.c @@ -371,6 +371,7 @@ config_print_zone(nsd_options_type* opt, SERV_GET_BIN(round_robin, o); SERV_GET_BIN(minimal_responses, o); SERV_GET_BIN(refuse_any, o); + SERV_GET_BIN(do_starttls, o); /* str */ SERV_GET_PATH(final, database, o); SERV_GET_STR(identity, o); @@ -385,6 +386,9 @@ config_print_zone(nsd_options_type* opt, SERV_GET_PATH(final, xfrdir, o); SERV_GET_PATH(final, zonelistfile, o); SERV_GET_STR(port, o); + SERV_GET_STR(tls_service_key, o); + SERV_GET_STR(tls_service_pem, o); + SERV_GET_STR(tls_port, o); /* int */ SERV_GET_INT(server_count, o); SERV_GET_INT(tcp_count, o); @@ -536,6 +540,10 @@ config_test_print_server(nsd_options_typ #endif printf("\tzonefiles-check: %s\n", opt->zonefiles_check?"yes":"no"); printf("\tzonefiles-write: %d\n", opt->zonefiles_write); + print_string_var("tls-service-key:", opt->tls_service_key); + print_string_var("tls-service-pem:", opt->tls_service_pem); + print_string_var("tls-port:", opt->tls_port); + printf("\tdo_starttls: %s\n", opt->do_starttls?"yes":"no"); #ifdef USE_DNSTAP printf("\ndnstap:\n"); Index: nsd-4.1.26/nsd.c =================================================================== --- nsd-4.1.26.orig/nsd.c +++ nsd-4.1.26/nsd.c @@ -659,6 +659,9 @@ main(int argc, char *argv[]) nsd.outgoing_tcp_mss = nsd.options->outgoing_tcp_mss; nsd.ipv4_edns_size = nsd.options->ipv4_edns_size; nsd.ipv6_edns_size = nsd.options->ipv6_edns_size; +#ifdef HAVE_SSL + nsd.tls_ctx = NULL; +#endif if(udp_port == 0) { @@ -945,6 +948,13 @@ main(int argc, char *argv[]) if(!(nsd.rc = daemon_remote_create(nsd.options))) error("could not perform remote control setup"); } + if(nsd.options->tls_service_key && nsd.options->tls_service_key[0] + && nsd.options->tls_service_pem && nsd.options->tls_service_pem[0]) { + if(!(nsd.tls_ctx = server_tls_ctx_create(&nsd, NULL))) + error("could not set up tls SSL_CTX"); + if (nsd.options->do_starttls) + log_msg(LOG_NOTICE, "STARTTLS enabled"); + } #endif /* HAVE_SSL */ /* Unless we're debugging, fork... */ Index: nsd-4.1.26/nsd.conf.5.in =================================================================== --- nsd-4.1.26.orig/nsd.conf.5.in +++ nsd-4.1.26/nsd.conf.5.in @@ -436,6 +436,26 @@ whitelisted. Default @ratelimit_default@ specific queries to receive this qps limit instead of the normal limit. With the value 0 the rate is unlimited. .\" rrlend +.TP +.B tls\-service\-key:\fR +If enabled, the server provides TLS service on its TCP sockets. +The file is the private key for the TLS session. The public certificate is in the +tls-service-pem file. Default is "", turned off. Requires a restart (a reload is +not enough) if changed, because the private key is read while root permissions +are held and before chroot (if any). Normal DNS TCP service is not provided and gives +errors, this service is best run with a different port: config or @port suffixes in the +interface config. +.TP +.B tls\-service\-pem:\fR +The public key certificate pem file for the tls service. Default is "", turned off. +.TP +.B tls\-port:\fR +The port number on which to provide TCP TLS service, default is turned off, only +interfaces configured with that port number as @number get the TLS service. +.TP +.B do\-starttls:\fR +Allow upgrade to TLS on regular TCP connections via STARTTLS. +Uses the tls\-service\-key and tls\-service\-pem for the key and certificate. .SS "Remote Control" The .B remote\-control: Index: nsd-4.1.26/nsd.conf.sample.in =================================================================== --- nsd-4.1.26.orig/nsd.conf.sample.in +++ nsd-4.1.26/nsd.conf.sample.in @@ -189,6 +189,16 @@ server: # dnstap-log-auth-query-messages: no # dnstap-log-auth-response-messages: no + # Service clients over TLS (on the TCP sockets), with plain DNS inside + # the TLS stream. Give the certificate to use and private key. + # Default is "" (disabled). Requires restart to take effect. + # tls-service-key: "path/to/privatekeyfile.key" + # tls-service-pem: "path/to/publiccertfile.pem" + # tls-port: + + # Do we allow tls upgrade on tcp connections after STARTTLS? + # do-starttls + # Remote control config section. remote-control: # Enable remote control with nsd-control(8) here. Index: nsd-4.1.26/nsd.h =================================================================== --- nsd-4.1.26.orig/nsd.h +++ nsd-4.1.26/nsd.h @@ -11,6 +11,9 @@ #define _NSD_H_ #include +#ifdef HAVE_OPENSSL_SSL_H +#include +#endif #include "dns.h" #include "edns.h" @@ -276,6 +279,11 @@ struct nsd unsigned int err_limit_count; struct nsd_options* options; + +#ifdef HAVE_SSL + /* TLS specific configuration */ + SSL_CTX *tls_ctx; +#endif }; extern struct nsd nsd; @@ -311,6 +319,9 @@ void server_prepare_xfrd(struct nsd *nsd void server_start_xfrd(struct nsd *nsd, int del_db, int reload_active); /* send SOA serial numbers to xfrd */ void server_send_soa_xfrd(struct nsd *nsd, int shortsoa); +#ifdef HAVE_SSL +SSL_CTX* server_tls_ctx_create(struct nsd *nsd, char* verifypem); +#endif ssize_t block_read(struct nsd* nsd, int s, void* p, ssize_t sz, int timeout); #endif /* _NSD_H_ */ Index: nsd-4.1.26/options.c =================================================================== --- nsd-4.1.26.orig/options.c +++ nsd-4.1.26/options.c @@ -113,6 +113,10 @@ nsd_options_create(region_type* region) opt->zonefiles_write = ZONEFILES_WRITE_INTERVAL; else opt->zonefiles_write = 0; opt->xfrd_reload_timeout = 1; + opt->tls_service_key = NULL; + opt->tls_service_pem = NULL; + opt->tls_port = NULL; + opt->do_starttls = 0; opt->control_enable = 0; opt->control_interface = NULL; opt->control_port = NSD_CONTROL_PORT; Index: nsd-4.1.26/options.h =================================================================== --- nsd-4.1.26.orig/options.h +++ nsd-4.1.26/options.h @@ -97,6 +97,15 @@ struct nsd_options { int refuse_any; int reuseport; + /* private key file for TLS */ + char* tls_service_key; + /* certificate file for TLS */ + char* tls_service_pem; + /* TLS dedicated port */ + const char* tls_port; + /* Allow upgrade to TLS after STARTTLS negotiation*/ + int do_starttls; + /** remote control section. enable toggle. */ int control_enable; /** the interfaces the remote control should listen on */ Index: nsd-4.1.26/query.c =================================================================== --- nsd-4.1.26.orig/query.c +++ nsd-4.1.26/query.c @@ -37,6 +37,11 @@ #include "nsec3.h" #include "tsig.h" +#ifdef HAVE_SSL +#define STARTTLS_STR "STARTTLS" +#define NO_TLS_STR "NO_TLS" +#endif + /* [Bug #253] Adding unnecessary NS RRset may lead to undesired truncation. * This function determines if the final response packet needs the NS RRset * included. Currently, it will only return negative if QTYPE == DNSKEY|DS. @@ -200,6 +205,10 @@ query_create(region_type *region, uint16 query->tsig_prepare_it = 1; query->tsig_update_it = 1; query->tsig_sign_it = 1; +#ifdef HAVE_SSL + query->first_query = 1; + query->tls_ok = 0; +#endif return query; } @@ -362,6 +371,22 @@ process_edns(nsd_type* nsd, struct query #endif } +#ifdef USE_TO_BIT + /* Change the value of the tls_ok bit depending on whether or not the + * connection should be upgraded to TLS based on this query. The value is + * used later to trigger the upgrade and also to construct the response. + */ + if (q->edns.tls_ok) { + /* Logs for testing */ + VERBOSITY(3, (LOG_INFO, "TO bit recieved")); + if (!(q->tcp && q->first_query && nsd->tls_ctx + && nsd->options->do_starttls)) { + VERBOSITY(3, (LOG_INFO, "TO bit rejected")); + q->edns.tls_ok = 0; + } + } +#endif + /* Strip the OPT resource record off... */ buffer_set_position(q->packet, q->edns.position); buffer_set_limit(q->packet, q->edns.position); @@ -553,6 +578,46 @@ answer_chaos(struct nsd *nsd, query_type } else { RCODE_SET(q->packet, RCODE_REFUSE); } +#ifdef HAVE_SSL + } else if (q->qname->name_size == 10 + && memcmp(dname_name(q->qname), "\010starttls", 10) == 0) { + if (q->tcp) { + if (q->first_query) { + if (nsd->tls_ctx +#ifdef USE_TO_BIT + && q->edns.tls_ok +#endif + && nsd->options->do_starttls ) { + /* Add STARTTLS answer */ + query_addtxt(q, + buffer_begin(q->packet) + QHEADERSZ, + CLASS_CH, + 0, + STARTTLS_STR); + ANCOUNT_SET(q->packet, ANCOUNT(q->packet) + 1); + /* Not required if TO bit assigned and used.*/ + q->tls_ok = 1; + /* Logs here are for testing */ + VERBOSITY(3, (LOG_INFO, "STARTTLS received and sent in response")); + } else { + /* Add NO_TLS answer */ + query_addtxt(q, + buffer_begin(q->packet) + QHEADERSZ, + CLASS_CH, + 0, + NO_TLS_STR); + ANCOUNT_SET(q->packet, ANCOUNT(q->packet) + 1); + VERBOSITY(3, (LOG_INFO, "STARTTLS received and but NO_TLS sent in response")); + } + } else { + RCODE_SET(q->packet, RCODE_REFUSE); + VERBOSITY(3, (LOG_INFO, "STARTTLS ignored as not first query on tcp connection")); + } + } else { + RCODE_SET(q->packet, RCODE_REFUSE); + } +#endif + /* Back port of fix in 4.1.1 for noname CH TXT queries */ } else { RCODE_SET(q->packet, RCODE_REFUSE); } @@ -1518,6 +1583,11 @@ query_add_optional(query_type *q, nsd_ty case EDNS_OK: if (q->edns.dnssec_ok) edns->ok[7] = 0x80; else edns->ok[7] = 0x00; +#ifdef USE_TO_BIT + if (q->edns.tls_ok) { + edns->ok[7] = edns->ok[7] | ((TLS_OK_MASK >> 8) & 0xFF); + } +#endif buffer_write(q->packet, edns->ok, OPT_LEN); if(q->edns.opt_reserved_space == 0 || !buffer_available( q->packet, 2+q->edns.opt_reserved_space)) { @@ -1541,6 +1611,11 @@ query_add_optional(query_type *q, nsd_ty case EDNS_ERROR: if (q->edns.dnssec_ok) edns->error[7] = 0x80; else edns->error[7] = 0x00; +#ifdef USE_TO_BIT + if (q->edns.tls_ok) { + edns->ok[7] = edns->ok[7] | ((TLS_OK_MASK >> 8) & 0xFF); + } +#endif buffer_write(q->packet, edns->error, OPT_LEN); buffer_write(q->packet, edns->rdata_none, OPT_RDATA); ARCOUNT_SET(q->packet, ARCOUNT(q->packet) + 1); Index: nsd-4.1.26/query.h =================================================================== --- nsd-4.1.26.orig/query.h +++ nsd-4.1.26/query.h @@ -63,6 +63,13 @@ struct query { int tcp; uint16_t tcplen; +#ifdef HAVE_SSL + int first_query; + /* This tls_ok variable is needed for the case where the TO bit is not used + * and upgrade is triggered based only on the STARTTLS query content. It can + * be removed if the TO bit is assigned by IANA and the logic is revised. */ + int tls_ok; +#endif buffer_type *packet; Index: nsd-4.1.26/server.c =================================================================== --- nsd-4.1.26.orig/server.c +++ nsd-4.1.26/server.c @@ -16,6 +16,9 @@ #include #include +#ifdef USE_TCP_FASTOPEN + #include +#endif #include #include @@ -40,6 +43,12 @@ #ifdef HAVE_OPENSSL_RAND_H #include #endif +#ifdef HAVE_OPENSSL_SSL_H +#include +#endif +#ifdef HAVE_OPENSSL_ERR_H +#include +#endif #ifndef USE_MINI_EVENT # ifdef HAVE_EVENT_H # include @@ -71,6 +80,11 @@ #define RELOAD_SYNC_TIMEOUT 25 /* seconds */ +#ifdef USE_TCP_FASTOPEN + #define TCP_FASTOPEN_FILE "/proc/sys/net/ipv4/tcp_fastopen" + #define TCP_FASTOPEN_SERVER_BIT_MASK 0x2 +#endif + /* * Data for the UDP handlers. */ @@ -86,6 +100,10 @@ struct tcp_accept_handler_data { struct nsd_socket *socket; int event_added; struct event event; +#ifdef HAVE_SSL + /* handler accepts TLS connections on the dedicated port */ + int tls_accept; +#endif }; /* @@ -170,6 +188,17 @@ struct tcp_handler_data * The timeout in msec for this tcp connection */ int tcp_timeout; +#ifdef HAVE_SSL + /* + * TLS object. + */ + SSL* tls; + + /* + * TLS handshake state. + */ + enum { tls_hs_none, tls_hs_read, tls_hs_write } shake_state; +#endif }; /* @@ -202,6 +231,29 @@ static void handle_tcp_reading(int fd, s */ static void handle_tcp_writing(int fd, short event, void* arg); +#ifdef HAVE_SSL +/*Comment*/ +static SSL* incoming_ssl_fd(SSL_CTX* ctx, int fd); +/* + * Handle TLS handshake. May be called multiple times if incomplete. + */ +static int tls_handshake(struct tcp_handler_data* data, int fd); + +/* + * Handle incoming queries on a TLS over TCP connection. The TLS + * connections are configured to be non-blocking and the handler may + * be called multiple times before a complete query is received. + */ +static void handle_tls_reading(int fd, short event, void* arg); + +/* + * Handle outgoing responses on a TLS over TCP connection. The TLS + * connections are configured to be non-blocking and the handler may + * be called multiple times before a complete response is sent. + */ +static void handle_tls_writing(int fd, short event, void* arg); +#endif + /* * Send all children the quit nonblocking, then close pipe. */ @@ -224,6 +276,34 @@ static uint32_t compression_table_capaci static uint32_t compression_table_size = 0; static domain_type* compressed_dnames[MAXRRSPP]; +#ifdef USE_TCP_FASTOPEN +/* Checks to see if the kernal value must be manually changed in order for + TCP Fast Open to support server mode */ +static void report_tcp_fastopen_config() { + + int tcp_fastopen_fp; + uint8_t tcp_fastopen_value; + + if ( (tcp_fastopen_fp = open(TCP_FASTOPEN_FILE, O_RDONLY)) == -1 ) { + log_msg(LOG_INFO,"Error opening " TCP_FASTOPEN_FILE ": %s\n", strerror(errno)); + } + if (read(tcp_fastopen_fp, &tcp_fastopen_value, 1) == -1 ) { + log_msg(LOG_INFO,"Error reading " TCP_FASTOPEN_FILE ": %s\n", strerror(errno)); + close(tcp_fastopen_fp); + } + if (!(tcp_fastopen_value & TCP_FASTOPEN_SERVER_BIT_MASK)) { + log_msg(LOG_ERR,"Error: TCP Fast Open support is available and configure in NSD by default.\n"); + log_msg(LOG_ERR,"However the kernal paramenters are not configured to support TCP_FASTOPEN in server mode.\n"); + log_msg(LOG_ERR,"To enable TFO use the command:"); + log_msg(LOG_ERR," 'sudo sysctl -w net.ipv4.tcp_fastopen=2' for pure server mode or\n"); + log_msg(LOG_ERR," 'sudo sysctl -w net.ipv4.tcp_fastopen=3' for both client and server mode\n"); + log_msg(LOG_ERR,"NSD will not have TCP Fast Open available until this change is made.\n"); + close(tcp_fastopen_fp); + } + close(tcp_fastopen_fp); +} +#endif + /* * Remove the specified pid from the list of child pids. Returns -1 if * the pid is not in the list, child_num otherwise. The field is set to 0. @@ -796,6 +876,10 @@ server_init_ifs(struct nsd *nsd, size_t /* TCP */ +#ifdef USE_TCP_FASTOPEN + report_tcp_fastopen_config(); +#endif + /* Make a socket... */ for (i = from; i < to; i++) { /* for reuseports copy socket specs of first entries */ @@ -930,6 +1014,13 @@ server_init_ifs(struct nsd *nsd, size_t return -1; } +#ifdef USE_TCP_FASTOPEN + int backlog = TCP_BACKLOG; + if ((setsockopt(nsd->tcp[i].s, IPPROTO_TCP, TCP_FASTOPEN, &backlog, sizeof(backlog))) == -1 ) { + log_msg(LOG_ERR, "Setting TCP Fast Open Failed: %s", strerror(errno)); + } +#endif + /* Listen to it... */ if (listen(nsd->tcp[i].s, TCP_BACKLOG) == -1) { log_msg(LOG_ERR, "can't listen: %s", strerror(errno)); @@ -1101,6 +1192,8 @@ server_shutdown(struct nsd *nsd) tsig_finalize(); #ifdef HAVE_SSL daemon_remote_delete(nsd->rc); /* ssl-delete secret keys */ + if (nsd->tls_ctx) + SSL_CTX_free(nsd->tls_ctx); #endif #ifdef MEMCLEAN /* OS collects memory pages */ @@ -1333,6 +1426,98 @@ server_send_soa_xfrd(struct nsd* nsd, in } } +#ifdef HAVE_SSL +void +log_crypto_err(const char* str) +{ + /* error:[error code]:[library name]:[function name]:[reason string] */ + char buf[128]; + unsigned long e; + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + log_msg(LOG_ERR, "%s crypto %s", str, buf); + while( (e=ERR_get_error()) ) { + ERR_error_string_n(e, buf, sizeof(buf)); + log_msg(LOG_ERR, "and additionally crypto %s", buf); + } +} + +SSL_CTX* +server_tls_ctx_create(struct nsd* nsd, char* verifypem) +{ + char *key, *pem; + SSL_CTX *ctx; + + ERR_load_crypto_strings(); + ERR_load_SSL_strings(); + OpenSSL_add_all_algorithms(); + (void)SSL_library_init(); + + key = nsd->options->tls_service_key; + pem = nsd->options->tls_service_pem; + + /* NOTE:This mimics the existing code in Unbound 1.5.1 by supporting SSL but + * raft-ietf-uta-tls-bcp-08 recommends only using TLSv1.2*/ + ctx = SSL_CTX_new(SSLv23_server_method()); + if(!ctx) { + log_crypto_err("could not SSL_CTX_new"); + return NULL; + } + /* no SSLv2 because has defects */ + if((SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2) & SSL_OP_NO_SSLv2) != SSL_OP_NO_SSLv2){ + log_crypto_err("could not set SSL_OP_NO_SSLv2"); + SSL_CTX_free(ctx); + return NULL; + } + if(!SSL_CTX_use_certificate_chain_file(ctx, pem)) { + log_msg(LOG_ERR, "error for cert file: %s", pem); + log_crypto_err("error in SSL_CTX use_certificate_chain_file"); + SSL_CTX_free(ctx); + return NULL; + } + if(!SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM)) { + log_msg(LOG_ERR, "error for private key file: %s", key); + log_crypto_err("Error in SSL_CTX use_PrivateKey_file"); + SSL_CTX_free(ctx); + return NULL; + } + if(!SSL_CTX_check_private_key(ctx)) { + log_msg(LOG_ERR, "error for key file: %s", key); + log_crypto_err("Error in SSL_CTX check_private_key"); + SSL_CTX_free(ctx); + return NULL; + } + + if(verifypem && verifypem[0]) { + if(!SSL_CTX_load_verify_locations(ctx, verifypem, NULL)) { + log_crypto_err("Error in SSL_CTX verify locations"); + SSL_CTX_free(ctx); + return NULL; + } + SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(verifypem)); + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + } + return ctx; +} + +/* check if tcp_handler_accept_data created for TLS dedicated port */ +int +using_tls_port(struct sockaddr* addr, const char* tls_port) +{ + in_port_t port; + + if (addr->sa_family == AF_INET) + port = ((struct sockaddr_in*)addr)->sin_port; +#ifndef HAVE_STRUCT_SOCKADDR_IN6 + else + port = ((struct sockaddr_in6*)addr)->sin6_port; +#endif /* HAVE_STRUCT_SOCKADDR_IN6 */ + if (atoi(tls_port) == ntohs(port)) + return 1; + + return 0; +} +#endif + /* pass timeout=-1 for blocking. Returns size, 0, -1(err), or -2(timeout) */ ssize_t block_read(struct nsd* nsd, int s, void* p, ssize_t sz, int timeout) @@ -2139,6 +2324,15 @@ server_child(struct nsd *nsd) &tcp_accept_handlers[i-from]; data->nsd = nsd; data->socket = &nsd->tcp[i]; +#ifdef HAVE_SSL + if (nsd->tls_ctx && nsd->options->tls_port && using_tls_port( + data->socket->addr->ai_addr, nsd->options->tls_port)) { + data->tls_accept = 1; + log_msg(LOG_NOTICE, "setup TCP for TLS service on interface %d", (int)i); + } + else + data->tls_accept = 0; +#endif event_set(handler, nsd->tcp[i].s, EV_PERSIST|EV_READ, handle_tcp_accept, data); if(event_base_set(event_base, handler) != 0) @@ -2504,11 +2698,39 @@ handle_udp(int fd, short event, void* ar } #endif /* defined(HAVE_SENDMMSG) && !defined(NONBLOCKING_IS_BROKEN) && defined(HAVE_RECVMMSG) */ +/* + * Setup an event for the tcp handler. + */ +static void +tcp_handler_setup_event(struct tcp_handler_data* data, void (*fn)(int, short, void *), + int fd, short event) +{ + struct timeval timeout; + struct event_base* ev_base; + + timeout.tv_sec = data->nsd->tcp_timeout; + timeout.tv_usec = 0L; + + ev_base = data->event.ev_base; + event_del(&data->event); + event_set(&data->event, fd, event, fn, data); + if(event_base_set(ev_base, &data->event) != 0) + log_msg(LOG_ERR, "event base set failed"); + if(event_add(&data->event, &timeout) != 0) + log_msg(LOG_ERR, "event add failed"); +} static void cleanup_tcp_handler(struct tcp_handler_data* data) { event_del(&data->event); +#ifdef HAVE_SSL + if(data->tls) { + SSL_shutdown(data->tls); + SSL_free(data->tls); + data->tls = NULL; + } +#endif close(data->event.ev_fd); /* @@ -2734,8 +2956,29 @@ handle_tcp_reading(int fd, short event, ev_base = data->event.ev_base; event_del(&data->event); - event_set(&data->event, fd, EV_PERSIST | EV_WRITE | EV_TIMEOUT, - handle_tcp_writing, data); +#ifdef HAVE_SSL + data->query->first_query = 0; +#ifdef USE_TO_BIT + if (data->nsd->tls_ctx && data->query->edns.tls_ok) { +#else + if (data->nsd->tls_ctx && data->query->tls_ok) { +#endif + data->tls = incoming_ssl_fd(data->nsd->tls_ctx, fd); + if(!data->tls) { + close(fd); + return; + } + data->shake_state = tls_hs_read; + event_set(&data->event, fd, EV_PERSIST | EV_READ | EV_TIMEOUT, + handle_tls_reading, data); + data->query_state = QUERY_PROCESSED; + } else { +#endif + event_set(&data->event, fd, EV_PERSIST | EV_READ | EV_TIMEOUT, + handle_tcp_reading, data); +#ifdef HAVE_SSL + } +#endif if(event_base_set(ev_base, &data->event) != 0) log_msg(LOG_ERR, "event base set tcpr failed"); if(event_add(&data->event, &timeout) != 0) @@ -2905,6 +3148,378 @@ handle_tcp_writing(int fd, short event, log_msg(LOG_ERR, "event add tcpw failed"); } +#ifdef HAVE_SSL +/** create SSL object and associate fd */ +static SSL* +incoming_ssl_fd(SSL_CTX* ctx, int fd) +{ + SSL* ssl = SSL_new((SSL_CTX*)ctx); + if(!ssl) { + log_crypto_err("could not SSL_new"); + return NULL; + } + SSL_set_accept_state(ssl); + (void)SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); + if(!SSL_set_fd(ssl, fd)) { + log_crypto_err("could not SSL_set_fd"); + SSL_free(ssl); + return NULL; + } + return ssl; +} + +/** TLS handshake to upgrade TCP connection */ +static int +tls_handshake(struct tcp_handler_data* data, int fd) +{ + int r; + + /* (continue to) setup the TLS connection */ + ERR_clear_error(); + r = SSL_do_handshake(data->tls); + + if(r != 1) { + int want = SSL_get_error(data->tls, r); + if(want == SSL_ERROR_WANT_READ) { + if(data->shake_state == tls_hs_read) { + /* try again later */ + return 1; + } + data->shake_state = tls_hs_read; + /* switch back to reading mode */ + tcp_handler_setup_event(data, handle_tls_reading, fd, EV_PERSIST|EV_TIMEOUT|EV_READ); + return 1; + } else if(want == SSL_ERROR_WANT_WRITE) { + if(data->shake_state == tls_hs_write) { + /* try again later */ + return 1; + } + data->shake_state = tls_hs_write; + /* switch back to writing mode */ + tcp_handler_setup_event(data, handle_tls_writing, fd, EV_PERSIST|EV_TIMEOUT|EV_WRITE); + return 1; + } else { + if(r == 0) + log_msg(LOG_ERR, "connection closed prematurely"); + cleanup_tcp_handler(data); + log_msg(LOG_ERR, "TLS failed"); + return 0; + } + } + + /* Use to log successful upgrade for testing - could be removed*/ + VERBOSITY(3, (LOG_INFO, "TLS handshake succeeded.")); + data->shake_state = tls_hs_none; + return 1; +} + +/** handle TLS reading of incoming query */ +static void +handle_tls_reading(int fd, short event, void* arg) +{ + struct tcp_handler_data *data = (struct tcp_handler_data *) arg; + ssize_t received; + + if ((event & EV_TIMEOUT)) { + /* Connection timed out. */ + cleanup_tcp_handler(data); + return; + } + + if (data->nsd->tcp_query_count > 0 && + data->query_count >= data->nsd->tcp_query_count) { + /* No more queries allowed on this tcp connection. */ + cleanup_tcp_handler(data); + return; + } + + assert((event & EV_READ)); + + if (data->bytes_transmitted == 0) { + query_reset(data->query, TCP_MAX_MESSAGE_LEN, 1); + } + + if(data->shake_state != tls_hs_none) { + if(!tls_handshake(data, fd)) + return; + if(data->shake_state != tls_hs_none) + return; + } + + /* + * Check if we received the leading packet length bytes yet. + */ + if(data->bytes_transmitted < sizeof(uint16_t)) { + ERR_clear_error(); + if((received=SSL_read(data->tls, (char *) &data->query->tcplen + + data->bytes_transmitted, + sizeof(uint16_t) - data->bytes_transmitted)) <= 0) { + int want = SSL_get_error(data->tls, received); + if(want == SSL_ERROR_ZERO_RETURN) { + cleanup_tcp_handler(data); + return; /* shutdown, closed */ + } else if(want == SSL_ERROR_WANT_READ) { + /* wants to be called again */ + return; + } + else if(want == SSL_ERROR_WANT_WRITE) { + /* switch to writing */ + data->shake_state = tls_hs_write; + tcp_handler_setup_event(data, handle_tls_writing, fd, EV_PERSIST | EV_WRITE | EV_TIMEOUT); + return; + } + cleanup_tcp_handler(data); + log_crypto_err("could not SSL_read"); + return; + } + + data->bytes_transmitted += received; + if (data->bytes_transmitted < sizeof(uint16_t)) { + /* + * Not done with the tcplen yet, wait for more + * data to become available. + */ + return; + } + + assert(data->bytes_transmitted == sizeof(uint16_t)); + + data->query->tcplen = ntohs(data->query->tcplen); + + /* + * Minimum query size is: + * + * Size of the header (12) + * + Root domain name (1) + * + Query class (2) + * + Query type (2) + */ + if (data->query->tcplen < QHEADERSZ + 1 + sizeof(uint16_t) + sizeof(uint16_t)) { + VERBOSITY(2, (LOG_WARNING, "packet too small, dropping tcp connection")); + cleanup_tcp_handler(data); + return; + } + + if (data->query->tcplen > data->query->maxlen) { + VERBOSITY(2, (LOG_WARNING, "insufficient tcp buffer, dropping connection")); + cleanup_tcp_handler(data); + return; + } + + buffer_set_limit(data->query->packet, data->query->tcplen); + } + + assert(buffer_remaining(data->query->packet) > 0); + + /* Read the (remaining) query data. */ + ERR_clear_error(); + received = SSL_read(data->tls, (void*)buffer_current(data->query->packet), + (int)buffer_remaining(data->query->packet)); + if(received <= 0) { + int want = SSL_get_error(data->tls, received); + if(want == SSL_ERROR_ZERO_RETURN) { + cleanup_tcp_handler(data); + return; /* shutdown, closed */ + } else if(want == SSL_ERROR_WANT_READ) { + /* wants to be called again */ + return; + } + else if(want == SSL_ERROR_WANT_WRITE) { + /* switch back writing */ + data->shake_state = tls_hs_write; + tcp_handler_setup_event(data, handle_tls_writing, fd, EV_PERSIST | EV_WRITE | EV_TIMEOUT); + return; + } + cleanup_tcp_handler(data); + log_crypto_err("could not SSL_read"); + return; + } + + data->bytes_transmitted += received; + buffer_skip(data->query->packet, received); + if (buffer_remaining(data->query->packet) > 0) { + /* + * Message not yet complete, wait for more data to + * become available. + */ + return; + } + + assert(buffer_position(data->query->packet) == data->query->tcplen); + + /* Account... */ +#ifndef INET6 + STATUP(data->nsd, ctcp); +#else + if (data->query->addr.ss_family == AF_INET) { + STATUP(data->nsd, ctcp); + } else if (data->query->addr.ss_family == AF_INET6) { + STATUP(data->nsd, ctcp6); + } +#endif + + /* We have a complete query, process it. */ + + /* tcp-query-count: handle query counter ++ */ + data->query_count++; + + buffer_flip(data->query->packet); + data->query_state = server_process_query(data->nsd, data->query); + if (data->query_state == QUERY_DISCARDED) { + /* Drop the packet and the entire connection... */ + STATUP(data->nsd, dropped); + cleanup_tcp_handler(data); + return; + } + + if (RCODE(data->query->packet) == RCODE_OK + && !AA(data->query->packet)) + { + STATUP(data->nsd, nona); + } + + query_add_optional(data->query, data->nsd); + + /* Switch to the tcp write handler. */ + buffer_flip(data->query->packet); + data->query->tcplen = buffer_remaining(data->query->packet); + data->bytes_transmitted = 0; + + tcp_handler_setup_event(data, handle_tls_writing, fd, EV_PERSIST | EV_WRITE | EV_TIMEOUT); + + /* see if we can write the answer right away(usually so,EAGAIN ifnot)*/ + handle_tls_writing(fd, EV_WRITE, data); +} + +/** handle TLS writing of outgoing response */ +static void +handle_tls_writing(int fd, short event, void* arg) +{ + struct tcp_handler_data *data = (struct tcp_handler_data *) arg; + ssize_t sent; + struct query *q = data->query; + buffer_type* write_buffer; + region_type* region; + + if ((event & EV_TIMEOUT)) { + /* Connection timed out. */ + cleanup_tcp_handler(data); + return; + } + + assert((event & EV_WRITE)); + + if(data->shake_state != tls_hs_none) { + if(!tls_handshake(data, fd)) + return; + if(data->shake_state != tls_hs_none) + return; + } + + (void)SSL_set_mode(data->tls, SSL_MODE_ENABLE_PARTIAL_WRITE); + + /* If we are writng the start of a message, we must include the length + * For now, create a new buffer with the length prepended. This is very + * inefficient and should be optimised. This could be done best by creating + * a wrapper for the underlying message buffer.*/ + write_buffer = NULL; + region = NULL; + if (data->bytes_transmitted == 0) { + region = region_create(xalloc, free); + write_buffer = buffer_create(region, buffer_limit(q->packet) + sizeof(q->tcplen)); + if (!write_buffer) { + return; + } + buffer_write_u16(write_buffer, q->tcplen); + buffer_write(write_buffer, buffer_current(q->packet), + (int)buffer_remaining(q->packet)); + buffer_flip(write_buffer); + } else { + write_buffer = q->packet; + } + + /* Write the response */ + ERR_clear_error(); + sent = SSL_write(data->tls, buffer_current(write_buffer), buffer_remaining(write_buffer)); + if(sent <= 0) { + int want = SSL_get_error(data->tls, sent); + if(want == SSL_ERROR_ZERO_RETURN) { + cleanup_tcp_handler(data); + /* closed */ + } else if(want == SSL_ERROR_WANT_READ) { + /* switch back to reading */ + data->shake_state = tls_hs_read; + tcp_handler_setup_event(data, handle_tls_reading, fd, EV_PERSIST | EV_READ | EV_TIMEOUT); + } else if(want != SSL_ERROR_WANT_WRITE) { + cleanup_tcp_handler(data); + log_crypto_err("could not SSL_write"); + } + if (data->bytes_transmitted == 0 && write_buffer != NULL) { + region_destroy(region); + } + return; + } + + buffer_skip(write_buffer, sent); + if(buffer_remaining(write_buffer) != 0) { + /* If not all sent, sync up the real buffer if it wasn't used.*/ + if (data->bytes_transmitted == 0 && (ssize_t)sent > sizeof(q->tcplen)) { + buffer_skip(q->packet, (ssize_t)sent - sizeof(q->tcplen)); + } + } + + if (data->bytes_transmitted == 0 && write_buffer != NULL) + region_destroy(region); + + data->bytes_transmitted += sent; + if (data->bytes_transmitted < q->tcplen + sizeof(q->tcplen)) { + /* + * Still more data to write when socket becomes + * writable again. + */ + return; + } + + assert(data->bytes_transmitted == q->tcplen + sizeof(q->tcplen)); + + if (data->query_state == QUERY_IN_AXFR) { + /* Continue processing AXFR and writing back results. */ + 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); + + /* Reset data. */ + buffer_flip(q->packet); + q->tcplen = buffer_remaining(q->packet); + data->bytes_transmitted = 0; + /* Reset to writing mode. */ + tcp_handler_setup_event(data, handle_tls_writing, fd, EV_PERSIST | EV_WRITE | EV_TIMEOUT); + + /* + * Write data if/when the socket is writable + * again. + */ + return; + } + } + + /* + * Done sending, wait for the next request to arrive on the + * TCP socket by installing the TCP read handler. + */ + if (data->nsd->tcp_query_count > 0 && + data->query_count >= data->nsd->tcp_query_count) { + + (void) shutdown(fd, SHUT_WR); + } + + data->bytes_transmitted = 0; + data->query->first_query = 0; + + tcp_handler_setup_event(data, handle_tls_reading, fd, EV_PERSIST | EV_READ | EV_TIMEOUT); +} +#endif static void handle_slowaccept_timeout(int ATTR_UNUSED(fd), short ATTR_UNUSED(event), @@ -3022,8 +3637,23 @@ handle_tcp_accept(int fd, short event, v timeout.tv_sec = tcp_data->tcp_timeout / 1000; timeout.tv_usec = (tcp_data->tcp_timeout % 1000)*1000; - event_set(&tcp_data->event, s, EV_PERSIST | EV_READ | EV_TIMEOUT, - handle_tcp_reading, tcp_data); +#ifdef HAVE_SSL + if (data->tls_accept) { + tcp_data->tls = incoming_ssl_fd(tcp_data->nsd->tls_ctx, s); + if(!tcp_data->tls) { + close(s); + return; + } + tcp_data->shake_state = tls_hs_read; + event_set(&tcp_data->event, s, EV_PERSIST | EV_READ | EV_TIMEOUT, + handle_tls_reading, tcp_data); + } else { +#endif + event_set(&tcp_data->event, s, EV_PERSIST | EV_READ | EV_TIMEOUT, + handle_tcp_reading, tcp_data); +#ifdef HAVE_SSL + } +#endif if(event_base_set(data->event.ev_base, &tcp_data->event) != 0) { log_msg(LOG_ERR, "cannot set tcp event base"); close(s); Index: nsd-4.1.26/doc/ChangeLog =================================================================== --- nsd-4.1.26.orig/doc/ChangeLog +++ nsd-4.1.26/doc/ChangeLog @@ -148,6 +148,11 @@ - Use accept4 to speed up answer of TCP queries, on Linux and FreeBSD and OpenBSD. +23 May 2018: Andreas via Sara + - Patch to add support for experimental STARTTLS implementation + - Patch to add support for experimental TCP Fast Open + - Patch to add support for tls service on a specified tls port + 22 May 2018: Wouter - Fix nsec3 hash of parent and child co-hosted nsec3 enabled zones. Index: nsd-4.1.26/configparser.y =================================================================== --- nsd-4.1.26.orig/configparser.y +++ nsd-4.1.26/configparser.y @@ -76,6 +76,7 @@ extern config_parser_state_type* cfg_par %token VAR_DNSTAP_SEND_IDENTITY VAR_DNSTAP_SEND_VERSION VAR_DNSTAP_IDENTITY %token VAR_DNSTAP_VERSION VAR_DNSTAP_LOG_AUTH_QUERY_MESSAGES %token VAR_DNSTAP_LOG_AUTH_RESPONSE_MESSAGES +%token VAR_TLS_SERVICE_KEY VAR_TLS_SERVICE_PEM VAR_TLS_PORT VAR_DO_STARTTLS %% toplevelvars: /* empty */ | toplevelvars toplevelvar ; @@ -107,6 +108,7 @@ content_server: server_ip_address | serv 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_tls_service_key | server_tls_service_pem | server_tls_port | server_do_starttls | server_minimal_responses | server_refuse_any | server_use_systemd; server_ip_address: VAR_IP_ADDRESS STRING { @@ -529,7 +531,32 @@ server_zonefiles_write: VAR_ZONEFILES_WR else cfg_parser->opt->zonefiles_write = atoi($2); } ; - +server_tls_service_key: VAR_TLS_SERVICE_KEY STRING + { + OUTYY(("P(server_tls_service_key:%s)\n", $2)); + cfg_parser->opt->tls_service_key = region_strdup(cfg_parser->opt->region, $2); + } + ; +server_tls_service_pem: VAR_TLS_SERVICE_PEM STRING + { + OUTYY(("P(server_tls_service_pem:%s)\n", $2)); + cfg_parser->opt->tls_service_pem = region_strdup(cfg_parser->opt->region, $2); + } + ; +server_tls_port: VAR_TLS_PORT STRING + { + OUTYY(("P(server_tls_port:%s)\n", $2)); + cfg_parser->opt->tls_port = region_strdup(cfg_parser->opt->region, $2); + } + ; +server_do_starttls: VAR_DO_STARTTLS STRING + { + OUTYY(("P(server_do_starttls:%s)\n", $2)); + if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0) + yyerror("expected yes or no."); + else cfg_parser->opt->do_starttls = (strcmp($2, "yes")==0); + } + ; rcstart: VAR_REMOTE_CONTROL { OUTYY(("\nP(remote-control:)\n"));