diff --git a/src/context.c b/src/context.c index eb242be0..87071f7b 100644 --- a/src/context.c +++ b/src/context.c @@ -54,6 +54,7 @@ #include "dnssec.h" #include "stub.h" #include "list.h" +#include "dict.h" #define GETDNS_PORT_ZERO 0 #define GETDNS_PORT_DNS 53 @@ -167,7 +168,7 @@ static inline void canonicalize_dname(uint8_t *dname) { uint8_t *next_label; - while (*dname) { + while (*dname && !(*dname & 0xC0)) { next_label = dname + *dname + 1; dname += 1; while (dname < next_label) { @@ -599,6 +600,64 @@ net_req_query_id_cmp(const void *id1, const void *id2) return (intptr_t)id1 - (intptr_t)id2; } +static getdns_tsig_info tsig_info[] = { + { GETDNS_NO_TSIG, NULL, 0, NULL, 0, 0, 0 } + , { GETDNS_HMAC_MD5 , "hmac-md5.sig-alg.reg.int", 24 + , (uint8_t *)"\x08hmac-md5\x07sig-alg\x03reg\x03int", 26, 10, 16 } + , { GETDNS_NO_TSIG, NULL, 0, NULL, 0, 0, 0 } + , { GETDNS_HMAC_SHA1 , "hmac-sha1" , 9 + , (uint8_t *)"\x09hmac-sha1" , 11, 10, 20 } + , { GETDNS_HMAC_SHA224, "hmac-sha224", 11 + , (uint8_t *)"\x0bhmac-sha224", 13, 14, 28 } + , { GETDNS_HMAC_SHA224, "hmac-sha256", 11 + , (uint8_t *)"\x0bhmac-sha256", 13, 16, 32 } + , { GETDNS_HMAC_SHA224, "hmac-sha384", 11 + , (uint8_t *)"\x0bhmac-sha383", 13, 24, 48 } + , { GETDNS_HMAC_SHA224, "hmac-sha512", 11 + , (uint8_t *)"\x0bhmac-sha512", 13, 32, 64 } + , { GETDNS_HMAC_MD5 , "hmac-md5" , 8 + , (uint8_t *)"\x08hmac-md5" , 10, 10, 16 } +}; + +const getdns_tsig_info *_getdns_get_tsig_info(getdns_tsig_algo tsig_alg) +{ + return tsig_alg > sizeof(tsig_info) - 1 + || tsig_info[tsig_alg].alg == GETDNS_NO_TSIG ? NULL + : &tsig_info[tsig_alg]; +} + +static const getdns_tsig_algo _getdns_get_tsig_algo(getdns_bindata *algo) +{ + getdns_tsig_info *i; + + if (!algo || algo->size == 0) + return GETDNS_NO_TSIG; + + if (algo->data[algo->size-1] != 0) { + /* Unterminated string */ + for (i = tsig_info; i < tsig_info + sizeof(tsig_info); i++) + if (algo->size == i->strlen_name && + strncasecmp((const char *)algo->data, i->name, + i->strlen_name) == 0) + return i->alg; + + } else if (!_getdns_bindata_is_dname(algo)) { + /* Terminated string */ + for (i = tsig_info; i < tsig_info + sizeof(tsig_info); i++) + if (algo->size - 1 == i->strlen_name && + strncasecmp((const char *)algo->data, i->name, + i->strlen_name) == 0) + return i->alg; + + } else { + /* fqdn, canonical_dname_compare is now safe to use! */ + for (i = tsig_info; i < tsig_info + sizeof(tsig_info); i++) + if (canonical_dname_compare(algo->data, i->dname) == 0) + return i->alg; + } + return GETDNS_NO_TSIG; +} + static void upstream_init(getdns_upstream *upstream, getdns_upstreams *parent, struct addrinfo *ai) @@ -635,6 +694,10 @@ upstream_init(getdns_upstream *upstream, upstream->has_prev_client_cookie = 0; upstream->has_server_cookie = 0; + upstream->tsig_alg = GETDNS_NO_TSIG; + upstream->tsig_dname_len = 0; + upstream->tsig_size = 0; + /* Tracking of network requests on this socket */ _getdns_rbtree_init(&upstream->netreq_by_query_id, net_req_query_id_cmp); @@ -1715,15 +1778,21 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, upstreams = upstreams_create( context, count * GETDNS_UPSTREAM_TRANSPORTS); for (i = 0; i < count; i++) { - getdns_dict *dict; + getdns_dict *dict; getdns_bindata *address_type; getdns_bindata *address_data; getdns_bindata *tls_auth_name; struct sockaddr_storage addr; - getdns_bindata *scope_id; + getdns_bindata *scope_id; getdns_upstream *upstream; + getdns_bindata *tsig_alg_name, *tsig_name, *tsig_key; + getdns_tsig_algo tsig_alg; + char tsig_name_str[1024]; + uint8_t tsig_dname_spc[256], *tsig_dname; + size_t tsig_dname_len; + if ((r = getdns_list_get_dict(upstream_list, i, &dict))) goto error; @@ -1760,6 +1829,63 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, eos[scope_id->size] = 0; } + tsig_alg_name = tsig_name = tsig_key = NULL; + tsig_dname = NULL; + tsig_dname_len = 0; + + if (getdns_dict_get_bindata(dict, + "tsig_algorithm", &tsig_alg_name) == GETDNS_RETURN_GOOD) + tsig_alg = _getdns_get_tsig_algo(tsig_alg_name); + else + tsig_alg = GETDNS_HMAC_MD5; + + if (getdns_dict_get_bindata(dict, "tsig_name", &tsig_name)) + tsig_alg = GETDNS_NO_TSIG; /* No name, no TSIG */ + + else if (tsig_name->size == 0) + tsig_alg = GETDNS_NO_TSIG; + + else if (tsig_name->data[tsig_name->size - 1] != 0) { + /* Unterminated string */ + if (tsig_name->size >= sizeof(tsig_name_str) - 1) + tsig_alg = GETDNS_NO_TSIG; + else { + (void) memcpy(tsig_name_str, tsig_name->data + , tsig_name->size); + tsig_name_str[tsig_name->size] = 0; + + tsig_dname_len = sizeof(tsig_dname_spc); + if (gldns_str2wire_dname_buf(tsig_name_str, + tsig_dname_spc, &tsig_dname_len)) + tsig_alg = GETDNS_NO_TSIG; + else + tsig_dname = tsig_dname_spc; + } + } else if (!_getdns_bindata_is_dname(tsig_name)) { + /* Terminated string */ + tsig_dname_len = sizeof(tsig_dname_spc); + if (gldns_str2wire_dname_buf(tsig_name_str, + tsig_dname_spc, &tsig_dname_len)) + tsig_alg = GETDNS_NO_TSIG; + else + tsig_dname = tsig_dname_spc; + + } else if (tsig_name->size > sizeof(tsig_dname_spc)) + tsig_alg = GETDNS_NO_TSIG; + + else { + /* fqdn */ + tsig_dname = memcpy(tsig_dname_spc, tsig_name->data + , tsig_name->size); + tsig_dname_len = tsig_name->size; + } + if (getdns_dict_get_bindata(dict, "tsig_secret", &tsig_key)) + tsig_alg = GETDNS_NO_TSIG; /* No key, no TSIG */ + + /* Don't check TSIG length contraints here. + * Let the upstream decide what is secure enough. + */ + /* Loop to create upstreams as needed*/ for (size_t j = 0; j < GETDNS_UPSTREAM_TRANSPORTS; j++) { uint32_t port; @@ -1798,6 +1924,25 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, upstream->tls_auth_name[tls_auth_name->size] = '\0'; } } + if ((upstream->tsig_alg = tsig_alg)) { + if (tsig_name) { + (void) memcpy(upstream->tsig_dname, + tsig_dname, tsig_dname_len); + upstream->tsig_dname_len = + tsig_dname_len; + } else + upstream->tsig_dname_len = 0; + + if (tsig_key) { + (void) memcpy(upstream->tsig_key, + tsig_key->data, tsig_key->size); + upstream->tsig_size = tsig_key->size; + } else + upstream->tsig_size = 0; + } else { + upstream->tsig_dname_len = 0; + upstream->tsig_size = 0; + } upstreams->count++; freeaddrinfo(ai); } @@ -2542,9 +2687,12 @@ upstream_port(getdns_upstream *upstream) } static getdns_dict* -_get_context_settings(getdns_context* context) { +_get_context_settings(getdns_context* context) +{ getdns_return_t r = GETDNS_RETURN_GOOD; getdns_dict* result = getdns_dict_create_with_context(context); + getdns_list *upstreams; + if (!result) { return NULL; } @@ -2562,34 +2710,8 @@ _get_context_settings(getdns_context* context) { r |= getdns_dict_set_int(result, "append_name", context->append_name); /* list fields */ if (context->suffix) r |= getdns_dict_set_list(result, "suffix", context->suffix); - if (context->upstreams && context->upstreams->count > 0) { - size_t i; - getdns_upstream *upstream; - getdns_list *upstreams = - getdns_list_create_with_context(context); - - for (i = 0; i < context->upstreams->count;) { - size_t j; - getdns_dict *d; - upstream = &context->upstreams->upstreams[i]; - d = sockaddr_dict(context, - (struct sockaddr *)&upstream->addr); - for ( j = 1, i++ - ; j < GETDNS_UPSTREAM_TRANSPORTS && - i < context->upstreams->count - ; j++, i++) { - - upstream = &context->upstreams->upstreams[i]; - if (upstream->transport != GETDNS_TRANSPORT_TLS) - continue; - if (upstream_port(upstream) != getdns_port_array[j]) - continue; - (void) getdns_dict_set_int(d, "tls_port", - (uint32_t) upstream_port(upstream)); - } - r |= _getdns_list_append_dict(upstreams, d); - getdns_dict_destroy(d); - } + + if (!getdns_context_get_upstream_recursive_servers(context, &upstreams)) { r |= getdns_dict_set_list(result, "upstream_recursive_servers", upstreams); getdns_list_destroy(upstreams); @@ -2956,43 +3078,88 @@ getdns_context_get_dnssec_allowed_skew(getdns_context *context, getdns_return_t getdns_context_get_upstream_recursive_servers(getdns_context *context, - getdns_list **upstream_list) { - RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER); - RETURN_IF_NULL(upstream_list, GETDNS_RETURN_INVALID_PARAMETER); - *upstream_list = NULL; - if (context->upstreams && context->upstreams->count > 0) { - getdns_return_t r = GETDNS_RETURN_GOOD; - size_t i; - getdns_upstream *upstream; - getdns_list *upstreams = getdns_list_create(); - for (i = 0; i < context->upstreams->count;) { + getdns_list **upstreams_r) +{ + size_t i; + getdns_list *upstreams; + getdns_return_t r; + + if (!context || !upstreams_r) + return GETDNS_RETURN_INVALID_PARAMETER; + + if (!(upstreams = getdns_list_create_with_context(context))) + return GETDNS_RETURN_MEMORY_ERROR; + + if (!context->upstreams || context->upstreams->count == 0) { + *upstreams_r = upstreams; + return GETDNS_RETURN_GOOD; + } + r = GETDNS_RETURN_GOOD; + i = 0; + while (!r && i < context->upstreams->count) { size_t j; getdns_dict *d; - upstream = &context->upstreams->upstreams[i]; - d = sockaddr_dict(context, (struct sockaddr *)&upstream->addr); + getdns_upstream *upstream = &context->upstreams->upstreams[i]; + getdns_bindata bindata; + const getdns_tsig_info *tsig_info; + + if (!(d = + sockaddr_dict(context, (struct sockaddr*)&upstream->addr))) { + r = GETDNS_RETURN_MEMORY_ERROR; + break; + } + if (upstream->tsig_alg) { + tsig_info = _getdns_get_tsig_info(upstream->tsig_alg); + + bindata.data = tsig_info->dname; + bindata.size = tsig_info->dname_len; + if ((r = getdns_dict_set_bindata( + d, "tsig_algorithm", &bindata))) + break; + + if (upstream->tsig_dname_len) { + bindata.data = upstream->tsig_dname; + bindata.size = upstream->tsig_dname_len; + if ((r = getdns_dict_set_bindata( + d, "tsig_name", &bindata))) + break; + } + if (upstream->tsig_size) { + bindata.data = upstream->tsig_key; + bindata.size = upstream->tsig_size; + if ((r = getdns_dict_set_bindata( + d, "tsig_secret", &bindata))) + break; + } + } for ( j = 1, i++ ; j < GETDNS_UPSTREAM_TRANSPORTS && i < context->upstreams->count ; j++, i++) { upstream = &context->upstreams->upstreams[i]; - if (upstream->transport != GETDNS_TRANSPORT_TLS) - continue; - if (upstream_port(upstream) != getdns_port_array[j]) - continue; - (void) getdns_dict_set_int(d, "tls_port", - (uint32_t) upstream_port(upstream)); + + if (upstream->transport == GETDNS_TRANSPORT_UDP && + upstream_port(upstream) != getdns_port_array[j] && + (r = getdns_dict_set_int(d, "port", + (uint32_t)upstream_port(upstream)))) + break; + + if (upstream->transport == GETDNS_TRANSPORT_TLS && + upstream_port(upstream) != getdns_port_array[j] && + (r = getdns_dict_set_int(d, "tls_port", + (uint32_t)upstream_port(upstream)))) + break; } - r |= _getdns_list_append_dict(upstreams, d); + if (!r) + r = _getdns_list_append_dict(upstreams, d); getdns_dict_destroy(d); } - if (r != GETDNS_RETURN_GOOD) { - getdns_list_destroy(upstreams); - return GETDNS_RETURN_MEMORY_ERROR; - } - *upstream_list = upstreams; - } - return GETDNS_RETURN_GOOD; + if (r) + getdns_list_destroy(upstreams); + else + *upstreams_r = upstreams; + return r; } getdns_return_t diff --git a/src/context.h b/src/context.h index 1e489d2d..9df5369a 100644 --- a/src/context.h +++ b/src/context.h @@ -79,6 +79,29 @@ typedef enum getdns_tls_hs_state { GETDNS_HS_FAILED } getdns_tls_hs_state_t; +typedef enum getdns_tsig_algo { + GETDNS_NO_TSIG = 0, /* Do not use tsig */ + GETDNS_HMAC_MD5 = 1, /* 128 bits */ + GETDNS_GSS_TSIG = 2, /* Not supported */ + GETDNS_HMAC_SHA1 = 3, /* 160 bits */ + GETDNS_HMAC_SHA224 = 4, + GETDNS_HMAC_SHA256 = 5, + GETDNS_HMAC_SHA384 = 6, + GETDNS_HMAC_SHA512 = 7 +} getdns_tsig_algo; + +typedef struct getdns_tsig_info { + getdns_tsig_algo alg; + const char *name; + size_t strlen_name; + const uint8_t *dname; + size_t dname_len; + size_t min_size; /* in # octets */ + size_t max_size; /* Actual size in # octets */ +} getdns_tsig_info; + +const getdns_tsig_info *_getdns_get_tsig_info(getdns_tsig_algo tsig_alg); + typedef struct getdns_upstream { /* backpointer to containing upstreams structure */ struct getdns_upstreams *upstreams; @@ -120,6 +143,13 @@ typedef struct getdns_upstream { unsigned has_server_cookie : 1; unsigned server_cookie_len : 5; + /* TSIG */ + uint8_t tsig_dname[256]; + size_t tsig_dname_len; + size_t tsig_size; + uint8_t tsig_key[256]; + getdns_tsig_algo tsig_alg; + } getdns_upstream; typedef struct getdns_upstreams { diff --git a/src/dict.c b/src/dict.c index 822d5deb..5fa1c67c 100644 --- a/src/dict.c +++ b/src/dict.c @@ -656,12 +656,15 @@ getdns_indent(size_t indent) return spaces + 80 - (indent < 80 ? indent : 0); } /* getdns_indent */ -static int +int _getdns_bindata_is_dname(getdns_bindata *bindata) { size_t i = 0, n_labels = 0; while (i < bindata->size && bindata->data[i]) { + if (bindata->data[i] & 0xC0) /* Compression pointer! */ + return 0; + i += ((size_t)bindata->data[i]) + 1; n_labels++; } diff --git a/src/dict.h b/src/dict.h index c10a2bd8..90372dfb 100644 --- a/src/dict.h +++ b/src/dict.h @@ -71,6 +71,11 @@ getdns_return_t _getdns_dict_find( getdns_return_t _getdns_dict_find_and_add( getdns_dict *dict, const char *key, getdns_item **item); +/* Return 1 (true) if bindata can be interpreted as an + * uncompressed dname. + */ +int _getdns_bindata_is_dname(getdns_bindata *bindata); + #endif /* dict.h */