diff --git a/README b/README index 4154cce9..6f559385 100644 --- a/README +++ b/README @@ -1,3 +1,15 @@ +This is a modified version of chrony 4.8, with added support for the protocols +needed for NTS pools. It is mainly intended for use as a server for the +experimental pool at https://experimental.ntspooltest.org/. + +For inclusion in a pool, this version of chrony requires that the pool token is +added to the file specified by the ntsauthtokenfile configuration command. See +the crhony.conf documentation for details on how to do this. + +The original README for chrony is included below for reference. + +=============================================================================== + This is the README for chrony. What is chrony? diff --git a/client.c b/client.c index 66f23b70..2c384f1e 100644 --- a/client.c +++ b/client.c @@ -157,7 +157,7 @@ get_addresses(const char *hostnames, int port) struct Address *addr; ARR_Instance addrs; char *hostname, *s1, *s2; - IPAddr ip_addrs[DNS_MAX_ADDRESSES]; + DNS_AddressLookupResult looked_up_addrs[DNS_MAX_ADDRESSES]; int i; addrs = ARR_CreateInstance(sizeof (*addr)); @@ -175,17 +175,17 @@ get_addresses(const char *hostnames, int port) addr->type = SCK_ADDR_UNIX; addr->addr.path = Strdup(hostname); } else { - if (DNS_Name2IPAddress(hostname, ip_addrs, DNS_MAX_ADDRESSES) != DNS_Success) { + if (DNS_Name2IPAddress(hostname, looked_up_addrs, DNS_MAX_ADDRESSES, 0) != DNS_Success) { DEBUG_LOG("Could not get IP address for %s", hostname); continue; } - for (i = 0; i < DNS_MAX_ADDRESSES && ip_addrs[i].family != IPADDR_UNSPEC; i++) { + for (i = 0; i < DNS_MAX_ADDRESSES && looked_up_addrs[i].ip.family != IPADDR_UNSPEC; i++) { addr = ARR_GetNewElement(addrs); addr->type = SCK_ADDR_IP; - addr->addr.ip.ip_addr = ip_addrs[i]; + addr->addr.ip.ip_addr = looked_up_addrs[i].ip; addr->addr.ip.port = port; - DEBUG_LOG("Resolved %s to %s", hostname, UTI_IPToString(&ip_addrs[i])); + DEBUG_LOG("Resolved %s to %s", hostname, UTI_IPToString(&looked_up_addrs[i].ip)); } } } @@ -449,11 +449,15 @@ bits_to_mask(int bits, int family, IPAddr *mask) static int parse_source_address(char *word, IPAddr *address) { + DNS_AddressLookupResult lookup; + if (UTI_StringToIdIP(word, address)) return 1; - if (DNS_Name2IPAddress(word, address, 1) == DNS_Success) + if (DNS_Name2IPAddress(word, &lookup, 1, 0) == DNS_Success) { + *address = lookup.ip; return 1; + } return 0; } @@ -944,10 +948,10 @@ process_cmd_allowdeny(CMD_Request *msg, char *line, int cmd, int allcmd) static int process_cmd_accheck(CMD_Request *msg, char *line) { - IPAddr ip; + DNS_AddressLookupResult lookup; msg->command = htons(REQ_ACCHECK); - if (DNS_Name2IPAddress(line, &ip, 1) == DNS_Success) { - UTI_IPHostToNetwork(&ip, &msg->data.ac_check.ip); + if (DNS_Name2IPAddress(line, &lookup, 1, 0) == DNS_Success) { + UTI_IPHostToNetwork(&lookup.ip, &msg->data.ac_check.ip); return 1; } else { LOG(LOGS_ERR, "Could not read address"); @@ -960,10 +964,10 @@ process_cmd_accheck(CMD_Request *msg, char *line) static int process_cmd_cmdaccheck(CMD_Request *msg, char *line) { - IPAddr ip; + DNS_AddressLookupResult lookup; msg->command = htons(REQ_CMDACCHECK); - if (DNS_Name2IPAddress(line, &ip, 1) == DNS_Success) { - UTI_IPHostToNetwork(&ip, &msg->data.ac_check.ip); + if (DNS_Name2IPAddress(line, &lookup, 1, 0) == DNS_Success) { + UTI_IPHostToNetwork(&lookup.ip, &msg->data.ac_check.ip); return 1; } else { LOG(LOGS_ERR, "Could not read address"); @@ -1025,7 +1029,7 @@ process_cmd_add_source(CMD_Request *msg, char *line) { CPS_NTP_Source data; CPS_Status status; - IPAddr ip_addr; + DNS_AddressLookupResult lookup; int result = 0, type; const char *opt_name, *word; @@ -1051,7 +1055,7 @@ process_cmd_add_source(CMD_Request *msg, char *line) /* Verify that the address is resolvable (chronyc and chronyd are assumed to be running on the same host) */ if (strlen(data.name) >= sizeof (msg->data.ntp_source.name) || - DNS_Name2IPAddress(data.name, &ip_addr, 1) != DNS_Success) { + DNS_Name2IPAddress(data.name, &lookup, 1, data.params.nts) != DNS_Success) { LOG(LOGS_ERR, "Invalid host/IP address"); break; } @@ -3730,5 +3734,3 @@ main(int argc, char **argv) return !ret; } - - diff --git a/cmdparse.c b/cmdparse.c index 29c65b31..6c142055 100644 --- a/cmdparse.c +++ b/cmdparse.c @@ -231,6 +231,7 @@ CPS_ParseAllowDeny(char *line, int *all, IPAddr *ip, int *subnet_bits) char *p, *net, *slash; uint32_t a, b, c; int bits, len, n; + DNS_AddressLookupResult lookup_result; p = CPS_SplitWord(line); @@ -294,7 +295,8 @@ CPS_ParseAllowDeny(char *line, int *all, IPAddr *ip, int *subnet_bits) } /* The last possibility is a hostname */ - if (bits < 0 && DNS_Name2IPAddress(net, ip, 1) == DNS_Success) { + if (bits < 0 && DNS_Name2IPAddress(net, &lookup_result, 1, 0) == DNS_Success) { + *ip = lookup_result.ip; *subnet_bits = ip->family == IPADDR_INET6 ? 128 : 32; return 1; } diff --git a/conf.c b/conf.c index 1f362d7f..cc29fdb9 100644 --- a/conf.c +++ b/conf.c @@ -102,6 +102,7 @@ static char *rtc_device; static int acquisition_port = -1; static int ntp_port = NTP_PORT; static char *keys_file = NULL; +static char *nts_auth_token_file = NULL; static char *drift_file = NULL; static int drift_file_interval = 3600; static char *rtc_file = NULL; @@ -281,6 +282,7 @@ static ARR_Instance nts_server_key_files; /* array of (char *) */ static int nts_server_port = NKE_PORT; static int nts_server_processes = 1; static int nts_server_connections = 100; +static int nts_longterm_connections = 5; static int nts_refresh = 2419200; /* 4 weeks */ static int nts_rotate = 604800; /* 1 week */ static ARR_Instance nts_trusted_certs_paths; /* array of (char *) */ @@ -515,6 +517,7 @@ CNF_Finalise(void) Free(dumpdir); Free(hwclock_file); Free(keys_file); + Free(nts_auth_token_file); Free(leapsec_tz); Free(leapsec_list); Free(logdir); @@ -662,6 +665,8 @@ CNF_ParseLine(const char *filename, int number, char *line) parse_initstepslew(p); } else if (!strcasecmp(command, "keyfile")) { parse_string(p, &keys_file); + } else if (!strcasecmp(command, "ntsauthtokenfile")) { + parse_string(p, &nts_auth_token_file); } else if (!strcasecmp(command, "leapsecmode")) { parse_leapsecmode(p); } else if (!strcasecmp(command, "leapsectz")) { @@ -698,6 +703,8 @@ CNF_ParseLine(const char *filename, int number, char *line) parse_double(p, &max_jitter); } else if (!strcasecmp(command, "maxntsconnections")) { parse_int(p, &nts_server_connections, 1, INT_MAX); + } else if (!strcasecmp(command, "maxntslongtermconnections")) { + parse_int(p, &nts_longterm_connections, 0, INT_MAX); } else if (!strcasecmp(command, "maxsamples")) { parse_int(p, &max_samples, 0, INT_MAX); } else if (!strcasecmp(command, "maxslewrate")) { @@ -1164,7 +1171,7 @@ static void parse_initstepslew(char *line) { char *p, *hostname; - IPAddr ip_addr; + DNS_AddressLookupResult addr; /* Ignore the line if chronyd was started with -R. */ if (restarted) { @@ -1183,8 +1190,8 @@ parse_initstepslew(char *line) hostname = p; p = CPS_SplitWord(p); if (*hostname) { - if (DNS_Name2IPAddress(hostname, &ip_addr, 1) == DNS_Success) { - ARR_AppendElement(init_sources, &ip_addr); + if (DNS_Name2IPAddress(hostname, &addr, 1, 0) == DNS_Success) { + ARR_AppendElement(init_sources, &addr.ip); } else { LOG(LOGS_WARN, "Could not resolve address of initstepslew server %s", hostname); } @@ -2000,6 +2007,8 @@ CNF_CheckReadOnlyAccess(void) if (keys_file) UTI_CheckReadOnlyAccess(keys_file); + if (nts_auth_token_file) + UTI_CheckReadOnlyAccess(nts_auth_token_file); for (i = 0; i < ARR_GetSize(nts_server_key_files); i++) UTI_CheckReadOnlyAccess(*(char **)ARR_GetElement(nts_server_key_files, i)); } @@ -2215,6 +2224,14 @@ CNF_GetKeysFile(void) /* ================================================== */ +char * +CNF_GetNtsAuthTokenFile(void) +{ + return nts_auth_token_file; +} + +/* ================================================== */ + double CNF_GetRtcAutotrim(void) { @@ -2842,6 +2859,14 @@ CNF_GetNtsServerConnections(void) /* ================================================== */ +int +CNF_GetNtsLongtermConnections(void) +{ + return nts_longterm_connections; +} + +/* ================================================== */ + int CNF_GetNtsRefresh(void) { diff --git a/conf.h b/conf.h index 00a11702..f836472f 100644 --- a/conf.h +++ b/conf.h @@ -68,6 +68,7 @@ extern int CNF_GetLogRtc(void); extern int CNF_GetLogRefclocks(void); extern int CNF_GetLogTempComp(void); extern char *CNF_GetKeysFile(void); +extern char *CNF_GetNtsAuthTokenFile(void); extern char *CNF_GetRtcFile(void); extern int CNF_GetManualEnabled(void); extern ARR_Instance CNF_GetOpenCommands(void); @@ -173,6 +174,7 @@ extern int CNF_GetNtsServerCertAndKeyFiles(const char ***certs, const char ***ke extern int CNF_GetNtsServerPort(void); extern int CNF_GetNtsServerProcesses(void); extern int CNF_GetNtsServerConnections(void); +extern int CNF_GetNtsLongtermConnections(void); extern int CNF_GetNtsRefresh(void); extern int CNF_GetNtsRotate(void); extern int CNF_GetNtsTrustedCertsPaths(const char ***paths, uint32_t **ids); diff --git a/configure b/configure index 195b1ed7..ec66e657 100755 --- a/configure +++ b/configure @@ -110,6 +110,7 @@ For better control, use the options below. --disable-readline Disable line editing support --without-editline Don't use editline even if it is available --disable-sechash Disable support for hashes other than MD5 + --without-getdns Don't use getdns --without-nettle Don't use nettle even if it is available --without-gnutls Don't use gnutls even if it is available --without-nss Don't use NSS even if it is available @@ -122,6 +123,7 @@ For better control, use the options below. --disable-ipv6 Disable IPv6 support --disable-rtc Don't include RTC even on Linux --disable-privdrop Disable support for dropping root privileges + --disable-srv Disable support for resolving through SRV records --without-libcap Don't use libcap even if it is available --enable-scfilter Enable support for system call filtering --without-seccomp Don't use seccomp even if it is available @@ -221,6 +223,8 @@ feat_cmdmon=1 feat_refclock=1 feat_readline=1 try_editline=1 +feat_srv=1 +try_getdns=1 feat_sechash=1 try_nettle=1 try_nss=1 @@ -383,9 +387,15 @@ do --without-tomcrypt ) try_tomcrypt=0 ;; + --disable-srv ) + feat_srv=0 + ;; --disable-nts ) feat_nts=0 ;; + --without-getdns ) + try_getdns=0 + ;; --without-gnutls ) try_gnutls=0 ;; @@ -970,6 +980,17 @@ EXTRA_OBJECTS="$EXTRA_OBJECTS $HASH_OBJ" EXTRA_CLI_OBJECTS="$EXTRA_CLI_OBJECTS $HASH_OBJ" LIBS="$LIBS $HASH_LINK" +if [ $feat_srv = "1" ] && [ $try_getdns = "1" ]; then + if test_code 'getdns' 'getdns/getdns.h' '' '-lgetdns' ' + getdns_context *context; + getdns_context_create(&context, 1);' + then + EXTRA_LIBS="$EXTRA_LIBS -lgetdns" + EXTRA_CLI_LIBS="$EXTRA_CLI_LIBS -lgetdns" + add_def FEAT_SRV + fi +fi + if [ $feat_nts = "1" ] && [ $try_gnutls = "1" ]; then if [ "$HASH_OBJ" = "hash_gnutls.o" ]; then test_cflags="" @@ -1096,7 +1117,7 @@ add_def MAIL_PROGRAM "\"$mail_program\"" common_features="`get_features SECHASH IPV6 DEBUG`" chronyc_features="`get_features READLINE`" -chronyd_features="`get_features CMDMON REFCLOCK RTC PRIVDROP SCFILTER SIGND NTS`" +chronyd_features="`get_features CMDMON REFCLOCK RTC PRIVDROP SCFILTER SIGND NTS SRV`" add_def CHRONYC_FEATURES "\"$chronyc_features $common_features\"" add_def CHRONYD_FEATURES "\"$chronyd_features $common_features\"" echo "Features : $chronyd_features $chronyc_features $common_features" @@ -1141,4 +1162,3 @@ done # ======================================================================= # vim:et:sw=2:ht=2:sts=2:fdm=marker:cms=#%s - diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc index fee500e6..cc8e59de 100644 --- a/doc/chrony.conf.adoc +++ b/doc/chrony.conf.adoc @@ -1860,6 +1860,15 @@ This directive can be used multiple times to specify multiple keys. The number of keys must be the same as the number of certificates, and the corresponding files must be specified in the same order. +[[ntsauthtokenfile]]*ntsauthtokenfile* _file_:: +This directive specifies a file containing the authentication tokens used +to check a pool connecting to chrony is allowed to do so. The file needs to be +readable by the user under which *chronyd* is running after dropping root +privileges. For security reasons, it should not be readable by other users. ++ +The file should contain one authentication token per line, and all lines +including the last one should be ended with a newline. Emtpy lines are ignored. + [[ntsprocesses]]*ntsprocesses* _processes_:: This directive specifies how many helper processes will *chronyd* operating as an NTS server start for handling client NTS-KE requests in order to improve @@ -1873,6 +1882,13 @@ per process that the NTS server will accept. The default value is 100. The maximum practical value is half of the system *FD_SETSIZE* constant (usually 1024). +[[maxntslongtermconnections]]*maxntslongtermconnections* _connections_:: +This directive specifies the maximum number of concurrent longterm NTS-KE +connections per process that the NTS server will accept from pools. The +default value is 5. This value should be kept smaller than *maxntsconnections*, +otherwise clients trying to use the server may get starved from new +connections. + [[ntsaeads2]]*ntsaeads* _ID_...:: This directive specifies a list of IDs of Authenticated Encryption with Associated Data (AEAD) algorithms enabled for NTS authentication of NTP diff --git a/nameserv.c b/nameserv.c index 9f7e648c..5d26b0f9 100644 --- a/nameserv.c +++ b/nameserv.c @@ -32,7 +32,14 @@ #include #include +#ifdef FEAT_SRV +#include +#endif +#ifdef FEAT_SRV +#include "logging.h" +#include "memory.h" +#endif #include "nameserv.h" #include "socket.h" #include "util.h" @@ -41,14 +48,152 @@ static int address_family = IPADDR_UNSPEC; +#ifdef FEAT_SRV +#define NTS_SERVICE_NAME "_ntske._tcp." + +static getdns_context *dns_context = NULL; + +static void +reinit() +{ + if (dns_context != NULL) + { + getdns_context_destroy(dns_context); + dns_context = NULL; + } + if (getdns_context_create(&dns_context, 1)) + LOG_MESSAGE(LOGS_ERR, "Could not initialize DNS resolver."); +} +#endif + void DNS_SetAddressFamily(int family) { address_family = family; } +#ifdef FEAT_SRV +static DNS_Status +srv_lookup(const char *name, DNS_AddressLookupResult *addrs, int max_addrs) { + int i, write_idx; + getdns_dict *extensions = NULL, *response = NULL; + getdns_list *service_addresses = NULL; + getdns_dict *service_entry = NULL; + getdns_bindata *raw_data = NULL; + char *service_domain = NULL; + char *last_name = NULL; + getdns_return_t getdns_status; + size_t returned_addresses; + size_t domain_name_len; + + if (dns_context == NULL) { + reinit(); + if (dns_context == NULL) { +#ifdef FORCE_DNSRETRY + return DNS_TryAgain; +#else + return DNS_Failure; +#endif + } + } + + service_domain = Malloc(strlen(NTS_SERVICE_NAME) + strlen(name) + 1); + strcpy(service_domain, NTS_SERVICE_NAME); + strcat(service_domain, name); + + if ((extensions = getdns_dict_create()) == NULL) + LOG_FATAL("Could not allocate memory"); + if (getdns_dict_set_int(extensions, "dnssec_return_only_secure", GETDNS_EXTENSION_TRUE)) + LOG_FATAL("Could not allocate memory"); + getdns_status = getdns_service_sync(dns_context, service_domain, extensions, &response); + free(service_domain); + getdns_dict_destroy(extensions); + if (getdns_status) { +#ifdef FORCE_DNSRETRY + return DNS_TryAgain; +#else + return DNS_Failure; +#endif + } + + if (getdns_dict_get_list(response, "/srv_addresses", &service_addresses)) + LOG_FATAL("Unrecoverable error calling getdns."); + if (getdns_list_get_length(service_addresses, &returned_addresses)) + LOG_FATAL("Unrecoverable error calling getdns."); + + write_idx = 0; + + for (i = 0; i < returned_addresses; i++) { + if (getdns_list_get_dict(service_addresses, i, &service_entry)) + LOG_FATAL("Unrecoverable error calling getdns."); + if (getdns_dict_get_bindata(service_entry, "domain_name", &raw_data)) + LOG_FATAL("Unrecoverable error calling getdns."); + if (getdns_convert_dns_name_to_fqdn(raw_data, &service_domain)) + LOG_FATAL("Unrecoverable error calling getnds."); + + /* Remove any potential trailing dot as it would interfere with certificate validation*/ + domain_name_len = strlen(service_domain); + if (service_domain[domain_name_len-1] == '.') + service_domain[domain_name_len-1] = 0; + + //*Ignore too-long domain names */ + if (strlen(service_domain) >= DNS_SERVICE_NAME_LEN) + continue; + + /* Ignore repeated names. This is needed to deal with multiple + addresses from the same service. */ + if (last_name != NULL && strcmp(last_name, service_domain) == 0) + continue; + + if (getdns_dict_get_bindata(service_entry, "address_data", &raw_data)) { + // No pre-populated address, recurse to resolve name + if (DNS_Name2IPAddress(service_domain, &addrs[write_idx], 1, 0) == DNS_Success) { + strncpy(addrs[write_idx].service_name, service_domain, DNS_SERVICE_NAME_LEN-1); + write_idx++; + free(last_name); + last_name = service_domain; + service_domain = NULL; + } + } else { + switch (raw_data->size) { + case sizeof (addrs[write_idx].ip.addr.in4): + if (address_family != IPADDR_UNSPEC && address_family != IPADDR_INET4) + continue; + /* copy first to deal with the fact that alignment of data might not be okay. */ + memcpy(&addrs[write_idx].ip.addr.in4, raw_data->data, + sizeof (addrs[write_idx].ip.addr.in4)); + addrs[write_idx].ip.addr.in4 = htonl(addrs[write_idx].ip.addr.in4); + addrs[write_idx].ip.family = IPADDR_INET4; + strncpy(addrs[write_idx].service_name, service_domain, DNS_SERVICE_NAME_LEN-1); + write_idx++; + free(last_name); + last_name = service_domain; + service_domain = NULL; + break; + +#ifdef FEAT_IPV6 + case sizeof (addrs[write_idx].ip.addr.in6): + if (address_family != IPADDR_UNSPEC && address_family != IPADDR_INET6) + continue; + memcpy(addrs[write_idx].ip.addr.in6, raw_data->data, + sizeof(addrs[write_idx].ip.addr.in6)); + addrs[write_idx].ip.family = IPADDR_INET6; + strncpy(addrs[write_idx].service_name, service_domain, DNS_SERVICE_NAME_LEN-1); + write_idx++; + free(last_name); + last_name = service_domain; + service_domain = NULL; + break; +#endif + } + } + free(service_domain); + } +} +#endif + DNS_Status -DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs) +DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_addrs, int use_srv_lookup) { struct addrinfo hints, *res, *ai; int i, result; @@ -56,18 +201,39 @@ DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs) max_addrs = MIN(max_addrs, DNS_MAX_ADDRESSES); - for (i = 0; i < max_addrs; i++) - ip_addrs[i].family = IPADDR_UNSPEC; + for (i = 0; i < max_addrs; i++) { + addrs[i].ip.family = IPADDR_UNSPEC; + addrs[i].service_name[0] = 0; + addrs[i].service_port = 0; + } + + /* Avoid dealing with the max_addrs=0 edgecase below */ + if (max_addrs < 1) + return DNS_Success; /* Avoid calling getaddrinfo() if the name is an IP address */ if (UTI_StringToIP(name, &ip)) { if (address_family != IPADDR_UNSPEC && ip.family != address_family) return DNS_Failure; if (max_addrs >= 1) - ip_addrs[0] = ip; + addrs[0].ip = ip; return DNS_Success; } +#ifdef FEAT_SRV + /* First try if we can do a service record based resolution" */ + if (use_srv_lookup) { + result = srv_lookup(name, addrs, max_addrs); + if (result != DNS_Success) + return result; + + if (addrs[0].ip.family != IPADDR_UNSPEC) + return DNS_Success; + } + + /* Fall back to regular name resolution */ +#endif + memset(&hints, 0, sizeof (hints)); switch (address_family) { @@ -99,8 +265,8 @@ DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs) case AF_INET: if (address_family != IPADDR_UNSPEC && address_family != IPADDR_INET4) continue; - ip_addrs[i].family = IPADDR_INET4; - ip_addrs[i].addr.in4 = ntohl(((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr); + addrs[i].ip.family = IPADDR_INET4; + addrs[i].ip.addr.in4 = ntohl(((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr); i++; break; #ifdef FEAT_IPV6 @@ -110,9 +276,9 @@ DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs) /* Don't return an address that would lose a scope ID */ if (((struct sockaddr_in6 *)ai->ai_addr)->sin6_scope_id != 0) continue; - ip_addrs[i].family = IPADDR_INET6; - memcpy(&ip_addrs[i].addr.in6, &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr.s6_addr, - sizeof (ip_addrs->addr.in6)); + addrs[i].ip.family = IPADDR_INET6; + memcpy(&addrs[i].ip.addr.in6, &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr.s6_addr, + sizeof (addrs->ip.addr.in6)); i++; break; #endif @@ -121,7 +287,7 @@ DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs) freeaddrinfo(res); - return !max_addrs || ip_addrs[0].family != IPADDR_UNSPEC ? DNS_Success : DNS_Failure; + return !max_addrs || addrs[0].ip.family != IPADDR_UNSPEC ? DNS_Success : DNS_Failure; } /* ================================================== */ @@ -160,7 +326,9 @@ void DNS_Reload(void) { res_init(); +#ifdef FEAT_SRV + reinit(); +#endif } /* ================================================== */ - diff --git a/nameserv.h b/nameserv.h index dbef61a3..29bb424e 100644 --- a/nameserv.h +++ b/nameserv.h @@ -30,23 +30,35 @@ #include "addressing.h" +#define DNS_SERVICE_NAME_LEN 256 + typedef enum { DNS_Success, DNS_TryAgain, DNS_Failure } DNS_Status; +typedef struct { + IPAddr ip; + char service_name[DNS_SERVICE_NAME_LEN]; + uint16_t service_port; +} DNS_AddressLookupResult; + +typedef struct { + DNS_AddressLookupResult ip_addr; + uint16_t port; +} DNS_SockAddrLookupResult; + /* Resolve names only to selected address family */ extern void DNS_SetAddressFamily(int family); /* Maximum number of addresses returned by DNS_Name2IPAddress */ #define DNS_MAX_ADDRESSES 16 -extern DNS_Status DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs); +extern DNS_Status DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_addrs, int use_srv_lookup); extern int DNS_IPAddress2Name(IPAddr *ip_addr, char *name, int len); extern void DNS_Reload(void); #endif /* GOT_NAMESERV_H */ - diff --git a/nameserv_async.c b/nameserv_async.c index 118443c6..805d9c68 100644 --- a/nameserv_async.c +++ b/nameserv_async.c @@ -42,8 +42,9 @@ struct DNS_Async_Instance { const char *name; + int use_srv_lookup; DNS_Status status; - IPAddr addresses[DNS_MAX_ADDRESSES]; + DNS_AddressLookupResult addresses[DNS_MAX_ADDRESSES]; DNS_NameResolveHandler handler; void *arg; @@ -61,7 +62,7 @@ start_resolving(void *anything) struct DNS_Async_Instance *inst = (struct DNS_Async_Instance *)anything; pthread_mutex_lock(&privops_lock); - inst->status = PRV_Name2IPAddress(inst->name, inst->addresses, DNS_MAX_ADDRESSES); + inst->status = PRV_Name2IPAddress(inst->name, inst->addresses, DNS_MAX_ADDRESSES, inst->use_srv_lookup); pthread_mutex_unlock(&privops_lock); /* Notify the main thread that the result is ready */ @@ -88,7 +89,7 @@ end_resolving(int fd, int event, void *anything) close(inst->pipe[1]); for (i = 0; inst->status == DNS_Success && i < DNS_MAX_ADDRESSES && - inst->addresses[i].family != IPADDR_UNSPEC; i++) + inst->addresses[i].ip.family != IPADDR_UNSPEC; i++) ; (inst->handler)(inst->status, i, inst->addresses, inst->arg); @@ -99,12 +100,13 @@ end_resolving(int fd, int event, void *anything) /* ================================================== */ void -DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, void *anything) +DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, int use_srv_lookup, void *anything) { struct DNS_Async_Instance *inst; inst = MallocNew(struct DNS_Async_Instance); inst->name = name; + inst->use_srv_lookup = use_srv_lookup; inst->handler = handler; inst->arg = anything; inst->status = DNS_Failure; diff --git a/nameserv_async.h b/nameserv_async.h index b8479e1d..e5662907 100644 --- a/nameserv_async.h +++ b/nameserv_async.h @@ -31,10 +31,10 @@ #include "nameserv.h" /* Function type for callback to process the result */ -typedef void (*DNS_NameResolveHandler)(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *anything); +typedef void (*DNS_NameResolveHandler)(DNS_Status status, int n_addrs, DNS_AddressLookupResult *addrs, void *anything); /* Request resolving of a name to IP address. The handler will be called when the result is available. */ -extern void DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, void *anything); +extern void DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, int perform_srv, void *anything); #endif diff --git a/ntp_auth.c b/ntp_auth.c index 58374c57..014c8363 100644 --- a/ntp_auth.c +++ b/ntp_auth.c @@ -332,7 +332,7 @@ NAU_CheckResponseAuth(NAU_Instance instance, NTP_Packet *response, NTP_PacketInf /* ================================================== */ void -NAU_ChangeAddress(NAU_Instance instance, IPAddr *address) +NAU_ChangeAddress(NAU_Instance instance, DNS_AddressLookupResult *address) { switch (instance->mode) { case NTP_AUTH_NONE: diff --git a/ntp_auth.h b/ntp_auth.h index 0b8a8253..31b5ba6c 100644 --- a/ntp_auth.h +++ b/ntp_auth.h @@ -28,6 +28,7 @@ #define GOT_NTP_AUTH_H #include "addressing.h" +#include "nameserv.h" #include "ntp.h" #include "reports.h" @@ -73,7 +74,7 @@ extern int NAU_CheckResponseAuth(NAU_Instance instance, NTP_Packet *response, NTP_PacketInfo *info); /* Change an authentication-specific address (e.g. after replacing a source) */ -extern void NAU_ChangeAddress(NAU_Instance instance, IPAddr *address); +extern void NAU_ChangeAddress(NAU_Instance instance, DNS_AddressLookupResult *address); /* Save authentication-specific data to speed up the next start */ extern void NAU_DumpData(NAU_Instance instance); diff --git a/ntp_core.c b/ntp_core.c index 7395a638..a274c106 100644 --- a/ntp_core.c +++ b/ntp_core.c @@ -823,14 +823,15 @@ NCR_ResetPoll(NCR_Instance instance) /* ================================================== */ void -NCR_ChangeRemoteAddress(NCR_Instance inst, NTP_Remote_Address *remote_addr, int ntp_only) +NCR_ChangeRemoteAddress(NCR_Instance inst, DNS_SockAddrLookupResult *remote_addr, int ntp_only) { NCR_ResetInstance(inst); if (!ntp_only) NAU_ChangeAddress(inst->auth, &remote_addr->ip_addr); - inst->remote_addr = *remote_addr; + inst->remote_addr.ip_addr = remote_addr->ip_addr.ip; + inst->remote_addr.port = remote_addr->port; if (inst->mode == MODE_CLIENT) close_client_socket(inst); @@ -838,7 +839,7 @@ NCR_ChangeRemoteAddress(NCR_Instance inst, NTP_Remote_Address *remote_addr, int NIO_CloseServerSocket(inst->local_addr.sock_fd); inst->local_addr.ip_addr.family = IPADDR_UNSPEC; inst->local_addr.if_index = INVALID_IF_INDEX; - inst->local_addr.sock_fd = NIO_OpenServerSocket(remote_addr); + inst->local_addr.sock_fd = NIO_OpenServerSocket(&inst->remote_addr); } /* Reset the polling interval only if the source wasn't unreachable to @@ -848,7 +849,7 @@ NCR_ChangeRemoteAddress(NCR_Instance inst, NTP_Remote_Address *remote_addr, int NCR_ResetPoll(inst); /* Update the reference ID and reset the source/sourcestats instances */ - SRC_SetRefid(inst->source, UTI_IPToRefid(&remote_addr->ip_addr), + SRC_SetRefid(inst->source, UTI_IPToRefid(&remote_addr->ip_addr.ip), &inst->remote_addr.ip_addr); SRC_ResetInstance(inst->source); diff --git a/ntp_core.h b/ntp_core.h index 67971ead..8b23f2f1 100644 --- a/ntp_core.h +++ b/ntp_core.h @@ -31,6 +31,7 @@ #include "addressing.h" #include "srcparams.h" +#include "nameserv.h" #include "ntp.h" #include "reports.h" @@ -71,7 +72,7 @@ extern void NCR_ResetInstance(NCR_Instance inst); extern void NCR_ResetPoll(NCR_Instance instance); /* Change the remote address of an instance */ -extern void NCR_ChangeRemoteAddress(NCR_Instance inst, NTP_Remote_Address *remote_addr, +extern void NCR_ChangeRemoteAddress(NCR_Instance inst, DNS_SockAddrLookupResult *remote_addr, int ntp_only); /* This routine is called when a new packet arrives off the network, diff --git a/ntp_sources.c b/ntp_sources.c index 7bf08894..d2294371 100644 --- a/ntp_sources.c +++ b/ntp_sources.c @@ -59,6 +59,7 @@ typedef struct { NCR_Instance data; /* Data for the protocol engine for this source */ char *name; /* Name of the source as it was specified (may be an IP address) */ + int nts; /* Whether the source is NTS enabled */ IPAddr resolved_addr; /* Address resolved from the name, which can be different from remote_addr (e.g. NTS-KE) */ int family; /* IP family of acceptable resolved addresses @@ -100,6 +101,8 @@ struct UnresolvedSource { int pool_id; /* Name to be resolved */ char *name; + /* Whether it should be resolved for NTS */ + int perform_srv; /* Address family to filter resolved addresses */ int family; /* Flag indicating addresses should be used in a random order */ @@ -144,7 +147,7 @@ static ARR_Instance pools; /* Requested update of a source's address */ struct AddressUpdate { NTP_Remote_Address old_address; - NTP_Remote_Address new_address; + DNS_SockAddrLookupResult new_address; }; /* Update saved when record_lock is true */ @@ -398,6 +401,7 @@ add_source(NTP_Remote_Address *remote_addr, char *name, int family, NTP_Source_T record = get_record(slot); record->name = Strdup(name ? name : UTI_IPToString(&remote_addr->ip_addr)); + record->nts = params->nts; record->data = NCR_CreateInstance(remote_addr, type, params, record->name); record->remote_addr = NCR_GetRemoteAddress(record->data); record->resolved_addr = remote_addr->ip_addr; @@ -431,21 +435,25 @@ add_source(NTP_Remote_Address *remote_addr, char *name, int family, NTP_Source_T /* ================================================== */ static NSR_Status -change_source_address(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr, +change_source_address(NTP_Remote_Address *old_addr, DNS_SockAddrLookupResult *new_address, int replacement) { + NTP_Remote_Address new_addr; int slot1, slot2, found; SourceRecord *record; LOG_Severity severity; char *name; + new_addr.ip_addr = new_address->ip_addr.ip; + new_addr.port = new_address->port; + found = find_slot2(old_addr, &slot1); if (found != 2) return NSR_NoSuchSource; /* Make sure there is no other source using the new address (with the same or different port), but allow a source to have its port changed */ - found = find_slot2(new_addr, &slot2); + found = find_slot2(&new_addr, &slot2); if (found == 2 || (found != 0 && slot1 != slot2)) return NSR_AlreadyInUse; @@ -453,14 +461,14 @@ change_source_address(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr record_lock = 1; record = get_record(slot1); - NCR_ChangeRemoteAddress(record->data, new_addr, !replacement); + NCR_ChangeRemoteAddress(record->data, new_address, !replacement); if (replacement) - record->resolved_addr = new_addr->ip_addr; + record->resolved_addr = new_addr.ip_addr; BRIEF_ASSERT(record->remote_addr == NCR_GetRemoteAddress(record->data) && - UTI_CompareIPs(&record->remote_addr->ip_addr, &new_addr->ip_addr, NULL) == 0); + UTI_CompareIPs(&record->remote_addr->ip_addr, &new_addr.ip_addr, NULL) == 0); - if (!UTI_IsIPReal(&old_addr->ip_addr) && UTI_IsIPReal(&new_addr->ip_addr)) { + if (!UTI_IsIPReal(&old_addr->ip_addr) && UTI_IsIPReal(&new_addr.ip_addr)) { if (auto_start_sources) NCR_StartInstance(record->data); if (record->pool_id != INVALID_POOL) @@ -485,10 +493,10 @@ change_source_address(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr LOG(severity, "Source %s %s %s (%s)", UTI_IPToString(&old_addr->ip_addr), replacement ? "replaced with" : "changed to", - UTI_IPToString(&new_addr->ip_addr), name); + UTI_IPToString(&new_addr.ip_addr), name); } else { LOG(severity, "Source %s (%s) changed port to %d", - UTI_IPToString(&new_addr->ip_addr), name, new_addr->port); + UTI_IPToString(&new_addr.ip_addr), name, new_addr.port); } return NSR_Success; @@ -507,7 +515,7 @@ handle_saved_address_update(void) /* This is expected to happen only if the old address is wrong */ LOG(LOGS_ERR, "Could not change %s to %s", UTI_IPSockAddrToString(&saved_address_update.old_address), - UTI_IPSockAddrToString(&saved_address_update.new_address)); + UTI_DNS_SockAddrLookupResultToString(&saved_address_update.new_address)); saved_address_update.old_address.ip_addr.family = IPADDR_UNSPEC; } @@ -515,10 +523,14 @@ handle_saved_address_update(void) /* ================================================== */ static int -replace_source_connectable(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr) +replace_source_connectable(NTP_Remote_Address *old_addr, DNS_SockAddrLookupResult *new_addr) { - if (!NIO_IsServerConnectable(new_addr)) { - DEBUG_LOG("%s not connectable", UTI_IPToString(&new_addr->ip_addr)); + NTP_Remote_Address new_remote; + new_remote.ip_addr = new_addr->ip_addr.ip; + new_remote.port = new_addr->port; + + if (!NIO_IsServerConnectable(&new_remote)) { + DEBUG_LOG("%s not connectable", UTI_IPToString(&new_addr->ip_addr.ip)); return 0; } @@ -533,9 +545,10 @@ replace_source_connectable(NTP_Remote_Address *old_addr, NTP_Remote_Address *new /* ================================================== */ static void -process_resolved_name(struct UnresolvedSource *us, IPAddr *ip_addrs, int n_addrs) +process_resolved_name(struct UnresolvedSource *us, DNS_AddressLookupResult *addrs, int n_addrs) { - NTP_Remote_Address old_addr, new_addr; + NTP_Remote_Address old_addr; + DNS_SockAddrLookupResult new_addr; SourceRecord *record; unsigned short first = 0; int i, j, slot; @@ -547,7 +560,7 @@ process_resolved_name(struct UnresolvedSource *us, IPAddr *ip_addrs, int n_addrs for (i = 0; i < n_addrs; i++) { if (find_slot2(&us->address, &slot) == 2 && - UTI_CompareIPs(&get_record(slot)->resolved_addr, &ip_addrs[i], NULL) == 0) { + UTI_CompareIPs(&get_record(slot)->resolved_addr, &addrs[i].ip, NULL) == 0) { DEBUG_LOG("%s still fresh", UTI_IPToString(&us->address.ip_addr)); return; } @@ -558,12 +571,12 @@ process_resolved_name(struct UnresolvedSource *us, IPAddr *ip_addrs, int n_addrs UTI_GetRandomBytes(&first, sizeof (first)); for (i = 0; i < n_addrs; i++) { - new_addr.ip_addr = ip_addrs[((unsigned int)i + first) % n_addrs]; + new_addr.ip_addr = addrs[((unsigned int)i + first) % n_addrs]; - DEBUG_LOG("(%d) %s", i + 1, UTI_IPToString(&new_addr.ip_addr)); + DEBUG_LOG("(%d) %s", i + 1, UTI_IPToString(&new_addr.ip_addr.ip)); /* Skip addresses not from the requested family */ - if (us->family != IPADDR_UNSPEC && us->family != new_addr.ip_addr.family) + if (us->family != IPADDR_UNSPEC && us->family != new_addr.ip_addr.ip.family) continue; if (us->pool_id != INVALID_POOL) { @@ -616,7 +629,7 @@ resolve_sources_timeout(void *arg) /* ================================================== */ static void -name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *anything) +name_resolve_handler(DNS_Status status, int n_addrs, DNS_AddressLookupResult *addrs, void *anything) { struct UnresolvedSource *us, *next; @@ -631,7 +644,7 @@ name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *any case DNS_TryAgain: break; case DNS_Success: - process_resolved_name(us, ip_addrs, n_addrs); + process_resolved_name(us, addrs, n_addrs); break; case DNS_Failure: LOG(LOGS_WARN, "Invalid host %s", us->name); @@ -662,7 +675,7 @@ name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *any if (next) { /* Continue with the next source in the list */ DEBUG_LOG("resolving %s", next->name); - DNS_Name2IPAddressAsync(next->name, name_resolve_handler, next); + DNS_Name2IPAddressAsync(next->name, name_resolve_handler, next->perform_srv, next); } else { /* This was the last source in the list. If some sources couldn't be resolved, try again in exponentially increasing interval. */ @@ -708,7 +721,7 @@ resolve_sources(void) resolving_source = us; DEBUG_LOG("resolving %s", us->name); - DNS_Name2IPAddressAsync(us->name, name_resolve_handler, us); + DNS_Name2IPAddressAsync(us->name, name_resolve_handler, us->perform_srv, us); } /* ================================================== */ @@ -833,6 +846,7 @@ NSR_AddSourceByName(char *name, int family, int port, int pool, NTP_Source_Type us = MallocNew(struct UnresolvedSource); us->name = Strdup(name); + us->perform_srv = params->nts; us->family = family; us->random_order = 0; us->refreshment = 0; @@ -1061,6 +1075,7 @@ resolve_source_replacement(SourceRecord *record, int refreshment) us = MallocNew(struct UnresolvedSource); us->name = Strdup(record->name); + us->perform_srv = record->nts; us->family = record->family; /* Ignore the order of addresses from the resolver to not get stuck with a pair of unreachable or otherwise unusable servers @@ -1174,15 +1189,15 @@ NSR_RefreshAddresses(void) /* ================================================== */ NSR_Status -NSR_UpdateSourceNtpAddress(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr) +NSR_UpdateSourceNtpAddress(NTP_Remote_Address *old_addr, DNS_SockAddrLookupResult *new_addr) { int slot; - if (!UTI_IsIPReal(&old_addr->ip_addr) || !UTI_IsIPReal(&new_addr->ip_addr)) + if (!UTI_IsIPReal(&old_addr->ip_addr) || !UTI_IsIPReal(&new_addr->ip_addr.ip)) return NSR_InvalidAF; - if (UTI_CompareIPs(&old_addr->ip_addr, &new_addr->ip_addr, NULL) != 0 && - find_slot(&new_addr->ip_addr, &slot)) + if (UTI_CompareIPs(&old_addr->ip_addr, &new_addr->ip_addr.ip, NULL) != 0 && + find_slot(&new_addr->ip_addr.ip, &slot)) return NSR_AlreadyInUse; /* If a record is being modified (e.g. by change_source_address(), or the diff --git a/ntp_sources.h b/ntp_sources.h index 89a322e9..3a9050d0 100644 --- a/ntp_sources.h +++ b/ntp_sources.h @@ -99,7 +99,7 @@ extern void NSR_RefreshAddresses(void); /* Procedure to update the address of a source. The update may be postponed. */ extern NSR_Status NSR_UpdateSourceNtpAddress(NTP_Remote_Address *old_addr, - NTP_Remote_Address *new_addr); + DNS_SockAddrLookupResult *new_addr); /* Procedure to get local reference ID corresponding to a source */ extern uint32_t NSR_GetLocalRefid(IPAddr *address); diff --git a/nts_ke.h b/nts_ke.h index 2cfbb085..99c99c71 100644 --- a/nts_ke.h +++ b/nts_ke.h @@ -41,6 +41,11 @@ #define NKE_RECORD_NTPV4_SERVER_NEGOTIATION 6 #define NKE_RECORD_NTPV4_PORT_NEGOTIATION 7 #define NKE_RECORD_COMPLIANT_128GCM_EXPORT 1024 +#define NKE_RECORD_KEEP_ALIVE 0x4000 +#define NKE_RECORD_SUPPORTED_ALGORITHMS 0x4001 +#define NKE_RECORD_SUPPORTED_PROTOCOLS 0x4004 +#define NKE_RECORD_FIXED_KEY 0x4002 +#define NKE_RECORD_AUTH_TOKEN 0x4005 #define NKE_NEXT_PROTOCOL_NTPV4 0 diff --git a/nts_ke_client.c b/nts_ke_client.c index e68d4dcf..77f30111 100644 --- a/nts_ke_client.c +++ b/nts_ke_client.c @@ -67,7 +67,7 @@ static int default_credentials_refs = 0; /* ================================================== */ static void -name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *arg) +name_resolve_handler(DNS_Status status, int n_addrs, DNS_AddressLookupResult *addrs, void *arg) { NKC_Instance inst = arg; int i; @@ -86,13 +86,13 @@ name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *arg return; } - inst->ntp_address.ip_addr = ip_addrs[0]; + inst->ntp_address.ip_addr = addrs[0].ip; /* Prefer an address in the same family as the NTS-KE server */ for (i = 0; i < n_addrs; i++) { - DEBUG_LOG("%s resolved to %s", inst->server_name, UTI_IPToString(&ip_addrs[i])); - if (ip_addrs[i].family == inst->address.ip_addr.family) { - inst->ntp_address.ip_addr = ip_addrs[i]; + DEBUG_LOG("%s resolved to %s", inst->server_name, UTI_IPToString(&addrs[i].ip)); + if (addrs[i].ip.family == inst->address.ip_addr.family) { + inst->ntp_address.ip_addr = addrs[i].ip; break; } } @@ -330,7 +330,7 @@ handle_message(void *arg) inst->server_name[length + 1] = '\0'; } - DNS_Name2IPAddressAsync(inst->server_name, name_resolve_handler, inst); + DNS_Name2IPAddressAsync(inst->server_name, name_resolve_handler, 0, inst); inst->resolving_name = 1; } } diff --git a/nts_ke_server.c b/nts_ke_server.c index 33aef082..eec69a26 100644 --- a/nts_ke_server.c +++ b/nts_ke_server.c @@ -80,6 +80,11 @@ typedef struct { uint16_t _pad; } HelperRequest; +typedef struct { + uint32_t len; + char *token; +} AuthToken; + /* ================================================== */ static ServerKey server_keys[MAX_SERVER_KEYS]; @@ -93,8 +98,12 @@ static int server_sock_fd6; static int helper_sock_fd; static int is_helper; +static ARR_Instance auth_tokens; + static int initialised = 0; +static int active_longterm_connections = 0; + /* Array of NKSN instances */ static ARR_Instance sessions; static NKSN_Credentials server_credentials; @@ -105,6 +114,26 @@ static int handle_message(void *arg); /* ================================================== */ +static int +handle_longterm_stop(void *arg) +{ + NKSN_Instance inst = (NKSN_Instance)arg; + + if (!NKSN_IsLongterm(inst)) + return 0; + + if (active_longterm_connections > 0) { + active_longterm_connections--; + } else { + /* This should never happen, unless something goes haywire*/ + LOG(LOGS_ERR, "Internal error: count of active longterm connections is lower than actual number of longterm connections"); + } + + return 0; +} + +/* ================================================== */ + static int handle_client(int sock_fd, IPSockAddr *addr) { @@ -337,25 +366,114 @@ helper_signal(int x) /* ================================================== */ static int -prepare_response(NKSN_Instance session, int error, int next_protocol, int aead_algorithm, - int compliant_128gcm) +prepare_error_response(NKSN_Instance session, int error) +{ + uint16_t datum; + + DEBUG_LOG("NTS KE error response: error=%d", error); + + NKSN_BeginMessage(session); + + datum = htons(error); + if (!NKSN_AddRecord(session, 1, NKE_RECORD_ERROR, &datum, sizeof (datum))) + return 0; + + if (!NKSN_EndMessage(session)) + return 0; + + return 1; +} + +/* ================================================== */ + +static int +prepare_response_keepalive(NKSN_Instance session) +{ + if ((NKSN_IsLongterm(session) || CNF_GetNtsLongtermConnections() > active_longterm_connections)) + { + if (!NKSN_IsLongterm(session)) { + active_longterm_connections++; + NKSN_MarkLongterm(session); + NKSN_SetStopHandler(session, handle_longterm_stop); + } + + DEBUG_LOG("Keeping session alive"); + if (!NKSN_AddRecord(session, 0, NKE_RECORD_KEEP_ALIVE, NULL, 0)) + return 0; + NKSN_KeepAlive(session); + } + + return 1; +} + +static int +prepare_supports_response(NKSN_Instance session, int want_supported_protocols, + int want_supported_algorithms, int keep_alive) +{ + DEBUG_LOG("NTS KE supports response: want_supported_protocols=%d, want_supported_algorithms=%d", + want_supported_protocols, want_supported_algorithms); + + NKSN_BeginMessage(session); + + if (want_supported_protocols) { + uint16_t supported_protocol = NKE_NEXT_PROTOCOL_NTPV4; + if (!NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, &supported_protocol, + sizeof(supported_protocol))) + return 0; + } + + if (want_supported_algorithms) { + /* Generate descriptions for the enabled Aeads that are actually supported */ + ARR_Instance supported_algorithms = ARR_CreateInstance(sizeof(uint16_t[2])); + for (int i=0; i= 0) { - datum = htons(error); - if (!NKSN_AddRecord(session, 1, NKE_RECORD_ERROR, &datum, sizeof (datum))) - return 0; - } else if (next_protocol < 0) { + if (next_protocol < 0) { if (!NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, NULL, 0)) return 0; } else if (aead_algorithm < 0) { @@ -391,7 +509,7 @@ prepare_response(NKSN_Instance session, int error, int next_protocol, int aead_a return 0; } - context.algorithm = aead_algorithm; + context->algorithm = aead_algorithm; exporter_algorithm = aead_algorithm; /* With AES-128-GCM-SIV, set the algorithm ID in the RFC5705 key exporter @@ -400,18 +518,23 @@ prepare_response(NKSN_Instance session, int error, int next_protocol, int aead_a if (exporter_algorithm == AEAD_AES_128_GCM_SIV && !compliant_128gcm) exporter_algorithm = AEAD_AES_SIV_CMAC_256; - if (!NKSN_GetKeys(session, aead_algorithm, exporter_algorithm, - NKE_NEXT_PROTOCOL_NTPV4, &context.c2s, &context.s2c)) + if (!have_keys && !NKSN_GetKeys(session, aead_algorithm, exporter_algorithm, + NKE_NEXT_PROTOCOL_NTPV4, &context->c2s, &context->s2c)) return 0; for (i = 0; i < NKE_MAX_COOKIES; i++) { - if (!NKS_GenerateCookie(&context, &cookie)) + if (!NKS_GenerateCookie(context, &cookie)) return 0; if (!NKSN_AddRecord(session, 0, NKE_RECORD_COOKIE, cookie.cookie, cookie.length)) return 0; } } + if (have_keys && keep_alive) { + if (!prepare_response_keepalive(session)) + return 0; + } + if (!NKSN_EndMessage(session)) return 0; @@ -423,11 +546,16 @@ prepare_response(NKSN_Instance session, int error, int next_protocol, int aead_a static int process_request(NKSN_Instance session) { - int next_protocol_records = 0, aead_algorithm_records = 0; + int have_next_protocol_record = 0, have_aead_algorithm_record = 0; + int have_supported_protocol_record = 0, have_supported_algorithm_record = 0; int next_protocol_values = 0, aead_algorithm_values = 0; int next_protocol = -1, aead_algorithm = -1, error = -1; int i, j, critical, type, length; - int compliant_128gcm = 0; + int compliant_128gcm = 0, have_fixed_key_record = 0; + int keep_alive = 0, is_authenticated = 0; + int is_support_request; + AuthToken *token; + NKE_Context context; uint16_t data[NKE_MAX_RECORD_BODY_LENGTH / sizeof (uint16_t)]; assert(NKE_MAX_RECORD_BODY_LENGTH % sizeof (uint16_t) == 0); @@ -438,13 +566,27 @@ process_request(NKSN_Instance session) break; switch (type) { + case NKE_RECORD_FIXED_KEY: + if (!critical || !is_authenticated || length % 2 != 0 || length < 2 || + length > 2 * NKE_MAX_KEY_LENGTH || have_fixed_key_record) { + error = NKE_ERROR_BAD_REQUEST; + break; + } + + have_fixed_key_record = 1; + + memcpy(context.c2s.key, data, length / 2); + context.c2s.length = length / 2; + memcpy(context.s2c.key, ((uint8_t*) data) + length / 2, length / 2); + context.s2c.length = length / 2; + break; case NKE_RECORD_NEXT_PROTOCOL: - if (!critical || length < 2 || length % 2 != 0) { + if (!critical || length < 2 || length % 2 != 0 || have_next_protocol_record) { error = NKE_ERROR_BAD_REQUEST; break; } - next_protocol_records++; + have_next_protocol_record = 1; for (i = 0; i < MIN(length, sizeof (data)) / 2; i++) { next_protocol_values++; @@ -453,12 +595,12 @@ process_request(NKSN_Instance session) } break; case NKE_RECORD_AEAD_ALGORITHM: - if (length < 2 || length % 2 != 0) { + if (length < 2 || length % 2 != 0 || have_aead_algorithm_record) { error = NKE_ERROR_BAD_REQUEST; break; } - aead_algorithm_records++; + have_aead_algorithm_record = 1; for (i = 0; i < MIN(length, sizeof (data)) / 2; i++) { aead_algorithm_values++; @@ -470,6 +612,22 @@ process_request(NKSN_Instance session) } } break; + case NKE_RECORD_SUPPORTED_PROTOCOLS: + if (length != 0 || !is_authenticated || have_supported_protocol_record) { + error = NKE_ERROR_BAD_REQUEST; + break; + } + + have_supported_protocol_record = 1; + break; + case NKE_RECORD_SUPPORTED_ALGORITHMS: + if (length != 0 || !is_authenticated || have_supported_algorithm_record) { + error = NKE_ERROR_BAD_REQUEST; + break; + } + + have_supported_algorithm_record = 1; + break; case NKE_RECORD_COMPLIANT_128GCM_EXPORT: if (length != 0) { error = NKE_ERROR_BAD_REQUEST; @@ -477,6 +635,19 @@ process_request(NKSN_Instance session) } compliant_128gcm = 1; break; + case NKE_RECORD_KEEP_ALIVE: + keep_alive = 1; + break; + case NKE_RECORD_AUTH_TOKEN: + for (i = 0; i < ARR_GetSize(auth_tokens); i++) { + token = (AuthToken *)ARR_GetElement(auth_tokens, i); + // Accept that this leaks the token length. Its length is not really secret anyway + if (length == token->len && UTI_IsMemoryEqual(data, token->token, length)) { + is_authenticated = 1; + break; + } + } + break; case NKE_RECORD_ERROR: case NKE_RECORD_WARNING: case NKE_RECORD_COOKIE: @@ -488,15 +659,39 @@ process_request(NKSN_Instance session) } } + is_support_request = have_supported_algorithm_record || have_supported_protocol_record; + if (error < 0) { - if (next_protocol_records != 1 || next_protocol_values < 1 || - (next_protocol == NKE_NEXT_PROTOCOL_NTPV4 && - (aead_algorithm_records != 1 || aead_algorithm_values < 1))) - error = NKE_ERROR_BAD_REQUEST; + if (is_support_request) { + if (have_next_protocol_record || have_aead_algorithm_record || have_fixed_key_record) + error = NKE_ERROR_BAD_REQUEST; + } else { + if (!have_next_protocol_record || next_protocol_values < 1 || + (next_protocol == NKE_NEXT_PROTOCOL_NTPV4 && + (!have_aead_algorithm_record || aead_algorithm_values < 1))) + error = NKE_ERROR_BAD_REQUEST; + + if (have_fixed_key_record) { + if (SIV_GetKeyLength(aead_algorithm) != context.c2s.length || + SIV_GetKeyLength(aead_algorithm) != context.s2c.length || + aead_algorithm_values != 1 || next_protocol_values != 1) + error = NKE_ERROR_BAD_REQUEST; + } + } } - if (!prepare_response(session, error, next_protocol, aead_algorithm, compliant_128gcm)) - return 0; + if (error >= 0) { + if (!prepare_error_response(session, error)) + return 0; + } else if (is_support_request) { + if (!prepare_supports_response(session, have_supported_algorithm_record, + have_supported_protocol_record, keep_alive && is_authenticated)) + return 0; + } else { + if (!prepare_response(session, next_protocol, aead_algorithm, compliant_128gcm, + have_fixed_key_record, &context, keep_alive && is_authenticated)) + return 0; + } return 1; } @@ -686,6 +881,55 @@ load_keys(void) return 0; } +static void +load_auth_tokens(void) +{ + char *nts_auth_tokens_file, line[1024], *key; + AuthToken *token; + int n_words; + FILE *f; + + nts_auth_tokens_file = CNF_GetNtsAuthTokenFile(); + if (!nts_auth_tokens_file) + return; + + if (!UTI_CheckFilePermissions(nts_auth_tokens_file, 0771)) + ; + + f = UTI_OpenFile(NULL, nts_auth_tokens_file, NULL, 'r', 0); + if (!f) { + LOG(LOGS_WARN, "Could not open ntsauthtokenfile %s", nts_auth_tokens_file); + return; + } + + line[sizeof (line) - 2] = '\0'; + while (fgets(line, sizeof (line), f)) { + if (line[sizeof (line) - 2] != '\n' && line[sizeof (line) - 2] != '\0') { + LOG(LOGS_ERR, "Authentication token line too long"); + goto error; + } + + n_words = UTI_SplitString(line, &key, 1); + if (n_words > 1) { + LOG(LOGS_ERR, "Authentication token line contained more than one key"); + goto error; + } + + if (n_words == 1) { + token = ARR_GetNewElement(auth_tokens); + token->len = strlen(key); + token->token = strdup(key); + } + } + + fclose(f); + return; + +error: + LOG(LOGS_ERR, "Could not load all authentication tokens"); + fclose(f); +} + /* ================================================== */ static void @@ -832,6 +1076,9 @@ NKS_Initialise(void) for (i = 0; i < CNF_GetNtsServerConnections(); i++) *(NKSN_Instance *)ARR_GetNewElement(sessions) = NULL; + auth_tokens = ARR_CreateInstance(sizeof (AuthToken)); + load_auth_tokens(); + /* Generate random keys, even if they will be replaced by reloaded keys, or unused (in the helper) */ for (i = 0; i < MAX_SERVER_KEYS; i++) { @@ -894,6 +1141,12 @@ NKS_Finalise(void) for (i = 0; i < MAX_SERVER_KEYS; i++) SIV_DestroyInstance(server_keys[i].siv); + for (i = 0; i < ARR_GetSize(auth_tokens); i++) { + AuthToken token = *(AuthToken *)ARR_GetElement(auth_tokens, i); + Free(token.token); + } + ARR_DestroyInstance(auth_tokens); + for (i = 0; i < ARR_GetSize(sessions); i++) { NKSN_Instance session = *(NKSN_Instance *)ARR_GetElement(sessions, i); if (session) diff --git a/nts_ke_session.c b/nts_ke_session.c index 30f6de46..f3a95f8e 100644 --- a/nts_ke_session.c +++ b/nts_ke_session.c @@ -69,9 +69,12 @@ struct NKSN_Instance_Record { int server; char *server_name; NKSN_MessageHandler handler; + NKSN_MessageHandler stop_handler; void *handler_arg; KeState state; + int keep_alive; + int is_longterm; int sock_fd; char *label; TLS_Instance tls_session; @@ -211,6 +214,12 @@ stop_session(NKSN_Instance inst) inst->state = KE_STOPPED; + if (inst->stop_handler) + inst->stop_handler(inst->handler_arg); + + inst->stop_handler = NULL; + inst->is_longterm = 0; + SCH_RemoveFileHandler(inst->sock_fd); SCK_CloseSocket(inst->sock_fd); inst->sock_fd = INVALID_SOCK_FD; @@ -358,7 +367,8 @@ handle_event(NKSN_Instance inst, int event) return 0; /* Client will receive a response */ - change_state(inst, inst->server ? KE_SHUTDOWN : KE_RECEIVE); + change_state(inst, (inst->server && !inst->keep_alive) ? KE_SHUTDOWN : KE_RECEIVE); + inst->keep_alive = 0; reset_message(&inst->message); inst->new_message = 0; return 0; @@ -579,12 +589,15 @@ NKSN_CreateInstance(int server_mode, const char *server_name, inst->server = server_mode; inst->server_name = server_name ? Strdup(server_name) : NULL; inst->handler = handler; + inst->stop_handler = NULL; inst->handler_arg = handler_arg; /* Replace a NULL argument with the session itself */ if (!inst->handler_arg) inst->handler_arg = inst; inst->state = KE_STOPPED; + inst->keep_alive = 0; + inst->is_longterm = 0; inst->sock_fd = INVALID_SOCK_FD; inst->label = NULL; inst->tls_session = NULL; @@ -613,6 +626,9 @@ NKSN_StartSession(NKSN_Instance inst, int sock_fd, const char *label, { assert(inst->state == KE_STOPPED); + inst->stop_handler = NULL; + inst->is_longterm = 0; + inst->tls_session = TLS_CreateInstance(inst->server, sock_fd, inst->server_name, label, NKE_ALPN_NAME, credentials, clock_updates < CNF_GetNoCertTimeCheck()); @@ -624,6 +640,7 @@ NKSN_StartSession(NKSN_Instance inst, int sock_fd, const char *label, inst->label = Strdup(label); inst->timeout_id = SCH_AddTimeoutByDelay(timeout, session_timeout, inst); + inst->keep_alive = 0; inst->retry_factor = NKE_RETRY_FACTOR2_CONNECT; reset_message(&inst->message); @@ -634,6 +651,30 @@ NKSN_StartSession(NKSN_Instance inst, int sock_fd, const char *label, return 1; } +/* ================================================== */ +void +NKSN_KeepAlive(NKSN_Instance inst) +{ + inst->keep_alive = 1; + SCH_RemoveTimeout(inst->timeout_id); + inst->timeout_id = 0; +} + +/* ================================================== */ + +void +NKSN_MarkLongterm(NKSN_Instance inst) +{ + inst->is_longterm = 1; +} + +/* ================================================== */ +void +NKSN_SetStopHandler(NKSN_Instance inst, NKSN_MessageHandler handler) +{ + inst->stop_handler = handler; +} + /* ================================================== */ void @@ -752,6 +793,14 @@ NKSN_IsStopped(NKSN_Instance inst) /* ================================================== */ +int +NKSN_IsLongterm(NKSN_Instance inst) +{ + return inst->is_longterm; +} + +/* ================================================== */ + void NKSN_StopSession(NKSN_Instance inst) { diff --git a/nts_ke_session.h b/nts_ke_session.h index b046144f..61406165 100644 --- a/nts_ke_session.h +++ b/nts_ke_session.h @@ -61,6 +61,18 @@ extern void NKSN_DestroyInstance(NKSN_Instance inst); extern int NKSN_StartSession(NKSN_Instance inst, int sock_fd, const char *label, NKSN_Credentials credentials, double timeout); +/* Mark a server session to be kept alive after sending the response. The + mark is reset after sending the next response. */ +extern void NKSN_KeepAlive(NKSN_Instance inst); + +/* Mark the session as longterm. This does not alter behaviour in this module + but can be used to check the status of a connection in the server. The mark + gets reset on starting of the session. */ +extern void NKSN_MarkLongterm(NKSN_Instance inst); + +/* Add a handler function to be run when the session stops */ +extern void NKSN_SetStopHandler(NKSN_Instance inst, NKSN_MessageHandler handler); + /* Begin an NTS-KE message. A request should be made right after starting the session and response should be made in the message handler. */ extern void NKSN_BeginMessage(NKSN_Instance inst); @@ -86,6 +98,9 @@ extern int NKSN_GetKeys(NKSN_Instance inst, SIV_Algorithm algorithm, /* Check if the session has stopped */ extern int NKSN_IsStopped(NKSN_Instance inst); +/* Check if the session has been marked longterm */ +extern int NKSN_IsLongterm(NKSN_Instance inst); + /* Stop the session */ extern void NKSN_StopSession(NKSN_Instance inst); diff --git a/nts_ntp_client.c b/nts_ntp_client.c index 2c3464fe..5fbcee8d 100644 --- a/nts_ntp_client.c +++ b/nts_ntp_client.c @@ -194,28 +194,32 @@ check_cookies(NNC_Instance inst) static int set_ntp_address(NNC_Instance inst, NTP_Remote_Address *negotiated_address) { - NTP_Remote_Address old_address, new_address; + NTP_Remote_Address old_address; + DNS_SockAddrLookupResult new_address; old_address = inst->ntp_address; - new_address = *negotiated_address; + new_address.ip_addr.service_name[0] = 0; + new_address.ip_addr.ip = negotiated_address->ip_addr; + new_address.port = negotiated_address->port; - if (new_address.ip_addr.family == IPADDR_UNSPEC) - new_address.ip_addr = inst->nts_address.ip_addr; + if (new_address.ip_addr.ip.family == IPADDR_UNSPEC) + new_address.ip_addr.ip = inst->nts_address.ip_addr; if (new_address.port == 0) new_address.port = inst->default_ntp_port; - if (UTI_CompareIPs(&old_address.ip_addr, &new_address.ip_addr, NULL) == 0 && + if (UTI_CompareIPs(&old_address.ip_addr, &new_address.ip_addr.ip, NULL) == 0 && old_address.port == new_address.port) /* Nothing to do */ return 1; if (NSR_UpdateSourceNtpAddress(&old_address, &new_address) != NSR_Success) { LOG(LOGS_ERR, "Could not change %s to negotiated address %s", - UTI_IPToString(&old_address.ip_addr), UTI_IPToString(&new_address.ip_addr)); + UTI_IPToString(&old_address.ip_addr), UTI_IPToString(&new_address.ip_addr.ip)); return 0; } - inst->ntp_address = new_address; + inst->ntp_address.ip_addr = new_address.ip_addr.ip; + inst->ntp_address.port = new_address.port; return 1; } @@ -545,12 +549,21 @@ NNC_CheckResponseAuth(NNC_Instance inst, NTP_Packet *packet, /* ================================================== */ void -NNC_ChangeAddress(NNC_Instance inst, IPAddr *address) +NNC_ChangeAddress(NNC_Instance inst, DNS_AddressLookupResult *address) { save_cookies(inst); - inst->nts_address.ip_addr = *address; - inst->ntp_address.ip_addr = *address; + inst->nts_address.ip_addr = address->ip; + inst->ntp_address.ip_addr = address->ip; + + if (address->service_name[0] != '\0') { + free(inst->name); + inst->name = strdup(address->service_name); + } + + if (address->service_port != 0) { + inst->nts_address.port = address->service_port; + } reset_instance(inst); diff --git a/nts_ntp_client.h b/nts_ntp_client.h index 2c314cca..b1928ffb 100644 --- a/nts_ntp_client.h +++ b/nts_ntp_client.h @@ -28,6 +28,7 @@ #define GOT_NTS_NTP_CLIENT_H #include "addressing.h" +#include "nameserv.h" #include "ntp.h" #include "reports.h" @@ -42,7 +43,7 @@ extern int NNC_GenerateRequestAuth(NNC_Instance inst, NTP_Packet *packet, extern int NNC_CheckResponseAuth(NNC_Instance inst, NTP_Packet *packet, NTP_PacketInfo *info); -extern void NNC_ChangeAddress(NNC_Instance inst, IPAddr *address); +extern void NNC_ChangeAddress(NNC_Instance inst, DNS_AddressLookupResult *address); extern void NNC_DumpData(NNC_Instance inst); diff --git a/privops.c b/privops.c index 3bc76d14..4914dd81 100644 --- a/privops.c +++ b/privops.c @@ -76,6 +76,7 @@ typedef struct { typedef struct { char name[256]; + int perform_srv; } ReqName2IPAddress; typedef struct { @@ -106,7 +107,7 @@ typedef struct { #endif typedef struct { - IPAddr addresses[DNS_MAX_ADDRESSES]; + DNS_AddressLookupResult addresses[DNS_MAX_ADDRESSES]; } ResName2IPAddress; typedef struct { @@ -283,7 +284,7 @@ do_name_to_ipaddress(ReqName2IPAddress *req, PrvResponse *res) req->name[sizeof (req->name) - 1] = '\0'; res->rc = DNS_Name2IPAddress(req->name, res->data.name_to_ipaddress.addresses, - DNS_MAX_ADDRESSES); + DNS_MAX_ADDRESSES, req->perform_srv); } #endif @@ -573,14 +574,14 @@ PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len) #ifdef PRIVOPS_NAME2IPADDRESS int -PRV_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs) +PRV_Name2IPAddress(const char *name, DNS_AddressLookupResult *ip_addrs, int perform_srv, int max_addrs) { PrvRequest req; PrvResponse res; int i; if (!have_helper()) - return DNS_Name2IPAddress(name, ip_addrs, max_addrs); + return DNS_Name2IPAddress(name, ip_addrs, perform_srv, max_addrs); memset(&req, 0, sizeof (req)); req.op = OP_NAME2IPADDRESS; diff --git a/privops.h b/privops.h index 146580b7..838e6419 100644 --- a/privops.h +++ b/privops.h @@ -28,6 +28,8 @@ #ifndef GOT_PRIVOPS_H #define GOT_PRIVOPS_H +#include "nameserv.h" + #ifdef PRIVOPS_ADJUSTTIME int PRV_AdjustTime(const struct timeval *delta, struct timeval *olddelta); #else @@ -53,7 +55,7 @@ int PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len); #endif #ifdef PRIVOPS_NAME2IPADDRESS -int PRV_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs); +int PRV_Name2IPAddress(const char *name, DNS_AddressLookupResult *ip_addrs, int perform_srv, int max_addrs); #else #define PRV_Name2IPAddress DNS_Name2IPAddress #endif diff --git a/test/unit/authtokens.txt b/test/unit/authtokens.txt new file mode 100644 index 00000000..acbe86c7 --- /dev/null +++ b/test/unit/authtokens.txt @@ -0,0 +1 @@ +abcd diff --git a/test/unit/ntp_auth.c b/test/unit/ntp_auth.c index e2924a33..d5adaf32 100644 --- a/test/unit/ntp_auth.c +++ b/test/unit/ntp_auth.c @@ -129,6 +129,7 @@ test_unit(void) RPT_AuthReport report; NTP_AuthMode mode; IPSockAddr nts_addr; + DNS_AddressLookupResult lookup; uint32_t key_id, kod; char conf[][100] = { "keyfile ntp_core.keys" @@ -204,8 +205,11 @@ test_unit(void) NAU_DumpData(inst); NAU_GetReport(inst, &report); - if (random() % 2) - NAU_ChangeAddress(inst, &nts_addr.ip_addr); + if (random() % 2) { + lookup.ip = nts_addr.ip_addr; + lookup.service_name[0] = 0; + NAU_ChangeAddress(inst, &lookup); + } if (inst->mode == NTP_AUTH_NTS) { for (j = random() % 5; j > 0; j--) diff --git a/test/unit/ntp_sources.c b/test/unit/ntp_sources.c index be5d2ea8..1890986f 100644 --- a/test/unit/ntp_sources.c +++ b/test/unit/ntp_sources.c @@ -32,7 +32,7 @@ static char *requested_name = NULL; static DNS_NameResolveHandler resolve_handler = NULL; static void *resolve_handler_arg = NULL; -#define DNS_Name2IPAddressAsync(name, handler, arg) \ +#define DNS_Name2IPAddressAsync(name, handler, service_nts, arg) \ requested_name = (name), \ resolve_handler = (handler), \ resolve_handler_arg = (arg) @@ -42,7 +42,7 @@ static void *resolve_handler_arg = NULL; #define NIO_IsServerConnectable(addr) (random() % 2) #define SCH_GetLastEventMonoTime() get_mono_time() -static void change_remote_address(NCR_Instance inst, NTP_Remote_Address *remote_addr, +static void change_remote_address(NCR_Instance inst, DNS_SockAddrLookupResult *remote_addr, int ntp_only); static double get_mono_time(void); @@ -53,7 +53,7 @@ static double get_mono_time(void); static void resolve_random_address(DNS_Status status, int rand_bits) { - IPAddr ip_addrs[DNS_MAX_ADDRESSES]; + DNS_AddressLookupResult addrs[DNS_MAX_ADDRESSES]; int i, n_addrs; TEST_CHECK(requested_name); @@ -61,22 +61,27 @@ resolve_random_address(DNS_Status status, int rand_bits) if (status == DNS_Success) { n_addrs = random() % DNS_MAX_ADDRESSES + 1; - for (i = 0; i < n_addrs; i++) - TST_GetRandomAddress(&ip_addrs[i], IPADDR_UNSPEC, rand_bits); + for (i = 0; i < n_addrs; i++) { + TST_GetRandomAddress(&addrs[i].ip, IPADDR_UNSPEC, rand_bits); + addrs[i].service_name[0] = 0; + } } else { n_addrs = 0; } - (resolve_handler)(status, n_addrs, ip_addrs, resolve_handler_arg); + DEBUG_LOG("Random resolving to %d addrs", n_addrs); + + (resolve_handler)(status, n_addrs, addrs, resolve_handler_arg); } static int update_random_address(NTP_Remote_Address *addr, int rand_bits) { - NTP_Remote_Address new_addr; + DNS_SockAddrLookupResult new_addr; NSR_Status status; - TST_GetRandomAddress(&new_addr.ip_addr, IPADDR_UNSPEC, rand_bits); + TST_GetRandomAddress(&new_addr.ip_addr.ip, IPADDR_UNSPEC, rand_bits); + new_addr.ip_addr.service_name[0] = 0; new_addr.port = random() % 1024; status = NSR_UpdateSourceNtpAddress(addr, &new_addr); @@ -92,19 +97,23 @@ update_random_address(NTP_Remote_Address *addr, int rand_bits) } static void -change_remote_address(NCR_Instance inst, NTP_Remote_Address *remote_addr, int ntp_only) +change_remote_address(NCR_Instance inst, DNS_SockAddrLookupResult *remote_addr, int ntp_only) { + NTP_Remote_Address rem_addr; int update = !ntp_only && random() % 4 == 0, update_pos = random() % 2, r = 0; + rem_addr.ip_addr = remote_addr->ip_addr.ip; + rem_addr.port = remote_addr->port; + TEST_CHECK(record_lock); if (update && update_pos == 0) - r = update_random_address(random() % 2 ? remote_addr : NCR_GetRemoteAddress(inst), 4); + r = update_random_address(random() % 2 ? &rem_addr : NCR_GetRemoteAddress(inst), 4); NCR_ChangeRemoteAddress(inst, remote_addr, ntp_only); if (update && update_pos == 1) - r = update_random_address(random() % 2 ? remote_addr : NCR_GetRemoteAddress(inst), 4); + r = update_random_address(random() % 2 ? &rem_addr : NCR_GetRemoteAddress(inst), 4); if (r) TEST_CHECK(UTI_IsIPReal(&saved_address_update.old_address.ip_addr)); diff --git a/test/unit/nts_ke_server.c b/test/unit/nts_ke_server.c index 5637f3e4..d7d82d9c 100644 --- a/test/unit/nts_ke_server.c +++ b/test/unit/nts_ke_server.c @@ -51,7 +51,7 @@ prepare_request(NKSN_Instance session, int valid) if (valid) index = -1; else - index = random() % 9; + index = random() % 11; DEBUG_LOG("index=%d", index); NKSN_BeginMessage(session); @@ -60,6 +60,14 @@ prepare_request(NKSN_Instance session, int valid) length = 2; assert(sizeof (data[0]) == 2); + if (random() % 3) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, data, length)); + } + + if (random() % 3) + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, "abcd", 4)); + if (index != 0) { memset(data, NKE_NEXT_PROTOCOL_NTPV4 + 1, sizeof (data)); data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4); @@ -90,7 +98,129 @@ prepare_request(NKSN_Instance session, int valid) if (index == 7) TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length)); - if (index == 8) { + if (index == 8) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, NULL, 0)); + + if (index == 9) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_ALGORITHMS, NULL, 0)); + + if (index == 10) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 1, 2000 + random() % 1000, data, length)); + } + + if (random() % 2) { + const char server[] = "127.0.0.1"; + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_NTPV4_SERVER_NEGOTIATION, + server, sizeof (server) - 1)); + } + + if (random() % 2) { + data[0] = htons(123); + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_NTPV4_PORT_NEGOTIATION, data, length)); + } + + if (random() % 2) + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_COMPLIANT_128GCM_EXPORT, NULL, 0)); + + if (random() % 2) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 0, 2000 + random() % 1000, data, length)); + } + + if (random() % 2) + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_KEEP_ALIVE, NULL, 0)); + + TEST_CHECK(NKSN_EndMessage(session)); +} + +static void +prepare_fixedkey_request(NKSN_Instance session, int valid) +{ + uint16_t data[32]; + int index, length, keytype; + + if (valid) + index = -1; + else + index = random() % 16; + DEBUG_LOG("index=%d", index); + + NKSN_BeginMessage(session); + + memset(data, 0, sizeof (data)); + length = 2; + assert(sizeof (data[0]) == 2); + + if (random() % 3) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, data, length)); + } + + if (index != 0) + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, "abcd", 4)); + + keytype = random() % 2 && SIV_GetKeyLength(AEAD_AES_128_GCM_SIV) > 0 ? + AEAD_AES_128_GCM_SIV : AEAD_AES_SIV_CMAC_256; + + if ((keytype == AEAD_AES_128_GCM_SIV) != (index == 1)) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_FIXED_KEY, data, + 2 * SIV_GetKeyLength(AEAD_AES_128_GCM_SIV))); + else + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_FIXED_KEY, data, + 2 * SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256))); + + if (index == 2) { + if (keytype == AEAD_AES_128_GCM_SIV) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_FIXED_KEY, data, + 2 * SIV_GetKeyLength(AEAD_AES_128_GCM_SIV))); + else + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_FIXED_KEY, data, + 2 * SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256))); + } + + if (index != 3) { + memset(data, NKE_NEXT_PROTOCOL_NTPV4 + 1, sizeof (data)); + data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4); + if (index == 4) + length = 0; + else if (index == 5) + length = 3 + random() % 15 * 2; + else if (index == 6) + length = 4 + random() % 15 * 2; + else + length = 2; + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, length)); + } + + if (index == 7) { + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, length)); + } + + if (index != 8) { + memset(data, keytype + 1, sizeof(data)); + data[0] = htons(keytype); + if (index == 9) + length = 0; + else if (index == 10) + length = 3 + random() % 15 * 2; + else if (index == 11) + length = 4 + random() % 15 * 2; + else + length = 2; + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length)); + } + + if (index == 12) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length)); + + if (index == 13) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, NULL, 0)); + + if (index == 14) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, NULL, 0)); + + if (index == 15) { length = random() % (sizeof (data) + 1); TEST_CHECK(NKSN_AddRecord(session, 1, 2000 + random() % 1000, data, length)); } @@ -114,6 +244,75 @@ prepare_request(NKSN_Instance session, int valid) TEST_CHECK(NKSN_AddRecord(session, 0, 2000 + random() % 1000, data, length)); } + if (random() % 2) + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_KEEP_ALIVE, NULL, 0)); + + TEST_CHECK(NKSN_EndMessage(session)); +} + +static void +prepare_support_request(NKSN_Instance session, int valid) +{ + uint16_t data[16]; + int index, length; + + if (valid) + index = -1; + else + index = random() % 6; + DEBUG_LOG("index=%d", index); + + NKSN_BeginMessage(session); + + memset(data, 0, sizeof (data)); + length = 2; + assert(sizeof (data[0]) == 2); + + if (random() % 3) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, data, length)); + } + + if (index != 0) + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, "abcd", 4)); + + if ((random() % 3) || (index == 1)) { + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, NULL, 0)); + if ((random() % 2) || (index == 2)) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_ALGORITHMS, NULL, 0)); + } else { + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_ALGORITHMS, NULL, 0)); + } + + if (index == 1) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, NULL, 0)); + + if (index == 2) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_ALGORITHMS, NULL, 0)); + + if (index == 3) { + data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4); + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, 2)); + } + + if (index == 4) { + data[0] = htons(AEAD_AES_SIV_CMAC_256); + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, 2)); + } + + if (index == 5) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 1, 2000 + random() % 1000, data, length)); + } + + if (random() % 2) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 0, 2000 + random() % 1000, data, length)); + } + + if (random() % 2) + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_KEEP_ALIVE, NULL, 0)); + TEST_CHECK(NKSN_EndMessage(session)); } @@ -137,6 +336,26 @@ process_response(NKSN_Instance session, int valid) } } +static void +process_support_response(NKSN_Instance session, int valid) +{ + int records, errors, critical, type, length; + + for (records = errors = 0; ; records++) { + if (!NKSN_GetRecord(session, &critical, &type, &length, NULL, 0)) + break; + if (type == NKE_RECORD_ERROR) + errors++; + } + + if (valid) { + TEST_CHECK(records >= 1); + } else { + TEST_CHECK(records == 1); + TEST_CHECK(errors == 1); + } +} + void test_unit(void) { @@ -152,6 +371,7 @@ test_unit(void) "ntsprocesses 0", "ntsserverkey nts_ke.key", "ntsservercert nts_ke.crt", + "ntsauthtokenfile authtokens.txt", }; CNF_Initialise(0, 0); @@ -175,6 +395,19 @@ test_unit(void) process_response(session, valid); } + for (i = 0; i < 10000; i++) { + valid = random() % 2; + prepare_fixedkey_request(session, valid); + TEST_CHECK(process_request(session)); + process_response(session, valid); + } + + for (i = 0; i < 10000; i++) { + valid = random() % 2; + prepare_support_request(session, valid); + TEST_CHECK(process_request(session)); + process_support_response(session, valid); + } for (i = 0; i < 10000; i++) { context.algorithm = AEAD_AES_SIV_CMAC_256; diff --git a/test/unit/nts_ntp_client.c b/test/unit/nts_ntp_client.c index 4b567324..501c92ae 100644 --- a/test/unit/nts_ntp_client.c +++ b/test/unit/nts_ntp_client.c @@ -245,7 +245,7 @@ test_unit(void) NTP_PacketInfo info; NTP_Packet packet; IPSockAddr addr; - IPAddr ip_addr; + DNS_AddressLookupResult ip_addr; int i, j, prev_num_cookies, valid; TEST_CHECK(SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256) > 0); @@ -293,9 +293,10 @@ test_unit(void) TEST_CHECK(inst->ok_response == valid); if (random() % 10 == 0) { - TST_GetRandomAddress(&ip_addr, IPADDR_INET4, 32); + TST_GetRandomAddress(&ip_addr.ip, IPADDR_INET4, 32); + ip_addr.service_name[0] = 0; NNC_ChangeAddress(inst, &ip_addr); - TEST_CHECK(UTI_CompareIPs(&inst->nts_address.ip_addr, &ip_addr, NULL) == 0); + TEST_CHECK(UTI_CompareIPs(&inst->nts_address.ip_addr, &ip_addr.ip, NULL) == 0); } } diff --git a/util.c b/util.c index 404e5b74..7ac4d428 100644 --- a/util.c +++ b/util.c @@ -559,6 +559,23 @@ UTI_IPSockAddrToString(const IPSockAddr *sa) /* ================================================== */ +char * +UTI_DNS_SockAddrLookupResultToString(const DNS_SockAddrLookupResult *sa) +{ + char buf[BUFFER_LENGTH], *result; + + /* Copy to a separate buffer to avoid a compiler warning */ + snprintf(buf, sizeof (buf), "%s", UTI_IPToString(&sa->ip_addr.ip)); + + result = NEXT_BUFFER; + snprintf(result, BUFFER_LENGTH, + sa->ip_addr.ip.family != IPADDR_INET6 ? "%s:%hu" : "[%s]:%hu", buf, sa->port); + + return result; +} + +/* ================================================== */ + char * UTI_IPSubnetToString(IPAddr *subnet, int bits) { diff --git a/util.h b/util.h index 682c58f5..5147d005 100644 --- a/util.h +++ b/util.h @@ -30,6 +30,7 @@ #include "sysincl.h" #include "addressing.h" +#include "nameserv.h" #include "ntp.h" #include "candm.h" #include "cmac.h" @@ -119,6 +120,7 @@ extern void UTI_IPNetworkToHost(const IPAddr *src, IPAddr *dest); extern int UTI_CompareIPs(const IPAddr *a, const IPAddr *b, const IPAddr *mask); extern char *UTI_IPSockAddrToString(const IPSockAddr *sa); +extern char *UTI_DNS_SockAddrLookupResultToString(const DNS_SockAddrLookupResult *sa); extern char *UTI_IPSubnetToString(IPAddr *subnet, int bits);