From 2ae06612e9c18abb5afaf25cd328fbefb555ecc6 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Thu, 21 Mar 2019 23:04:42 +0100 Subject: [PATCH 1/7] DNS Coookies support --- Makefile.in | 12 ++-- configlexer.lex | 2 + configparser.y | 28 +++++++- edns.c | 188 ++++++++++++++++++++++++++++++++++++++++++++++-- edns.h | 30 ++++++-- nsd-checkconf.c | 3 + nsd.c | 29 ++++++++ nsd.conf.5.in | 10 +++ nsd.h | 7 ++ options.c | 2 + options.h | 5 ++ query.c | 14 +++- query.h | 4 +- server.c | 31 ++++---- siphash.c | 165 ++++++++++++++++++++++++++++++++++++++++++ 15 files changed, 495 insertions(+), 35 deletions(-) create mode 100644 siphash.c diff --git a/Makefile.in b/Makefile.in index 4165f4296..d0a5a772a 100644 --- a/Makefile.in +++ b/Makefile.in @@ -74,7 +74,7 @@ EDIT = sed \ TARGETS=nsd nsd-checkconf nsd-checkzone nsd-control nsd.conf.sample nsd-control-setup.sh MANUALS=nsd.8 nsd-checkconf.8 nsd-checkzone.8 nsd-control.8 nsd.conf.5 -COMMON_OBJ=answer.o axfr.o buffer.o configlexer.o configparser.o dname.o dns.o edns.o iterated_hash.o lookup3.o namedb.o nsec3.o options.o packet.o query.o rbtree.o radtree.o rdata.o region-allocator.o rrl.o tsig.o tsig-openssl.o udb.o udbradtree.o udbzone.o util.o +COMMON_OBJ=answer.o axfr.o buffer.o configlexer.o configparser.o dname.o dns.o edns.o iterated_hash.o lookup3.o namedb.o nsec3.o options.o packet.o query.o rbtree.o radtree.o rdata.o region-allocator.o rrl.o siphash.o tsig.o tsig-openssl.o udb.o udbradtree.o udbzone.o util.o XFRD_OBJ=xfrd-disk.o xfrd-notify.o xfrd-tcp.o xfrd.o remote.o $(DNSTAP_OBJ) NSD_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) difffile.o ipc.o mini_event.o netio.o nsd.o server.o dbaccess.o dbcreate.o zlexer.o zonec.o zparser.o ALL_OBJ=$(NSD_OBJ) nsd-checkconf.o nsd-checkzone.o nsd-control.o nsd-mem.o @@ -371,6 +371,7 @@ answer.o: $(srcdir)/answer.c config.h $(srcdir)/answer.h $(srcdir)/dns.h $(srcdi axfr.o: $(srcdir)/axfr.c config.h $(srcdir)/axfr.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h \ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/query.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/radtree.h $(srcdir)/rbtree.h \ $(srcdir)/packet.h $(srcdir)/tsig.h $(srcdir)/options.h +bla.o: $(srcdir)/bla.c buffer.o: $(srcdir)/buffer.c config.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h $(srcdir)/util.h configlexer.o: configlexer.c $(srcdir)/configyyrename.h config.h $(srcdir)/options.h \ $(srcdir)/region-allocator.h $(srcdir)/rbtree.h configparser.h @@ -404,8 +405,7 @@ namedb.o: $(srcdir)/namedb.c config.h $(srcdir)/namedb.h $(srcdir)/dname.h $(src $(srcdir)/util.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/nsec3.h netio.o: $(srcdir)/netio.c config.h $(srcdir)/netio.h $(srcdir)/region-allocator.h $(srcdir)/util.h nsd.o: $(srcdir)/nsd.c config.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h \ - $(srcdir)/util.h $(srcdir)/options.h $(srcdir)/rbtree.h $(srcdir)/tsig.h $(srcdir)/dname.h $(srcdir)/remote.h $(srcdir)/xfrd-disk.h \ - $(srcdir)/dnstap/dnstap_collector.h + $(srcdir)/util.h $(srcdir)/options.h $(srcdir)/rbtree.h $(srcdir)/tsig.h $(srcdir)/dname.h $(srcdir)/remote.h $(srcdir)/xfrd-disk.h nsd-checkconf.o: $(srcdir)/nsd-checkconf.c config.h $(srcdir)/tsig.h $(srcdir)/buffer.h \ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/dname.h $(srcdir)/options.h $(srcdir)/rbtree.h $(srcdir)/rrl.h $(srcdir)/query.h \ $(srcdir)/namedb.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/packet.h @@ -444,8 +444,8 @@ rrl.o: $(srcdir)/rrl.c config.h $(srcdir)/rrl.h $(srcdir)/query.h $(srcdir)/name server.o: $(srcdir)/server.c config.h $(srcdir)/axfr.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h \ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/query.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/radtree.h $(srcdir)/rbtree.h \ $(srcdir)/packet.h $(srcdir)/tsig.h $(srcdir)/netio.h $(srcdir)/xfrd.h $(srcdir)/options.h $(srcdir)/xfrd-tcp.h $(srcdir)/xfrd-disk.h \ - $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/rrl.h \ - $(srcdir)/dnstap/dnstap_collector.h + $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/rrl.h +siphash.o: $(srcdir)/siphash.c tsig.o: $(srcdir)/tsig.c config.h $(srcdir)/tsig.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/dname.h \ $(srcdir)/tsig-openssl.h $(srcdir)/dns.h $(srcdir)/packet.h $(srcdir)/namedb.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/query.h $(srcdir)/nsd.h \ $(srcdir)/edns.h @@ -461,7 +461,7 @@ util.o: $(srcdir)/util.c config.h $(srcdir)/util.h $(srcdir)/region-allocator.h xfrd.o: $(srcdir)/xfrd.c config.h $(srcdir)/xfrd.h $(srcdir)/rbtree.h $(srcdir)/region-allocator.h $(srcdir)/namedb.h \ $(srcdir)/dname.h $(srcdir)/buffer.h $(srcdir)/util.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/options.h $(srcdir)/tsig.h $(srcdir)/xfrd-tcp.h \ $(srcdir)/xfrd-disk.h $(srcdir)/xfrd-notify.h $(srcdir)/netio.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/packet.h $(srcdir)/rdata.h \ - $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/rrl.h $(srcdir)/query.h $(srcdir)/dnstap/dnstap_collector.h + $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/rrl.h $(srcdir)/query.h xfrd-disk.o: $(srcdir)/xfrd-disk.c config.h $(srcdir)/xfrd-disk.h $(srcdir)/xfrd.h $(srcdir)/rbtree.h \ $(srcdir)/region-allocator.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/buffer.h $(srcdir)/util.h $(srcdir)/dns.h $(srcdir)/radtree.h \ $(srcdir)/options.h $(srcdir)/tsig.h $(srcdir)/nsd.h $(srcdir)/edns.h diff --git a/configlexer.lex b/configlexer.lex index d9178b84e..f5381d696 100644 --- a/configlexer.lex +++ b/configlexer.lex @@ -291,6 +291,8 @@ min-refresh-time{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MIN_REFRESH_TIM 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;} +answer-cookie{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ANSWER_COOKIE;} +cookie-secret{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_COOKIE_SECRET;} {NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++;} /* Quoted strings. Strip leading and ending quotes */ diff --git a/configparser.y b/configparser.y index d1e17b21b..0429df314 100644 --- a/configparser.y +++ b/configparser.y @@ -69,6 +69,7 @@ extern config_parser_state_type* cfg_parser; %token VAR_RRL_WHITELIST_RATELIMIT VAR_RRL_WHITELIST %token VAR_ZONEFILES_CHECK VAR_ZONEFILES_WRITE VAR_LOG_TIME_ASCII %token VAR_ROUND_ROBIN VAR_ZONESTATS VAR_REUSEPORT VAR_VERSION +%token VAR_ANSWER_COOKIE VAR_COOKIE_SECRET %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 @@ -107,7 +108,8 @@ 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_use_systemd; + server_minimal_responses | server_refuse_any | server_use_systemd | + server_answer_cookie | server_cookie_secret; server_ip_address: VAR_IP_ADDRESS STRING { OUTYY(("P(server_ip_address:%s)\n", $2)); @@ -529,6 +531,30 @@ server_zonefiles_write: VAR_ZONEFILES_WRITE STRING else cfg_parser->opt->zonefiles_write = atoi($2); } ; +server_answer_cookie: VAR_ANSWER_COOKIE STRING + { + OUTYY(("P(server_answer_cookie:%s)\n", $2)); + if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0) + yyerror("expected yes or no."); + else cfg_parser->opt->do_answer_cookie = (strcmp($2, "yes")==0); + } + ; +server_cookie_secret: VAR_COOKIE_SECRET STRING + { + uint8_t secret[32]; + int secret_len; + + OUTYY(("P(server_cookie_secret:%s)\n", $2)); + if ((secret_len = hex_pton($2, secret, sizeof(secret))) == -1 + || ( secret_len != 8 && secret_len != 16 + && secret_len != 24 && secret_len != 32)) + yyerror("expected 64, 128, 192 or 256 bit hex string"); + else { + cfg_parser->opt->cookie_secret = + region_strdup(cfg_parser->opt->region, $2); + } + } + ; rcstart: VAR_REMOTE_CONTROL { diff --git a/edns.c b/edns.c index 72a38d0a3..db6bc472e 100644 --- a/edns.c +++ b/edns.c @@ -11,6 +11,10 @@ #include "config.h" #include +#ifdef HAVE_SSL +#include +#include +#endif #include "dns.h" #include "edns.h" @@ -33,15 +37,17 @@ edns_init_data(edns_data_type *data, uint16_t max_length) data->error[3] = (max_length & 0xff00) >> 8; /* size_hi */ data->error[4] = max_length & 0x00ff; /* size_lo */ data->error[5] = 1; /* XXX Extended RCODE=BAD VERS */ + + /* COOKIE OPT HDR */ + data->cookie[0] = (COOKIE_CODE & 0xff00) >> 8; + data->cookie[1] = (COOKIE_CODE & 0x00ff); + data->cookie[2] = (24 & 0xff00) >> 8; + data->cookie[3] = (24 & 0x00ff); } void edns_init_nsid(edns_data_type *data, uint16_t nsid_len) { - /* add nsid length bytes */ - data->rdata_nsid[0] = ((OPT_HDR + nsid_len) & 0xff00) >> 8; /* length_hi */ - data->rdata_nsid[1] = ((OPT_HDR + nsid_len) & 0x00ff); /* length_lo */ - /* NSID OPT HDR */ data->nsid[0] = (NSID_CODE & 0xff00) >> 8; data->nsid[1] = (NSID_CODE & 0x00ff); @@ -58,6 +64,8 @@ edns_init_record(edns_record_type *edns) edns->opt_reserved_space = 0; edns->dnssec_ok = 0; edns->nsid = 0; + edns->cookie_status = COOKIE_NOT_PRESENT; + edns->cookie_len = 0; } /** handle a single edns option in the query */ @@ -81,6 +89,23 @@ edns_handle_option(uint16_t optcode, uint16_t optlen, buffer_type* packet, buffer_skip(packet, optlen); } break; + case COOKIE_CODE: + /* Cookies enabled? */ + if(nsd->do_answer_cookie) { + if (optlen == 8) + edns->cookie_status = COOKIE_INVALID; + else if (optlen < 16 || optlen > 40) + return 0; /* FORMERR */ + else + edns->cookie_status = COOKIE_UNVERIFIED; + + edns->cookie_len = optlen; + memcpy(edns->cookie, buffer_current(packet), optlen); + buffer_skip(packet, optlen); + edns->opt_reserved_space += OPT_HDR + 24; + } else { + buffer_skip(packet, optlen); + } default: buffer_skip(packet, optlen); break; @@ -155,3 +180,158 @@ edns_reserved_space(edns_record_type *edns) /* MIEK; when a pkt is too large?? */ return edns->status == EDNS_NOT_PRESENT ? 0 : (OPT_LEN + OPT_RDATA + edns->opt_reserved_space); } + +int siphash(const uint8_t *in, const size_t inlen, + const uint8_t *k, uint8_t *out, const size_t outlen); + +static int aes_server_cookie(uint8_t *server_cookie_out, + const uint8_t *secret, size_t secret_len, const uint8_t *in) +{ +#ifndef HAVE_SSL + (void)server_cookie_out; + (void)secret; + (void)secret_len; + (void)in; + return 0; +#else +# if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + EVP_CIPHER_CTX evp_ctx_spc; + EVP_CIPHER_CTX *evp_ctx = &evp_ctx_spc; +# else + EVP_CIPHER_CTX *evp_ctx; +# endif + const EVP_CIPHER *aes_ecb; + uint8_t out[16]; + int out_len, success; + + switch(secret_len) { + case 16: aes_ecb = EVP_aes_128_ecb(); break; + case 24: aes_ecb = EVP_aes_192_ecb(); break; + case 32: aes_ecb = EVP_aes_256_ecb(); break; + default: return 0; + } +# if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + EVP_CIPHER_CTX_init(evp_ctx); +# else + if (!(evp_ctx = EVP_CIPHER_CTX_new())) + return 0; +# endif + if((success = EVP_EncryptInit(evp_ctx, aes_ecb, secret, NULL) + && EVP_EncryptUpdate(evp_ctx, out, &out_len, in, 16) + && out_len == 16)) { + size_t i; + + for (i = 0; i < 8; i++) + server_cookie_out[i] = out[i] ^ out[i + 8]; + } +# if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + EVP_CIPHER_CTX_cleanup(evp_ctx); +# else + EVP_CIPHER_CTX_free(evp_ctx); +# endif + return success; +#endif +} + +/** RFC 1982 comparison, uses unsigned integers, and tries to avoid + * compiler optimization (eg. by avoiding a-b<0 comparisons), + * this routine matches compare_serial(), for SOA serial number checks */ +static int +compare_1982(uint32_t a, uint32_t b) +{ + /* for 32 bit values */ + const uint32_t cutoff = ((uint32_t) 1 << (32 - 1)); + + if (a == b) { + return 0; + } else if ((a < b && b - a < cutoff) || (a > b && a - b > cutoff)) { + return -1; + } else { + return 1; + } +} + +/** if we know that b is larger than a, return the difference between them, + * that is the distance between them. in RFC1982 arith */ +static uint32_t +subtract_1982(uint32_t a, uint32_t b) +{ + /* for 32 bit values */ + const uint32_t cutoff = ((uint32_t) 1 << (32 - 1)); + + if(a == b) + return 0; + if(a < b && b - a < cutoff) { + return b-a; + } + if(a > b && a - b > cutoff) { + return ((uint32_t)0xffffffff) - (a-b-1); + } + /* wrong case, b smaller than a */ + return 0; +} + +void cookie_verify(edns_record_type *data, struct nsd* nsd, uint32_t *now_p) +{ + uint8_t server_cookie[8]; + uint32_t cookie_time, now_uint32; +#ifdef HAVE_SSL + const EVP_CIPHER *aes_ecb; +#endif + /* We support only draft-sury-toorop-dns-cookies-algorithms sizes */ + if(data->cookie_len != 24) + return; + + if(data->cookie[8] != 1) + return; + + cookie_time = (data->cookie[12] << 24) + | (data->cookie[13] << 16) + | (data->cookie[14] << 8) + | data->cookie[15]; + + now_uint32 = *now_p ? *now_p : (*now_p = (uint32_t)time(NULL)); + + if(compare_1982(now_uint32, cookie_time) > 0) + if (subtract_1982(cookie_time, now_uint32) > 3600) { + return; + } else if (subtract_1982(now_uint32, cookie_time) > 300) + /* ignore cookies > 5 minutes in future */ + return; + + switch(data->cookie[9]) { + case 3: if (aes_server_cookie(server_cookie, nsd->cookie_secret, + nsd->cookie_secret_len, data->cookie)) + break; + else return; + case 4: if (nsd->cookie_secret_len != 8) + return; + siphash(data->cookie, 16, nsd->cookie_secret, server_cookie, 8); + break; + default: + return; + } + data->cookie_status = memcmp(data->cookie + 16, server_cookie, 8) == 0 + ? COOKIE_VALID : COOKIE_INVALID; +} + +void cookie_create(edns_record_type *data, struct nsd* nsd, uint32_t *now_p) +{ + uint32_t now_uint32 = *now_p ? *now_p : (*now_p = (uint32_t)time(NULL)); + + data->cookie[ 8] = 1; + data->cookie[ 9] = nsd->cookie_secret_len == 8 ? 4 : 3; + data->cookie[10] = 0; + data->cookie[11] = 0; + data->cookie[12] = (now_uint32 & 0xFF000000) >> 24; + data->cookie[13] = (now_uint32 & 0x00FF0000) >> 16; + data->cookie[14] = (now_uint32 & 0x0000FF00) >> 8; + data->cookie[15] = now_uint32 & 0x000000FF; + if (data->cookie[9] == 4) + siphash(data->cookie, 16, + nsd->cookie_secret, data->cookie + 16, 8); + else + aes_server_cookie(data->cookie + 16, nsd->cookie_secret, + nsd->cookie_secret_len, data->cookie); +} + diff --git a/edns.h b/edns.h index 9325beb24..678998bea 100644 --- a/edns.h +++ b/edns.h @@ -18,6 +18,7 @@ struct query; #define OPT_RDATA 2 /* holds the rdata length comes after OPT_LEN */ #define OPT_HDR 4U /* NSID opt header length */ #define NSID_CODE 3 /* nsid option code */ +#define COOKIE_CODE 10 /* COOKIE option code */ #define DNSSEC_OK_MASK 0x8000U /* do bit mask */ struct edns_data @@ -25,8 +26,8 @@ struct edns_data char ok[OPT_LEN]; char error[OPT_LEN]; char rdata_none[OPT_RDATA]; - char rdata_nsid[OPT_RDATA]; char nsid[OPT_HDR]; + char cookie[OPT_HDR]; }; typedef struct edns_data edns_data_type; @@ -39,14 +40,26 @@ enum edns_status }; typedef enum edns_status edns_status_type; +enum cookie_status +{ + COOKIE_NOT_PRESENT, + COOKIE_UNVERIFIED, + COOKIE_VALID, + COOKIE_INVALID +}; +typedef enum cookie_status cookie_status_type; + struct edns_record { - edns_status_type status; - size_t position; - size_t maxlen; - size_t opt_reserved_space; - int dnssec_ok; - int nsid; + edns_status_type status; + size_t position; + size_t maxlen; + size_t opt_reserved_space; + int dnssec_ok; + int nsid; + cookie_status_type cookie_status; + size_t cookie_len; + uint8_t cookie[40]; }; typedef struct edns_record edns_record_type; @@ -63,4 +76,7 @@ size_t edns_reserved_space(edns_record_type *data); void edns_init_nsid(edns_data_type *data, uint16_t nsid_len); +void cookie_verify(edns_record_type *data, struct nsd* nsd, uint32_t *now_p); +void cookie_create(edns_record_type *data, struct nsd* nsd, uint32_t *now_p); + #endif /* _EDNS_H_ */ diff --git a/nsd-checkconf.c b/nsd-checkconf.c index 20eb42a8c..523305683 100644 --- a/nsd-checkconf.c +++ b/nsd-checkconf.c @@ -536,6 +536,9 @@ config_test_print_server(nsd_options_type* opt) #endif printf("\tzonefiles-check: %s\n", opt->zonefiles_check?"yes":"no"); printf("\tzonefiles-write: %d\n", opt->zonefiles_write); + printf("\tanswer-cookie: %s\n", opt->do_answer_cookie?"yes":"no"); + if (opt->cookie_secret) + print_string_var("cookie-secret:", opt->cookie_secret); #ifdef USE_DNSTAP printf("\ndnstap:\n"); diff --git a/nsd.c b/nsd.c index 1075ef953..3aa0f1c8a 100644 --- a/nsd.c +++ b/nsd.c @@ -25,6 +25,9 @@ #include #endif /* HAVE_LOGIN_CAP_H */ #endif /* HAVE_SETUSERCONTEXT */ +#ifdef HAVE_OPENSSL_RAND_H +#include +#endif #include #include @@ -437,6 +440,8 @@ main(int argc, char *argv[]) nsd.grab_ip6_optional = 0; nsd.file_rotation_ok = 0; + nsd.do_answer_cookie = 1; + /* Set up our default identity to gethostname(2) */ if (gethostname(hostname, MAXHOSTNAMELEN) == 0) { nsd.identity = hostname; @@ -710,6 +715,30 @@ main(int argc, char *argv[]) #endif /* IPV6 MTU) */ #endif /* defined(INET6) */ + nsd.do_answer_cookie = nsd.options->do_answer_cookie; + if (nsd.cookie_secret_len != 0) + ; /* pass */ + + else if (nsd.options->cookie_secret) { + int len = hex_pton(nsd.options->cookie_secret, + nsd.cookie_secret, sizeof(nsd.cookie_secret)); + if (len != 8 && len != 16 && len != 24 && len != 32) { + error("A cookie secret must be a " + "64, 128, 192 or 256 bit hex string"); + } + nsd.cookie_secret_len = len; + } else { + int i; + + /* Calculate a new random secret */ + srandom(getpid() ^ time(NULL)); + nsd.cookie_secret_len = 8; +#if defined(HAVE_SSL) + if (!RAND_status() || !RAND_bytes(nsd.cookie_secret, 8)) +#endif + for (i = 0; i < 8; i++) + nsd.cookie_secret[i] = random_generate(256); + } if (nsd.nsid_len == 0 && nsd.options->nsid) { if (strlen(nsd.options->nsid) % 2 != 0) { error("the NSID must be a hex string of an even length."); diff --git a/nsd.conf.5.in b/nsd.conf.5.in index 551db3b27..be7338101 100644 --- a/nsd.conf.5.in +++ b/nsd.conf.5.in @@ -435,6 +435,16 @@ The max qps for query sorts for a source, which have been whitelisted. Default @ratelimit_default@ (with a suggested 2000 qps). With the rrl\-whitelist option you can set specific queries to receive this qps limit instead of the normal limit. With the value 0 the rate is unlimited. +.TP +.B answer\-cookie:\fR +Enable to answer to requests containig DNS Cookies as specified in RFC7873. +Default is yes. +.TP +.B cookie\-secret:\fR <64, 128, 192 or 256 bit hex string> +Server's in an Anycast deployment need to be able to verify each other's +Server Cookies. For this they need to share the secret used to construct and +verify the Server Cookies. Default is a 64 bits random secret generated at +startup time. .\" rrlend .SS "Remote Control" The diff --git a/nsd.h b/nsd.h index c900ca6cb..77ac1f9da 100644 --- a/nsd.h +++ b/nsd.h @@ -275,6 +275,13 @@ struct nsd /* ratelimit for errors, packet count */ unsigned int err_limit_count; + /** do answer with server cookie when request contained cookie option */ + int do_answer_cookie; + /** cookie secret */ + uint8_t cookie_secret[32]; + /** cookie secret length */ + size_t cookie_secret_len; + struct nsd_options* options; }; diff --git a/options.c b/options.c index 7d1fde98c..845b4c4b8 100644 --- a/options.c +++ b/options.c @@ -113,6 +113,8 @@ nsd_options_create(region_type* region) opt->zonefiles_write = ZONEFILES_WRITE_INTERVAL; else opt->zonefiles_write = 0; opt->xfrd_reload_timeout = 1; + opt->do_answer_cookie = 1; + opt->cookie_secret = NULL; opt->control_enable = 0; opt->control_interface = NULL; opt->control_port = NSD_CONTROL_PORT; diff --git a/options.h b/options.h index 1d30156bb..13120acb2 100644 --- a/options.h +++ b/options.h @@ -142,6 +142,11 @@ struct nsd_options { /** true to log dnstap AUTH_RESPONSE message events */ int dnstap_log_auth_response_messages; + /** do answer with server cookie when request contained cookie option */ + int do_answer_cookie; + /** cookie secret */ + char *cookie_secret; + region_type* region; }; diff --git a/query.c b/query.c index e2a56e9ae..3aabccd47 100644 --- a/query.c +++ b/query.c @@ -1358,7 +1358,7 @@ query_prepare_response(query_type *q) * */ query_state_type -query_process(query_type *q, nsd_type *nsd) +query_process(query_type *q, nsd_type *nsd, uint32_t *now_p) { /* The query... */ nsd_rc_type rc; @@ -1482,6 +1482,9 @@ query_process(query_type *q, nsd_type *nsd) return query_error(q, NSD_RC_OK); } + if (q->edns.cookie_status == COOKIE_UNVERIFIED) + cookie_verify(&q->edns, nsd, now_p); + query_prepare_response(q); if (q->qclass != CLASS_IN && q->qclass != CLASS_ANY) { @@ -1507,7 +1510,7 @@ query_process(query_type *q, nsd_type *nsd) } void -query_add_optional(query_type *q, nsd_type *nsd) +query_add_optional(query_type *q, nsd_type *nsd, uint32_t *now_p) { struct edns_data *edns = &nsd->edns_ipv4; #if defined(INET6) @@ -1539,6 +1542,13 @@ query_add_optional(query_type *q, nsd_type *nsd) /* nsid payload */ buffer_write(q->packet, nsd->nsid, nsd->nsid_len); } + if(q->edns.cookie_status != COOKIE_NOT_PRESENT) { + /* cookie opt header */ + buffer_write(q->packet, edns->cookie, OPT_HDR); + /* cookie payload */ + cookie_create(&q->edns, nsd, now_p); + buffer_write(q->packet, q->edns.cookie, 24); + } } ARCOUNT_SET(q->packet, ARCOUNT(q->packet) + 1); STATUP(nsd, edns); diff --git a/query.h b/query.h index 0a511f593..6f35de8aa 100644 --- a/query.h +++ b/query.h @@ -184,7 +184,7 @@ void query_reset(query_type *query, size_t maxlen, int is_tcp); /* * Process a query and write the response in the query I/O buffer. */ -query_state_type query_process(query_type *q, nsd_type *nsd); +query_state_type query_process(query_type *q, nsd_type *nsd, uint32_t *now_p); /* * Prepare the query structure for writing the response. The packet @@ -197,7 +197,7 @@ void query_prepare_response(query_type *q); /* * Add EDNS0 information to the response if required. */ -void query_add_optional(query_type *q, nsd_type *nsd); +void query_add_optional(query_type *q, nsd_type *nsd, uint32_t *now_p); /* * Write an error response into the query structure with the indicated diff --git a/server.c b/server.c index 162baf487..2eb1f1553 100644 --- a/server.c +++ b/server.c @@ -2015,23 +2015,24 @@ server_main(struct nsd *nsd) } static query_state_type -server_process_query(struct nsd *nsd, struct query *query) +server_process_query(struct nsd *nsd, struct query *query, uint32_t *now_p) { - return query_process(query, nsd); + return query_process(query, nsd, now_p); } static query_state_type -server_process_query_udp(struct nsd *nsd, struct query *query) +server_process_query_udp(struct nsd *nsd, struct query *query, uint32_t *now_p) { #ifdef RATELIMIT - if(query_process(query, nsd) != QUERY_DISCARDED) { - if(rrl_process_query(query)) + if(query_process(query, nsd, now_p) != QUERY_DISCARDED) { + if(query->edns.cookie_status != COOKIE_VALID + && rrl_process_query(query)) return rrl_slip(query); else return QUERY_PROCESSED; } return QUERY_DISCARDED; #else - return query_process(query, nsd); + return query_process(query, nsd, now_p); #endif } @@ -2262,6 +2263,7 @@ handle_udp(int fd, short event, void* arg) struct udp_handler_data *data = (struct udp_handler_data *) arg; int received, sent, recvcount, i; struct query *q; + uint32_t now = 0; if (!(event & EV_READ)) { return; @@ -2311,7 +2313,7 @@ handle_udp(int fd, short event, void* arg) #endif /* USE_DNSTAP */ /* Process and answer the query... */ - if (server_process_query_udp(data->nsd, q) != QUERY_DISCARDED) { + if (server_process_query_udp(data->nsd, q, &now) != QUERY_DISCARDED) { if (RCODE(q->packet) == RCODE_OK && !AA(q->packet)) { STATUP(data->nsd, nona); ZTATUP(data->nsd, q->zone, nona); @@ -2326,7 +2328,7 @@ handle_udp(int fd, short event, void* arg) #endif /* Add EDNS0 and TSIG info if necessary. */ - query_add_optional(q, data->nsd); + query_add_optional(q, data->nsd, &now); buffer_flip(q->packet); iovecs[i].iov_len = buffer_remaining(q->packet); @@ -2406,6 +2408,7 @@ handle_udp(int fd, short event, void* arg) int i; #endif /* NONBLOCKING_IS_BROKEN */ struct query *q; + uint32_t now = 0; #if (defined(NONBLOCKING_IS_BROKEN) || !defined(HAVE_RECVMMSG)) q = data->query; #endif @@ -2481,7 +2484,7 @@ handle_udp(int fd, short event, void* arg) #endif /* USE_DNSTAP */ /* Process and answer the query... */ - if (server_process_query_udp(data->nsd, q) != QUERY_DISCARDED) { + if (server_process_query_udp(data->nsd, q, &now) != QUERY_DISCARDED) { if (RCODE(q->packet) == RCODE_OK && !AA(q->packet)) { STATUP(data->nsd, nona); ZTATUP(data->nsd, q->zone, nona); @@ -2496,7 +2499,7 @@ handle_udp(int fd, short event, void* arg) #endif /* Add EDNS0 and TSIG info if necessary. */ - query_add_optional(q, data->nsd); + query_add_optional(q, data->nsd, &now); buffer_flip(q->packet); @@ -2578,6 +2581,7 @@ handle_tcp_reading(int fd, short event, void* arg) ssize_t received; struct event_base* ev_base; struct timeval timeout; + uint32_t now; if ((event & EV_TIMEOUT)) { /* Connection timed out. */ @@ -2729,7 +2733,7 @@ handle_tcp_reading(int fd, short event, void* arg) dt_collector_submit_auth_query(data->nsd, &data->query->addr, data->query->addrlen, data->query->tcp, data->query->packet); #endif /* USE_DNSTAP */ - data->query_state = server_process_query(data->nsd, data->query); + data->query_state = server_process_query(data->nsd, data->query, &now); if (data->query_state == QUERY_DISCARDED) { /* Drop the packet and the entire connection... */ STATUP(data->nsd, dropped); @@ -2759,7 +2763,7 @@ handle_tcp_reading(int fd, short event, void* arg) #endif #endif /* USE_ZONE_STATS */ - query_add_optional(data->query, data->nsd); + query_add_optional(data->query, data->nsd, &now); /* Switch to the tcp write handler. */ buffer_flip(data->query->packet); @@ -2794,6 +2798,7 @@ handle_tcp_writing(int fd, short event, void* arg) struct query *q = data->query; struct timeval timeout; struct event_base* ev_base; + uint32_t now = 0; if ((event & EV_TIMEOUT)) { /* Connection timed out. */ @@ -2897,7 +2902,7 @@ handle_tcp_writing(int fd, short event, void* arg) buffer_clear(q->packet); data->query_state = query_axfr(data->nsd, q); if (data->query_state != QUERY_PROCESSED) { - query_add_optional(data->query, data->nsd); + query_add_optional(data->query, data->nsd, &now); /* Reset data. */ buffer_flip(q->packet); diff --git a/siphash.c b/siphash.c new file mode 100644 index 000000000..d69f4b579 --- /dev/null +++ b/siphash.c @@ -0,0 +1,165 @@ +/* + SipHash reference C implementation + + Copyright (c) 2012-2016 Jean-Philippe Aumasson + + Copyright (c) 2012-2014 Daniel J. Bernstein + + To the extent possible under law, the author(s) have dedicated all copyright + and related and neighboring rights to this software to the public domain + worldwide. This software is distributed without any warranty. + + You should have received a copy of the CC0 Public Domain Dedication along + with + this software. If not, see + . + */ +#include +#include +#include +#include + +/* default: SipHash-2-4 */ +#define cROUNDS 2 +#define dROUNDS 4 + +#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) + +#define U32TO8_LE(p, v) \ + (p)[0] = (uint8_t)((v)); \ + (p)[1] = (uint8_t)((v) >> 8); \ + (p)[2] = (uint8_t)((v) >> 16); \ + (p)[3] = (uint8_t)((v) >> 24); + +#define U64TO8_LE(p, v) \ + U32TO8_LE((p), (uint32_t)((v))); \ + U32TO8_LE((p) + 4, (uint32_t)((v) >> 32)); + +#define U8TO64_LE(p) \ + (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) | \ + ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | \ + ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | \ + ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56)) + +#define SIPROUND \ + do { \ + v0 += v1; \ + v1 = ROTL(v1, 13); \ + v1 ^= v0; \ + v0 = ROTL(v0, 32); \ + v2 += v3; \ + v3 = ROTL(v3, 16); \ + v3 ^= v2; \ + v0 += v3; \ + v3 = ROTL(v3, 21); \ + v3 ^= v0; \ + v2 += v1; \ + v1 = ROTL(v1, 17); \ + v1 ^= v2; \ + v2 = ROTL(v2, 32); \ + } while (0) + +#ifdef DEBUG +#define TRACE \ + do { \ + printf("(%3d) v0 %08x %08x\n", (int)inlen, (uint32_t)(v0 >> 32), \ + (uint32_t)v0); \ + printf("(%3d) v1 %08x %08x\n", (int)inlen, (uint32_t)(v1 >> 32), \ + (uint32_t)v1); \ + printf("(%3d) v2 %08x %08x\n", (int)inlen, (uint32_t)(v2 >> 32), \ + (uint32_t)v2); \ + printf("(%3d) v3 %08x %08x\n", (int)inlen, (uint32_t)(v3 >> 32), \ + (uint32_t)v3); \ + } while (0) +#else +#define TRACE +#endif + +int siphash(const uint8_t *in, const size_t inlen, const uint8_t *k, + uint8_t *out, const size_t outlen) { + + assert((outlen == 8) || (outlen == 16)); + uint64_t v0 = 0x736f6d6570736575ULL; + uint64_t v1 = 0x646f72616e646f6dULL; + uint64_t v2 = 0x6c7967656e657261ULL; + uint64_t v3 = 0x7465646279746573ULL; + uint64_t k0 = U8TO64_LE(k); + uint64_t k1 = U8TO64_LE(k + 8); + uint64_t m; + int i; + const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t)); + const int left = inlen & 7; + uint64_t b = ((uint64_t)inlen) << 56; + v3 ^= k1; + v2 ^= k0; + v1 ^= k1; + v0 ^= k0; + + if (outlen == 16) + v1 ^= 0xee; + + for (; in != end; in += 8) { + m = U8TO64_LE(in); + v3 ^= m; + + TRACE; + for (i = 0; i < cROUNDS; ++i) + SIPROUND; + + v0 ^= m; + } + + switch (left) { + case 7: + b |= ((uint64_t)in[6]) << 48; + case 6: + b |= ((uint64_t)in[5]) << 40; + case 5: + b |= ((uint64_t)in[4]) << 32; + case 4: + b |= ((uint64_t)in[3]) << 24; + case 3: + b |= ((uint64_t)in[2]) << 16; + case 2: + b |= ((uint64_t)in[1]) << 8; + case 1: + b |= ((uint64_t)in[0]); + break; + case 0: + break; + } + + v3 ^= b; + + TRACE; + for (i = 0; i < cROUNDS; ++i) + SIPROUND; + + v0 ^= b; + + if (outlen == 16) + v2 ^= 0xee; + else + v2 ^= 0xff; + + TRACE; + for (i = 0; i < dROUNDS; ++i) + SIPROUND; + + b = v0 ^ v1 ^ v2 ^ v3; + U64TO8_LE(out, b); + + if (outlen == 8) + return 0; + + v1 ^= 0xdd; + + TRACE; + for (i = 0; i < dROUNDS; ++i) + SIPROUND; + + b = v0 ^ v1 ^ v2 ^ v3; + U64TO8_LE(out + 8, b); + + return 0; +} From 9062452a7cc8836a7bf4438e7837e25862e40b70 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Fri, 22 Mar 2019 10:10:10 +0100 Subject: [PATCH 2/7] Rename do_answer_cookie in answer_cookie for config --- configparser.y | 2 +- nsd-checkconf.c | 2 +- nsd.c | 2 +- options.c | 2 +- options.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/configparser.y b/configparser.y index 0429df314..34d330908 100644 --- a/configparser.y +++ b/configparser.y @@ -536,7 +536,7 @@ server_answer_cookie: VAR_ANSWER_COOKIE STRING OUTYY(("P(server_answer_cookie:%s)\n", $2)); if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0) yyerror("expected yes or no."); - else cfg_parser->opt->do_answer_cookie = (strcmp($2, "yes")==0); + else cfg_parser->opt->answer_cookie = (strcmp($2, "yes")==0); } ; server_cookie_secret: VAR_COOKIE_SECRET STRING diff --git a/nsd-checkconf.c b/nsd-checkconf.c index 523305683..bde147343 100644 --- a/nsd-checkconf.c +++ b/nsd-checkconf.c @@ -536,7 +536,7 @@ config_test_print_server(nsd_options_type* opt) #endif printf("\tzonefiles-check: %s\n", opt->zonefiles_check?"yes":"no"); printf("\tzonefiles-write: %d\n", opt->zonefiles_write); - printf("\tanswer-cookie: %s\n", opt->do_answer_cookie?"yes":"no"); + printf("\tanswer-cookie: %s\n", opt->answer_cookie?"yes":"no"); if (opt->cookie_secret) print_string_var("cookie-secret:", opt->cookie_secret); diff --git a/nsd.c b/nsd.c index 3aa0f1c8a..d9b004528 100644 --- a/nsd.c +++ b/nsd.c @@ -715,7 +715,7 @@ main(int argc, char *argv[]) #endif /* IPV6 MTU) */ #endif /* defined(INET6) */ - nsd.do_answer_cookie = nsd.options->do_answer_cookie; + nsd.do_answer_cookie = nsd.options->answer_cookie; if (nsd.cookie_secret_len != 0) ; /* pass */ diff --git a/options.c b/options.c index 845b4c4b8..c541e3607 100644 --- a/options.c +++ b/options.c @@ -113,7 +113,7 @@ nsd_options_create(region_type* region) opt->zonefiles_write = ZONEFILES_WRITE_INTERVAL; else opt->zonefiles_write = 0; opt->xfrd_reload_timeout = 1; - opt->do_answer_cookie = 1; + opt->answer_cookie = 1; opt->cookie_secret = NULL; opt->control_enable = 0; opt->control_interface = NULL; diff --git a/options.h b/options.h index 13120acb2..482fa5b3a 100644 --- a/options.h +++ b/options.h @@ -143,7 +143,7 @@ struct nsd_options { int dnstap_log_auth_response_messages; /** do answer with server cookie when request contained cookie option */ - int do_answer_cookie; + int answer_cookie; /** cookie secret */ char *cookie_secret; From a2762d1897573222814861923f62c52e87405506 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Sat, 23 Mar 2019 15:08:22 +0100 Subject: [PATCH 3/7] SipHash2.4 takes 128 bit keys! --- configparser.y | 5 ++--- edns.c | 4 ++-- nsd.c | 11 ++++++----- nsd.conf.5.in | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/configparser.y b/configparser.y index 34d330908..d1a2f3341 100644 --- a/configparser.y +++ b/configparser.y @@ -546,9 +546,8 @@ server_cookie_secret: VAR_COOKIE_SECRET STRING OUTYY(("P(server_cookie_secret:%s)\n", $2)); if ((secret_len = hex_pton($2, secret, sizeof(secret))) == -1 - || ( secret_len != 8 && secret_len != 16 - && secret_len != 24 && secret_len != 32)) - yyerror("expected 64, 128, 192 or 256 bit hex string"); + || ( secret_len != 16 && secret_len != 24 && secret_len != 32)) + yyerror("expected 128, 192 or 256 bit hex string"); else { cfg_parser->opt->cookie_secret = region_strdup(cfg_parser->opt->region, $2); diff --git a/edns.c b/edns.c index db6bc472e..b1e009fa4 100644 --- a/edns.c +++ b/edns.c @@ -304,7 +304,7 @@ void cookie_verify(edns_record_type *data, struct nsd* nsd, uint32_t *now_p) nsd->cookie_secret_len, data->cookie)) break; else return; - case 4: if (nsd->cookie_secret_len != 8) + case 4: if (nsd->cookie_secret_len != 16) return; siphash(data->cookie, 16, nsd->cookie_secret, server_cookie, 8); break; @@ -320,7 +320,7 @@ void cookie_create(edns_record_type *data, struct nsd* nsd, uint32_t *now_p) uint32_t now_uint32 = *now_p ? *now_p : (*now_p = (uint32_t)time(NULL)); data->cookie[ 8] = 1; - data->cookie[ 9] = nsd->cookie_secret_len == 8 ? 4 : 3; + data->cookie[ 9] = nsd->cookie_secret_len == 16 ? 4 : 3; data->cookie[10] = 0; data->cookie[11] = 0; data->cookie[12] = (now_uint32 & 0xFF000000) >> 24; diff --git a/nsd.c b/nsd.c index d9b004528..4af60edda 100644 --- a/nsd.c +++ b/nsd.c @@ -722,9 +722,9 @@ main(int argc, char *argv[]) else if (nsd.options->cookie_secret) { int len = hex_pton(nsd.options->cookie_secret, nsd.cookie_secret, sizeof(nsd.cookie_secret)); - if (len != 8 && len != 16 && len != 24 && len != 32) { + if (len != 16 && len != 24 && len != 32) { error("A cookie secret must be a " - "64, 128, 192 or 256 bit hex string"); + "128, 192 or 256 bit hex string"); } nsd.cookie_secret_len = len; } else { @@ -732,11 +732,12 @@ main(int argc, char *argv[]) /* Calculate a new random secret */ srandom(getpid() ^ time(NULL)); - nsd.cookie_secret_len = 8; + nsd.cookie_secret_len = 16; #if defined(HAVE_SSL) - if (!RAND_status() || !RAND_bytes(nsd.cookie_secret, 8)) + if (!RAND_status() + || !RAND_bytes(nsd.cookie_secret, nsd.cookie_secret_len)) #endif - for (i = 0; i < 8; i++) + for (i = 0; i < nsd.cookie_secret_len; i++) nsd.cookie_secret[i] = random_generate(256); } if (nsd.nsid_len == 0 && nsd.options->nsid) { diff --git a/nsd.conf.5.in b/nsd.conf.5.in index be7338101..51fe4ec3b 100644 --- a/nsd.conf.5.in +++ b/nsd.conf.5.in @@ -440,10 +440,10 @@ With the value 0 the rate is unlimited. Enable to answer to requests containig DNS Cookies as specified in RFC7873. Default is yes. .TP -.B cookie\-secret:\fR <64, 128, 192 or 256 bit hex string> +.B cookie\-secret:\fR <128, 192 or 256 bit hex string> Server's in an Anycast deployment need to be able to verify each other's Server Cookies. For this they need to share the secret used to construct and -verify the Server Cookies. Default is a 64 bits random secret generated at +verify the Server Cookies. Default is a 128 bits random secret generated at startup time. .\" rrlend .SS "Remote Control" From 84f0638d4260e39d803d901de9dcc4f0c114efe7 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Sat, 20 Jul 2019 13:55:43 -0400 Subject: [PATCH 4/7] Server cookies up-to-date with draft-sury-toorop-dnsop-server-cookies-01 --- Makefile.in | 8 +- edns.c | 146 +++++------ edns.h | 4 +- install-sh | 677 ++++++++++++++++++++++++++++++++++++---------------- query.c | 4 +- server.c | 8 +- 6 files changed, 539 insertions(+), 308 deletions(-) diff --git a/Makefile.in b/Makefile.in index d0a5a772a..6071485c6 100644 --- a/Makefile.in +++ b/Makefile.in @@ -371,7 +371,6 @@ answer.o: $(srcdir)/answer.c config.h $(srcdir)/answer.h $(srcdir)/dns.h $(srcdi axfr.o: $(srcdir)/axfr.c config.h $(srcdir)/axfr.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h \ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/query.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/radtree.h $(srcdir)/rbtree.h \ $(srcdir)/packet.h $(srcdir)/tsig.h $(srcdir)/options.h -bla.o: $(srcdir)/bla.c buffer.o: $(srcdir)/buffer.c config.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h $(srcdir)/util.h configlexer.o: configlexer.c $(srcdir)/configyyrename.h config.h $(srcdir)/options.h \ $(srcdir)/region-allocator.h $(srcdir)/rbtree.h configparser.h @@ -405,7 +404,8 @@ namedb.o: $(srcdir)/namedb.c config.h $(srcdir)/namedb.h $(srcdir)/dname.h $(src $(srcdir)/util.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/nsec3.h netio.o: $(srcdir)/netio.c config.h $(srcdir)/netio.h $(srcdir)/region-allocator.h $(srcdir)/util.h nsd.o: $(srcdir)/nsd.c config.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h \ - $(srcdir)/util.h $(srcdir)/options.h $(srcdir)/rbtree.h $(srcdir)/tsig.h $(srcdir)/dname.h $(srcdir)/remote.h $(srcdir)/xfrd-disk.h + $(srcdir)/util.h $(srcdir)/options.h $(srcdir)/rbtree.h $(srcdir)/tsig.h $(srcdir)/dname.h $(srcdir)/remote.h $(srcdir)/xfrd-disk.h \ + $(srcdir)/dnstap/dnstap_collector.h nsd-checkconf.o: $(srcdir)/nsd-checkconf.c config.h $(srcdir)/tsig.h $(srcdir)/buffer.h \ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/dname.h $(srcdir)/options.h $(srcdir)/rbtree.h $(srcdir)/rrl.h $(srcdir)/query.h \ $(srcdir)/namedb.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/packet.h @@ -444,7 +444,7 @@ rrl.o: $(srcdir)/rrl.c config.h $(srcdir)/rrl.h $(srcdir)/query.h $(srcdir)/name server.o: $(srcdir)/server.c config.h $(srcdir)/axfr.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h \ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/query.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/radtree.h $(srcdir)/rbtree.h \ $(srcdir)/packet.h $(srcdir)/tsig.h $(srcdir)/netio.h $(srcdir)/xfrd.h $(srcdir)/options.h $(srcdir)/xfrd-tcp.h $(srcdir)/xfrd-disk.h \ - $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/rrl.h + $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/dnstap/dnstap_collector.h $(srcdir)/rrl.h siphash.o: $(srcdir)/siphash.c tsig.o: $(srcdir)/tsig.c config.h $(srcdir)/tsig.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/dname.h \ $(srcdir)/tsig-openssl.h $(srcdir)/dns.h $(srcdir)/packet.h $(srcdir)/namedb.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/query.h $(srcdir)/nsd.h \ @@ -461,7 +461,7 @@ util.o: $(srcdir)/util.c config.h $(srcdir)/util.h $(srcdir)/region-allocator.h xfrd.o: $(srcdir)/xfrd.c config.h $(srcdir)/xfrd.h $(srcdir)/rbtree.h $(srcdir)/region-allocator.h $(srcdir)/namedb.h \ $(srcdir)/dname.h $(srcdir)/buffer.h $(srcdir)/util.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/options.h $(srcdir)/tsig.h $(srcdir)/xfrd-tcp.h \ $(srcdir)/xfrd-disk.h $(srcdir)/xfrd-notify.h $(srcdir)/netio.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/packet.h $(srcdir)/rdata.h \ - $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/rrl.h $(srcdir)/query.h + $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/rrl.h $(srcdir)/query.h $(srcdir)/dnstap/dnstap_collector.h xfrd-disk.o: $(srcdir)/xfrd-disk.c config.h $(srcdir)/xfrd-disk.h $(srcdir)/xfrd.h $(srcdir)/rbtree.h \ $(srcdir)/region-allocator.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/buffer.h $(srcdir)/util.h $(srcdir)/dns.h $(srcdir)/radtree.h \ $(srcdir)/options.h $(srcdir)/tsig.h $(srcdir)/nsd.h $(srcdir)/edns.h diff --git a/edns.c b/edns.c index b1e009fa4..fdf1c65e6 100644 --- a/edns.c +++ b/edns.c @@ -184,55 +184,6 @@ edns_reserved_space(edns_record_type *edns) int siphash(const uint8_t *in, const size_t inlen, const uint8_t *k, uint8_t *out, const size_t outlen); -static int aes_server_cookie(uint8_t *server_cookie_out, - const uint8_t *secret, size_t secret_len, const uint8_t *in) -{ -#ifndef HAVE_SSL - (void)server_cookie_out; - (void)secret; - (void)secret_len; - (void)in; - return 0; -#else -# if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) - EVP_CIPHER_CTX evp_ctx_spc; - EVP_CIPHER_CTX *evp_ctx = &evp_ctx_spc; -# else - EVP_CIPHER_CTX *evp_ctx; -# endif - const EVP_CIPHER *aes_ecb; - uint8_t out[16]; - int out_len, success; - - switch(secret_len) { - case 16: aes_ecb = EVP_aes_128_ecb(); break; - case 24: aes_ecb = EVP_aes_192_ecb(); break; - case 32: aes_ecb = EVP_aes_256_ecb(); break; - default: return 0; - } -# if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) - EVP_CIPHER_CTX_init(evp_ctx); -# else - if (!(evp_ctx = EVP_CIPHER_CTX_new())) - return 0; -# endif - if((success = EVP_EncryptInit(evp_ctx, aes_ecb, secret, NULL) - && EVP_EncryptUpdate(evp_ctx, out, &out_len, in, 16) - && out_len == 16)) { - size_t i; - - for (i = 0; i < 8; i++) - server_cookie_out[i] = out[i] ^ out[i + 8]; - } -# if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) - EVP_CIPHER_CTX_cleanup(evp_ctx); -# else - EVP_CIPHER_CTX_free(evp_ctx); -# endif - return success; -#endif -} - /** RFC 1982 comparison, uses unsigned integers, and tries to avoid * compiler optimization (eg. by avoiding a-b<0 comparisons), * this routine matches compare_serial(), for SOA serial number checks */ @@ -271,67 +222,78 @@ subtract_1982(uint32_t a, uint32_t b) return 0; } -void cookie_verify(edns_record_type *data, struct nsd* nsd, uint32_t *now_p) +void cookie_verify(query_type *q, struct nsd* nsd, uint32_t *now_p) { - uint8_t server_cookie[8]; + uint8_t hash[8]; uint32_t cookie_time, now_uint32; -#ifdef HAVE_SSL - const EVP_CIPHER *aes_ecb; -#endif - /* We support only draft-sury-toorop-dns-cookies-algorithms sizes */ - if(data->cookie_len != 24) + + /* We support only draft-sury-toorop-dnsop-server-cookies sizes */ + if(q->edns.cookie_len != 24) return; - if(data->cookie[8] != 1) + if(q->edns.cookie[8] != 1) return; - cookie_time = (data->cookie[12] << 24) - | (data->cookie[13] << 16) - | (data->cookie[14] << 8) - | data->cookie[15]; + cookie_time = (q->edns.cookie[12] << 24) + | (q->edns.cookie[13] << 16) + | (q->edns.cookie[14] << 8) + | q->edns.cookie[15]; now_uint32 = *now_p ? *now_p : (*now_p = (uint32_t)time(NULL)); if(compare_1982(now_uint32, cookie_time) > 0) + /* ignore cookies > 1 hour in past */ if (subtract_1982(cookie_time, now_uint32) > 3600) { return; } else if (subtract_1982(now_uint32, cookie_time) > 300) /* ignore cookies > 5 minutes in future */ return; - - switch(data->cookie[9]) { - case 3: if (aes_server_cookie(server_cookie, nsd->cookie_secret, - nsd->cookie_secret_len, data->cookie)) - break; - else return; - case 4: if (nsd->cookie_secret_len != 16) - return; - siphash(data->cookie, 16, nsd->cookie_secret, server_cookie, 8); - break; - default: - return; +#ifdef INET6 + if (q->addr.ss_family == AF_INET6) { + memcpy( q->edns.cookie + 16 + , &((struct sockaddr_in6 *)&q->addr)->sin6_addr, 16); + siphash(q->edns.cookie, 32, nsd->cookie_secret, hash, 8); + } else { + memcpy( q->edns.cookie + 16 + , &((struct sockaddr_in *)&q->addr)->sin_addr, 4); + siphash(q->edns.cookie, 20, nsd->cookie_secret, hash, 8); } - data->cookie_status = memcmp(data->cookie + 16, server_cookie, 8) == 0 - ? COOKIE_VALID : COOKIE_INVALID; +#else + memcpy( q->edns.cookie + 16, &q->addr->sin_addr, 4); + siphash(q->edns.cookie, 20, nsd->cookie_secret, hash, 8); +#endif + q->edns.cookie_status = memcmp(q->edns.cookie + 16, hash, 8) + ? COOKIE_INVALID : COOKIE_VALID; } -void cookie_create(edns_record_type *data, struct nsd* nsd, uint32_t *now_p) +void cookie_create(query_type *q, struct nsd* nsd, uint32_t *now_p) { - uint32_t now_uint32 = *now_p ? *now_p : (*now_p = (uint32_t)time(NULL)); - - data->cookie[ 8] = 1; - data->cookie[ 9] = nsd->cookie_secret_len == 16 ? 4 : 3; - data->cookie[10] = 0; - data->cookie[11] = 0; - data->cookie[12] = (now_uint32 & 0xFF000000) >> 24; - data->cookie[13] = (now_uint32 & 0x00FF0000) >> 16; - data->cookie[14] = (now_uint32 & 0x0000FF00) >> 8; - data->cookie[15] = now_uint32 & 0x000000FF; - if (data->cookie[9] == 4) - siphash(data->cookie, 16, - nsd->cookie_secret, data->cookie + 16, 8); - else - aes_server_cookie(data->cookie + 16, nsd->cookie_secret, - nsd->cookie_secret_len, data->cookie); + uint8_t hash[8]; + uint32_t now_uint32 = *now_p + ? *now_p : (*now_p = (uint32_t)time(NULL)); + + q->edns.cookie[ 8] = 1; + q->edns.cookie[ 9] = 0; + q->edns.cookie[10] = 0; + q->edns.cookie[11] = 0; + q->edns.cookie[12] = (now_uint32 & 0xFF000000) >> 24; + q->edns.cookie[13] = (now_uint32 & 0x00FF0000) >> 16; + q->edns.cookie[14] = (now_uint32 & 0x0000FF00) >> 8; + q->edns.cookie[15] = now_uint32 & 0x000000FF; +#ifdef INET6 + if (q->addr.ss_family == AF_INET6) { + memcpy( q->edns.cookie + 16 + , &((struct sockaddr_in6 *)&q->addr)->sin6_addr, 16); + siphash(q->edns.cookie, 32, nsd->cookie_secret, hash, 8); + } else { + memcpy( q->edns.cookie + 16 + , &((struct sockaddr_in *)&q->addr)->sin_addr, 4); + siphash(q->edns.cookie, 20, nsd->cookie_secret, hash, 8); + } +#else + memcpy( q->edns.cookie + 16, &q->addr->sin_addr, 4); + siphash(q->edns.cookie, 20, nsd->cookie_secret, hash, 8); +#endif + memcpy(q->edns.cookie + 16, hash, 8); } diff --git a/edns.h b/edns.h index 678998bea..38e666230 100644 --- a/edns.h +++ b/edns.h @@ -76,7 +76,7 @@ size_t edns_reserved_space(edns_record_type *data); void edns_init_nsid(edns_data_type *data, uint16_t nsid_len); -void cookie_verify(edns_record_type *data, struct nsd* nsd, uint32_t *now_p); -void cookie_create(edns_record_type *data, struct nsd* nsd, uint32_t *now_p); +void cookie_verify(struct query *q, struct nsd* nsd, uint32_t *now_p); +void cookie_create(struct query *q, struct nsd* nsd, uint32_t *now_p); #endif /* _EDNS_H_ */ diff --git a/install-sh b/install-sh index e9de23842..8175c640f 100755 --- a/install-sh +++ b/install-sh @@ -1,251 +1,518 @@ #!/bin/sh -# # install - install a program, script, or datafile -# This comes from X11R5 (mit/util/scripts/install.sh). + +scriptversion=2018-03-11.20; # UTC + +# This originates from X11R5 (mit/util/scripts/install.sh), which was +# later released in X11R6 (xc/config/util/install.sh) with the +# following copyright and license. +# +# Copyright (C) 1994 X Consortium +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- +# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # -# Copyright 1991 by the Massachusetts Institute of Technology +# Except as contained in this notice, the name of the X Consortium shall not +# be used in advertising or otherwise to promote the sale, use or other deal- +# ings in this Software without prior written authorization from the X Consor- +# tium. # -# Permission to use, copy, modify, distribute, and sell this software and its -# documentation for any purpose is hereby granted without fee, provided that -# the above copyright notice appear in all copies and that both that -# copyright notice and this permission notice appear in supporting -# documentation, and that the name of M.I.T. not be used in advertising or -# publicity pertaining to distribution of the software without specific, -# written prior permission. M.I.T. makes no representations about the -# suitability of this software for any purpose. It is provided "as is" -# without express or implied warranty. +# +# FSF changes to this file are in the public domain. # # Calling this script install-sh is preferred over install.sh, to prevent -# `make' implicit rules from creating a file called install from it +# 'make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written -# from scratch. It can only install one file at a time, a restriction -# shared with many OS's install programs. +# from scratch. + +tab=' ' +nl=' +' +IFS=" $tab$nl" +# Set DOITPROG to "echo" to test this script. -# set DOITPROG to echo to test this script +doit=${DOITPROG-} +doit_exec=${doit:-exec} -# Don't use :- since 4.3BSD and earlier shells don't like it. -doit="${DOITPROG-}" +# Put in absolute file names if you don't have them in your path; +# or use environment vars. +chgrpprog=${CHGRPPROG-chgrp} +chmodprog=${CHMODPROG-chmod} +chownprog=${CHOWNPROG-chown} +cmpprog=${CMPPROG-cmp} +cpprog=${CPPROG-cp} +mkdirprog=${MKDIRPROG-mkdir} +mvprog=${MVPROG-mv} +rmprog=${RMPROG-rm} +stripprog=${STRIPPROG-strip} -# put in absolute paths if you don't have them in your path; or use env. vars. +posix_mkdir= -mvprog="${MVPROG-mv}" -cpprog="${CPPROG-cp}" -chmodprog="${CHMODPROG-chmod}" -chownprog="${CHOWNPROG-chown}" -chgrpprog="${CHGRPPROG-chgrp}" -stripprog="${STRIPPROG-strip}" -rmprog="${RMPROG-rm}" -mkdirprog="${MKDIRPROG-mkdir}" +# Desired mode of installed file. +mode=0755 -transformbasename="" -transform_arg="" -instcmd="$mvprog" -chmodcmd="$chmodprog 0755" -chowncmd="" -chgrpcmd="" -stripcmd="" +chgrpcmd= +chmodcmd=$chmodprog +chowncmd= +mvcmd=$mvprog rmcmd="$rmprog -f" -mvcmd="$mvprog" -src="" -dst="" -dir_arg="" - -while [ x"$1" != x ]; do - case $1 in - -c) instcmd="$cpprog" - shift - continue;; - - -d) dir_arg=true - shift - continue;; - - -m) chmodcmd="$chmodprog $2" - shift - shift - continue;; - - -o) chowncmd="$chownprog $2" - shift - shift - continue;; - - -g) chgrpcmd="$chgrpprog $2" - shift - shift - continue;; - - -s) stripcmd="$stripprog" - shift - continue;; - - -t=*) transformarg=`echo $1 | sed 's/-t=//'` - shift - continue;; - - -b=*) transformbasename=`echo $1 | sed 's/-b=//'` - shift - continue;; - - *) if [ x"$src" = x ] - then - src=$1 - else - # this colon is to work around a 386BSD /bin/sh bug - : - dst=$1 - fi - shift - continue;; - esac -done +stripcmd= -if [ x"$src" = x ] -then - echo "install: no input file specified" - exit 1 -else - true -fi +src= +dst= +dir_arg= +dst_arg= -if [ x"$dir_arg" != x ]; then - dst=$src - src="" - - if [ -d $dst ]; then - instcmd=: - chmodcmd="" - else - instcmd=mkdir - fi -else - -# Waiting for this to be detected by the "$instcmd $src $dsttmp" command -# might cause directories to be created, which would be especially bad -# if $src (and thus $dsttmp) contains '*'. - - if [ -f $src -o -d $src ] - then - true - else - echo "install: $src does not exist" - exit 1 - fi - - if [ x"$dst" = x ] - then - echo "install: no destination specified" - exit 1 - else - true - fi - -# If destination is a directory, append the input filename; if your system -# does not like double slashes in filenames, you may need to add some logic - - if [ -d $dst ] - then - dst="$dst"/`basename $src` - else - true - fi -fi +copy_on_change=false +is_target_a_directory=possibly -## this sed command emulates the dirname command -dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` +usage="\ +Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE + or: $0 [OPTION]... SRCFILES... DIRECTORY + or: $0 [OPTION]... -t DIRECTORY SRCFILES... + or: $0 [OPTION]... -d DIRECTORIES... -# Make sure that the destination directory exists. -# this part is taken from Noah Friedman's mkinstalldirs script +In the 1st form, copy SRCFILE to DSTFILE. +In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. +In the 4th, create DIRECTORIES. -# Skip lots of stat calls in the usual case. -if [ ! -d "$dstdir" ]; then -defaultIFS=' -' -IFS="${IFS-${defaultIFS}}" +Options: + --help display this help and exit. + --version display version info and exit. -oIFS="${IFS}" -# Some sh's can't handle IFS=/ for some reason. -IFS='%' -set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` -IFS="${oIFS}" + -c (ignored) + -C install only if different (preserve the last data modification time) + -d create directories instead of installing files. + -g GROUP $chgrpprog installed files to GROUP. + -m MODE $chmodprog installed files to MODE. + -o USER $chownprog installed files to USER. + -s $stripprog installed files. + -t DIRECTORY install into DIRECTORY. + -T report an error if DSTFILE is a directory. -pathcomp='' +Environment variables override the default commands: + CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG + RMPROG STRIPPROG +" -while [ $# -ne 0 ] ; do - pathcomp="${pathcomp}${1}" - shift +while test $# -ne 0; do + case $1 in + -c) ;; - if [ ! -d "${pathcomp}" ] ; - then - $mkdirprog "${pathcomp}" - else - true - fi + -C) copy_on_change=true;; - pathcomp="${pathcomp}/" -done -fi + -d) dir_arg=true;; -if [ x"$dir_arg" != x ] -then - $doit $instcmd $dst && + -g) chgrpcmd="$chgrpprog $2" + shift;; - if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && - if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && - if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && - if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi -else + --help) echo "$usage"; exit $?;; -# If we're going to rename the final executable, determine the name now. + -m) mode=$2 + case $mode in + *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) + echo "$0: invalid mode: $mode" >&2 + exit 1;; + esac + shift;; - if [ x"$transformarg" = x ] - then - dstfile=`basename $dst` - else - dstfile=`basename $dst $transformbasename | - sed $transformarg`$transformbasename - fi + -o) chowncmd="$chownprog $2" + shift;; -# don't allow the sed command to completely eliminate the filename + -s) stripcmd=$stripprog;; - if [ x"$dstfile" = x ] - then - dstfile=`basename $dst` - else - true - fi + -t) + is_target_a_directory=always + dst_arg=$2 + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + shift;; -# Make a temp file name in the proper directory. + -T) is_target_a_directory=never;; - dsttmp=$dstdir/#inst.$$# + --version) echo "$0 $scriptversion"; exit $?;; -# Move or copy the file name to the temp name + --) shift + break;; - $doit $instcmd $src $dsttmp && + -*) echo "$0: invalid option: $1" >&2 + exit 1;; - trap "rm -f ${dsttmp}" 0 && + *) break;; + esac + shift +done -# and set any options; do chmod last to preserve setuid bits +# We allow the use of options -d and -T together, by making -d +# take the precedence; this is for compatibility with GNU install. -# If any of these fail, we abort the whole thing. If we want to -# ignore errors from any of these, just make sure not to ignore -# errors from the above "$doit $instcmd $src $dsttmp" command. +if test -n "$dir_arg"; then + if test -n "$dst_arg"; then + echo "$0: target directory not allowed when installing a directory." >&2 + exit 1 + fi +fi - if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && - if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && - if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && - if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && +if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then + # When -d is used, all remaining arguments are directories to create. + # When -t is used, the destination is already specified. + # Otherwise, the last argument is the destination. Remove it from $@. + for arg + do + if test -n "$dst_arg"; then + # $@ is not empty: it contains at least $arg. + set fnord "$@" "$dst_arg" + shift # fnord + fi + shift # arg + dst_arg=$arg + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + done +fi -# Now rename the file to the real destination. +if test $# -eq 0; then + if test -z "$dir_arg"; then + echo "$0: no input file specified." >&2 + exit 1 + fi + # It's OK to call 'install-sh -d' without argument. + # This can happen when creating conditional directories. + exit 0 +fi + +if test -z "$dir_arg"; then + if test $# -gt 1 || test "$is_target_a_directory" = always; then + if test ! -d "$dst_arg"; then + echo "$0: $dst_arg: Is not a directory." >&2 + exit 1 + fi + fi +fi - $doit $rmcmd -f $dstdir/$dstfile && - $doit $mvcmd $dsttmp $dstdir/$dstfile +if test -z "$dir_arg"; then + do_exit='(exit $ret); exit $ret' + trap "ret=129; $do_exit" 1 + trap "ret=130; $do_exit" 2 + trap "ret=141; $do_exit" 13 + trap "ret=143; $do_exit" 15 + + # Set umask so as not to create temps with too-generous modes. + # However, 'strip' requires both read and write access to temps. + case $mode in + # Optimize common cases. + *644) cp_umask=133;; + *755) cp_umask=22;; + + *[0-7]) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw='% 200' + fi + cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; + *) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw=,u+rw + fi + cp_umask=$mode$u_plus_rw;; + esac +fi -fi && +for src +do + # Protect names problematic for 'test' and other utilities. + case $src in + -* | [=\(\)!]) src=./$src;; + esac + + if test -n "$dir_arg"; then + dst=$src + dstdir=$dst + test -d "$dstdir" + dstdir_status=$? + else + + # Waiting for this to be detected by the "$cpprog $src $dsttmp" command + # might cause directories to be created, which would be especially bad + # if $src (and thus $dsttmp) contains '*'. + if test ! -f "$src" && test ! -d "$src"; then + echo "$0: $src does not exist." >&2 + exit 1 + fi + + if test -z "$dst_arg"; then + echo "$0: no destination specified." >&2 + exit 1 + fi + dst=$dst_arg + + # If destination is a directory, append the input filename. + if test -d "$dst"; then + if test "$is_target_a_directory" = never; then + echo "$0: $dst_arg: Is a directory" >&2 + exit 1 + fi + dstdir=$dst + dstbase=`basename "$src"` + case $dst in + */) dst=$dst$dstbase;; + *) dst=$dst/$dstbase;; + esac + dstdir_status=0 + else + dstdir=`dirname "$dst"` + test -d "$dstdir" + dstdir_status=$? + fi + fi + + case $dstdir in + */) dstdirslash=$dstdir;; + *) dstdirslash=$dstdir/;; + esac + + obsolete_mkdir_used=false + + if test $dstdir_status != 0; then + case $posix_mkdir in + '') + # Create intermediate dirs using mode 755 as modified by the umask. + # This is like FreeBSD 'install' as of 1997-10-28. + umask=`umask` + case $stripcmd.$umask in + # Optimize common cases. + *[2367][2367]) mkdir_umask=$umask;; + .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; + + *[0-7]) + mkdir_umask=`expr $umask + 22 \ + - $umask % 100 % 40 + $umask % 20 \ + - $umask % 10 % 4 + $umask % 2 + `;; + *) mkdir_umask=$umask,go-w;; + esac + + # With -d, create the new directory with the user-specified mode. + # Otherwise, rely on $mkdir_umask. + if test -n "$dir_arg"; then + mkdir_mode=-m$mode + else + mkdir_mode= + fi + + posix_mkdir=false + case $umask in + *[123567][0-7][0-7]) + # POSIX mkdir -p sets u+wx bits regardless of umask, which + # is incompatible with FreeBSD 'install' when (umask & 300) != 0. + ;; + *) + # Note that $RANDOM variable is not portable (e.g. dash); Use it + # here however when possible just to lower collision chance. + tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ + + trap 'ret=$?; rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null; exit $ret' 0 + + # Because "mkdir -p" follows existing symlinks and we likely work + # directly in world-writeable /tmp, make sure that the '$tmpdir' + # directory is successfully created first before we actually test + # 'mkdir -p' feature. + if (umask $mkdir_umask && + $mkdirprog $mkdir_mode "$tmpdir" && + exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 + then + if test -z "$dir_arg" || { + # Check for POSIX incompatibilities with -m. + # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or + # other-writable bit of parent directory when it shouldn't. + # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. + test_tmpdir="$tmpdir/a" + ls_ld_tmpdir=`ls -ld "$test_tmpdir"` + case $ls_ld_tmpdir in + d????-?r-*) different_mode=700;; + d????-?--*) different_mode=755;; + *) false;; + esac && + $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { + ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` + test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" + } + } + then posix_mkdir=: + fi + rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" + else + # Remove any dirs left behind by ancient mkdir implementations. + rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null + fi + trap '' 0;; + esac;; + esac + if + $posix_mkdir && ( + umask $mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" + ) + then : + else + + # The umask is ridiculous, or mkdir does not conform to POSIX, + # or it failed possibly due to a race condition. Create the + # directory the slow way, step by step, checking for races as we go. + + case $dstdir in + /*) prefix='/';; + [-=\(\)!]*) prefix='./';; + *) prefix='';; + esac + + oIFS=$IFS + IFS=/ + set -f + set fnord $dstdir + shift + set +f + IFS=$oIFS + + prefixes= + + for d + do + test X"$d" = X && continue + + prefix=$prefix$d + if test -d "$prefix"; then + prefixes= + else + if $posix_mkdir; then + (umask=$mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break + # Don't fail if two instances are running concurrently. + test -d "$prefix" || exit 1 + else + case $prefix in + *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; + *) qprefix=$prefix;; + esac + prefixes="$prefixes '$qprefix'" + fi + fi + prefix=$prefix/ + done + + if test -n "$prefixes"; then + # Don't fail if two instances are running concurrently. + (umask $mkdir_umask && + eval "\$doit_exec \$mkdirprog $prefixes") || + test -d "$dstdir" || exit 1 + obsolete_mkdir_used=true + fi + fi + fi + + if test -n "$dir_arg"; then + { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && + { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || + test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 + else + + # Make a couple of temp file names in the proper directory. + dsttmp=${dstdirslash}_inst.$$_ + rmtmp=${dstdirslash}_rm.$$_ + + # Trap to clean up those temp files at exit. + trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 + + # Copy the file name to the temp name. + (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && + + # and set any options; do chmod last to preserve setuid bits. + # + # If any of these fail, we abort the whole thing. If we want to + # ignore errors from any of these, just make sure not to ignore + # errors from the above "$doit $cpprog $src $dsttmp" command. + # + { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && + { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && + { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && + + # If -C, don't bother to copy if it wouldn't change the file. + if $copy_on_change && + old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && + new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && + set -f && + set X $old && old=:$2:$4:$5:$6 && + set X $new && new=:$2:$4:$5:$6 && + set +f && + test "$old" = "$new" && + $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 + then + rm -f "$dsttmp" + else + # Rename the file to the real destination. + $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || + + # The rename failed, perhaps because mv can't rename something else + # to itself, or perhaps because mv is so ancient that it does not + # support -f. + { + # Now remove or move aside any old file at destination location. + # We try this two ways since rm can't unlink itself on some + # systems and the destination file might be busy for other + # reasons. In this case, the final cleanup might fail but the new + # file should still install successfully. + { + test ! -f "$dst" || + $doit $rmcmd -f "$dst" 2>/dev/null || + { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && + { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } + } || + { echo "$0: cannot unlink or rename $dst" >&2 + (exit 1); exit 1 + } + } && + + # Now rename the file to the real destination. + $doit $mvcmd "$dsttmp" "$dst" + } + fi || exit 1 + + trap '' 0 + fi +done -exit 0 +# Local variables: +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/query.c b/query.c index 787879842..f4112f16c 100644 --- a/query.c +++ b/query.c @@ -1487,7 +1487,7 @@ query_process(query_type *q, nsd_type *nsd, uint32_t *now_p) } if (q->edns.cookie_status == COOKIE_UNVERIFIED) - cookie_verify(&q->edns, nsd, now_p); + cookie_verify(q, nsd, now_p); query_prepare_response(q); @@ -1550,7 +1550,7 @@ query_add_optional(query_type *q, nsd_type *nsd, uint32_t *now_p) /* cookie opt header */ buffer_write(q->packet, edns->cookie, OPT_HDR); /* cookie payload */ - cookie_create(&q->edns, nsd, now_p); + cookie_create(q, nsd, now_p); buffer_write(q->packet, q->edns.cookie, 24); } } diff --git a/server.c b/server.c index 4971b83ad..3a9da89d1 100644 --- a/server.c +++ b/server.c @@ -3637,6 +3637,7 @@ handle_tls_reading(int fd, short event, void* arg) { struct tcp_handler_data *data = (struct tcp_handler_data *) arg; ssize_t received; + uint32_t now = 0; if ((event & EV_TIMEOUT)) { /* Connection timed out. */ @@ -3786,7 +3787,7 @@ handle_tls_reading(int fd, short event, void* arg) dt_collector_submit_auth_query(data->nsd, &data->query->addr, data->query->addrlen, data->query->tcp, data->query->packet); #endif /* USE_DNSTAP */ - data->query_state = server_process_query(data->nsd, data->query); + data->query_state = server_process_query(data->nsd, data->query, &now); if (data->query_state == QUERY_DISCARDED) { /* Drop the packet and the entire connection... */ STATUP(data->nsd, dropped); @@ -3816,7 +3817,7 @@ handle_tls_reading(int fd, short event, void* arg) #endif #endif /* USE_ZONE_STATS */ - query_add_optional(data->query, data->nsd); + query_add_optional(data->query, data->nsd, &now); /* Switch to the tcp write handler. */ buffer_flip(data->query->packet); @@ -3854,6 +3855,7 @@ handle_tls_writing(int fd, short event, void* arg) * TCP length in front of the packet, like writev. */ static buffer_type* global_tls_temp_buffer = NULL; buffer_type* write_buffer; + uint32_t now = 0; if ((event & EV_TIMEOUT)) { /* Connection timed out. */ @@ -3938,7 +3940,7 @@ handle_tls_writing(int fd, short event, void* arg) buffer_clear(q->packet); data->query_state = query_axfr(data->nsd, q); if (data->query_state != QUERY_PROCESSED) { - query_add_optional(data->query, data->nsd); + query_add_optional(data->query, data->nsd, &now); /* Reset data. */ buffer_flip(q->packet); From 6541f26ae5fbe2ebb07a7328d00b43b851b5236b Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Sat, 20 Jul 2019 14:02:04 -0400 Subject: [PATCH 5/7] Siphash is only alg which has 128 bit secret --- nsd.c | 4 ++-- nsd.conf.5.in | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nsd.c b/nsd.c index aa29e92b2..06ac684c4 100644 --- a/nsd.c +++ b/nsd.c @@ -725,9 +725,9 @@ main(int argc, char *argv[]) else if (nsd.options->cookie_secret) { int len = hex_pton(nsd.options->cookie_secret, nsd.cookie_secret, sizeof(nsd.cookie_secret)); - if (len != 16 && len != 24 && len != 32) { + if (len != 16) { error("A cookie secret must be a " - "128, 192 or 256 bit hex string"); + "128 bit hex string"); } nsd.cookie_secret_len = len; } else { diff --git a/nsd.conf.5.in b/nsd.conf.5.in index 87e8b5350..f8bcf2665 100644 --- a/nsd.conf.5.in +++ b/nsd.conf.5.in @@ -455,10 +455,10 @@ With the value 0 the rate is unlimited. Enable to answer to requests containig DNS Cookies as specified in RFC7873. Default is yes. .TP -.B cookie\-secret:\fR <128, 192 or 256 bit hex string> +.B cookie\-secret:\fR <128 bit hex string> Server's in an Anycast deployment need to be able to verify each other's Server Cookies. For this they need to share the secret used to construct and -verify the Server Cookies. Default is a 128 bits random secret generated at +verify the Server Cookies. Default is a 128 bits random secret generated at startup time. .\" rrlend .TP From 21865ff36b7f0c0c6f732da47ea8422b8fb6bfd1 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Sat, 20 Jul 2019 14:15:29 -0400 Subject: [PATCH 6/7] Don't overwrite what we have to compare --- edns.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/edns.c b/edns.c index fdf1c65e6..f3b94c8ac 100644 --- a/edns.c +++ b/edns.c @@ -224,7 +224,7 @@ subtract_1982(uint32_t a, uint32_t b) void cookie_verify(query_type *q, struct nsd* nsd, uint32_t *now_p) { - uint8_t hash[8]; + uint8_t hash[8], hash2verify[8]; uint32_t cookie_time, now_uint32; /* We support only draft-sury-toorop-dnsop-server-cookies sizes */ @@ -248,6 +248,7 @@ void cookie_verify(query_type *q, struct nsd* nsd, uint32_t *now_p) } else if (subtract_1982(now_uint32, cookie_time) > 300) /* ignore cookies > 5 minutes in future */ return; + memcpy(hash2verify, q->edns.cookie + 16, 8); #ifdef INET6 if (q->addr.ss_family == AF_INET6) { memcpy( q->edns.cookie + 16 @@ -262,7 +263,7 @@ void cookie_verify(query_type *q, struct nsd* nsd, uint32_t *now_p) memcpy( q->edns.cookie + 16, &q->addr->sin_addr, 4); siphash(q->edns.cookie, 20, nsd->cookie_secret, hash, 8); #endif - q->edns.cookie_status = memcmp(q->edns.cookie + 16, hash, 8) + q->edns.cookie_status = memcmp(hash2verify, hash, 8) ? COOKIE_INVALID : COOKIE_VALID; } From 14faeb19ba7226e29e10d42d0624dea5daafea9e Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Tue, 28 Jul 2020 13:48:29 +0200 Subject: [PATCH 7/7] Small fixes --- edns.c | 5 +++-- nsd.c | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/edns.c b/edns.c index 86475bcad..8d67a5bb1 100644 --- a/edns.c +++ b/edns.c @@ -106,6 +106,7 @@ edns_handle_option(uint16_t optcode, uint16_t optlen, buffer_type* packet, } else { buffer_skip(packet, optlen); } + break; default: buffer_skip(packet, optlen); break; @@ -244,9 +245,9 @@ void cookie_verify(query_type *q, struct nsd* nsd, uint32_t *now_p) now_uint32 = *now_p ? *now_p : (*now_p = (uint32_t)time(NULL)); - if(compare_1982(now_uint32, cookie_time) > 0) + if(compare_1982(now_uint32, cookie_time) > 0) { /* ignore cookies > 1 hour in past */ - if (subtract_1982(cookie_time, now_uint32) > 3600) { + if (subtract_1982(cookie_time, now_uint32) > 3600) return; } else if (subtract_1982(now_uint32, cookie_time) > 300) /* ignore cookies > 5 minutes in future */ diff --git a/nsd.c b/nsd.c index ff42ba6ca..35f08f87a 100644 --- a/nsd.c +++ b/nsd.c @@ -1139,7 +1139,7 @@ main(int argc, char *argv[]) } nsd.cookie_secret_len = len; } else { - int i; + size_t i; /* Calculate a new random secret */ srandom(getpid() ^ time(NULL));