mirror of https://github.com/getdnsapi/getdns.git
1576 lines
45 KiB
C
1576 lines
45 KiB
C
/**
|
|
*
|
|
* \file util-internal.c
|
|
* @brief private library routines
|
|
*
|
|
* These routines are not intended to be used by applications calling into
|
|
* the library.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2013, NLnet Labs, Verisign, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the names of the copyright holders nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL Verisign, Inc. BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include "config.h"
|
|
#include "getdns/getdns.h"
|
|
#include "dict.h"
|
|
#include "list.h"
|
|
#include "util-internal.h"
|
|
#include "types-internal.h"
|
|
#include "rr-dict.h"
|
|
#if defined(WIRE_DEBUG) && WIRE_DEBUG
|
|
#include "gldns/wire2str.h"
|
|
#endif
|
|
#include "gldns/str2wire.h"
|
|
#include "gldns/gbuffer.h"
|
|
#include "gldns/pkthdr.h"
|
|
#include "dnssec.h"
|
|
|
|
|
|
getdns_return_t
|
|
getdns_dict_util_get_string(getdns_dict * dict, char *name, char **result)
|
|
{
|
|
struct getdns_bindata *bindata = NULL;
|
|
if (!result) {
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
}
|
|
*result = NULL;
|
|
getdns_dict_get_bindata(dict, name, &bindata);
|
|
if (!bindata) {
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
}
|
|
*result = (char *) bindata->data;
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
getdns_return_t
|
|
_getdns_dict_to_sockaddr(struct getdns_dict * ns, struct sockaddr_storage * output)
|
|
{
|
|
char *address_type = NULL;
|
|
struct getdns_bindata *address_data = NULL;
|
|
uint32_t port = 53;
|
|
memset(output, 0, sizeof(struct sockaddr_storage));
|
|
output->ss_family = AF_UNSPEC;
|
|
|
|
uint32_t prt = 0;
|
|
if (getdns_dict_get_int(ns, GETDNS_STR_PORT,
|
|
&prt) == GETDNS_RETURN_GOOD) {
|
|
port = prt;
|
|
}
|
|
|
|
getdns_dict_util_get_string(ns, GETDNS_STR_ADDRESS_TYPE,
|
|
&address_type);
|
|
getdns_dict_get_bindata(ns, GETDNS_STR_ADDRESS_DATA, &address_data);
|
|
if (!address_type || !address_data) {
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
}
|
|
if (strncmp(GETDNS_STR_IPV4, address_type,
|
|
strlen(GETDNS_STR_IPV4)) == 0) {
|
|
/* data is an in_addr_t */
|
|
struct sockaddr_in *addr = (struct sockaddr_in *) output;
|
|
addr->sin_family = AF_INET;
|
|
addr->sin_port = htons((uint16_t) port);
|
|
memcpy(&(addr->sin_addr), address_data->data,
|
|
address_data->size);
|
|
} else {
|
|
/* data is a v6 addr in host order */
|
|
struct sockaddr_in6 *addr = (struct sockaddr_in6 *) output;
|
|
addr->sin6_family = AF_INET6;
|
|
addr->sin6_port = htons((uint16_t) port);
|
|
memcpy(&(addr->sin6_addr), address_data->data,
|
|
address_data->size);
|
|
}
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
getdns_return_t
|
|
_getdns_sockaddr_to_dict(struct getdns_context *context, struct sockaddr_storage *address,
|
|
struct getdns_dict ** output)
|
|
{
|
|
if (!output || !address) {
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
}
|
|
struct getdns_bindata addr_data;
|
|
*output = NULL;
|
|
struct getdns_dict *result = getdns_dict_create_with_context(context);
|
|
if (address->ss_family == AF_INET) {
|
|
struct sockaddr_in *addr = (struct sockaddr_in *) address;
|
|
getdns_dict_util_set_string(result, GETDNS_STR_ADDRESS_TYPE,
|
|
GETDNS_STR_IPV4);
|
|
addr_data.size = sizeof(addr->sin_addr);
|
|
addr_data.data = (uint8_t *) & (addr->sin_addr);
|
|
getdns_dict_set_bindata(result, GETDNS_STR_ADDRESS_DATA,
|
|
&addr_data);
|
|
} else if (address->ss_family == AF_INET6) {
|
|
struct sockaddr_in6 *addr = (struct sockaddr_in6 *) address;
|
|
getdns_dict_util_set_string(result, GETDNS_STR_ADDRESS_TYPE,
|
|
GETDNS_STR_IPV6);
|
|
addr_data.size = sizeof(addr->sin6_addr);
|
|
addr_data.data = (uint8_t *) & (addr->sin6_addr);
|
|
getdns_dict_set_bindata(result, GETDNS_STR_ADDRESS_DATA,
|
|
&addr_data);
|
|
} else {
|
|
// invalid
|
|
getdns_dict_destroy(result);
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
}
|
|
*output = result;
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
getdns_dict *
|
|
_getdns_rr_iter2rr_dict_canonical(
|
|
struct mem_funcs *mf, _getdns_rr_iter *i, uint32_t *orig_ttl)
|
|
{
|
|
getdns_dict *rr_dict, *rdata_dict;
|
|
const uint8_t *bin_data;
|
|
size_t bin_size, owner_len = 0, rdata_sz;
|
|
uint32_t int_val = 0;
|
|
enum wf_data_type { wf_int, wf_bindata, wf_special } val_type;
|
|
_getdns_rdf_iter rdf_storage, *rdf;
|
|
getdns_list *repeat_list = NULL;
|
|
getdns_dict *repeat_dict = NULL;
|
|
uint8_t ff_bytes[256];
|
|
uint16_t rr_type;
|
|
int canonicalize;
|
|
gldns_buffer gbuf;
|
|
getdns_bindata *bindata;
|
|
uint8_t *data;
|
|
|
|
assert(i);
|
|
if (!(rr_dict = _getdns_dict_create_with_mf(mf)))
|
|
return NULL;
|
|
|
|
bin_data = _getdns_owner_if_or_as_decompressed(
|
|
i, ff_bytes, &bin_size);
|
|
|
|
if (orig_ttl) {
|
|
if (bin_data != ff_bytes)
|
|
bin_data = memcpy(ff_bytes, bin_data, bin_size);
|
|
_dname_canonicalize2(ff_bytes);
|
|
owner_len = bin_size;
|
|
}
|
|
/* question */
|
|
if (_getdns_rr_iter_section(i) == SECTION_QUESTION) {
|
|
|
|
if (getdns_dict_set_int(rr_dict, "qtype",
|
|
(uint32_t) gldns_read_uint16(i->rr_type)) ||
|
|
|
|
getdns_dict_set_int(rr_dict, "qclass",
|
|
(uint32_t) gldns_read_uint16(i->rr_type + 2)) ||
|
|
|
|
_getdns_dict_set_const_bindata(
|
|
rr_dict, "qname", bin_size, bin_data)) {
|
|
|
|
goto error;
|
|
}
|
|
return rr_dict;
|
|
}
|
|
if (getdns_dict_set_int(rr_dict, "type",
|
|
(uint32_t)(rr_type = gldns_read_uint16(i->rr_type)))) {
|
|
|
|
goto error;
|
|
}
|
|
canonicalize = orig_ttl && _dnssec_rdata_to_canonicalize(rr_type)
|
|
&& (i->rr_type + 12 <= i->nxt) /* To estimate rdata size */;
|
|
|
|
if (rr_type == GETDNS_RRTYPE_OPT) {
|
|
int_val = gldns_read_uint16(i->rr_type + 6);
|
|
|
|
if (getdns_dict_set_int(rr_dict, "udp_payload_size",
|
|
(uint32_t) gldns_read_uint16(i->rr_type + 2)) ||
|
|
|
|
getdns_dict_set_int(rr_dict, "extended_rcode",
|
|
(uint32_t) *(i->rr_type + 4)) ||
|
|
|
|
getdns_dict_set_int(rr_dict, "version",
|
|
(uint32_t) *(i->rr_type + 5)) ||
|
|
|
|
getdns_dict_set_int(rr_dict, "do",
|
|
(uint32_t) ((int_val & 0x8000) >> 15)) ||
|
|
|
|
getdns_dict_set_int(rr_dict, "z",
|
|
(uint32_t) (int_val & 0x7FF))) {
|
|
|
|
goto error;
|
|
}
|
|
} else if (getdns_dict_set_int(rr_dict, "class",
|
|
(uint32_t) gldns_read_uint16(i->rr_type + 2)) ||
|
|
|
|
getdns_dict_set_int(rr_dict, "ttl",
|
|
( orig_ttl && rr_type != GETDNS_RRTYPE_RRSIG
|
|
? *orig_ttl : (uint32_t) gldns_read_uint32(i->rr_type + 4))) ||
|
|
|
|
_getdns_dict_set_const_bindata(
|
|
rr_dict, "name", bin_size, bin_data)) {
|
|
|
|
goto error;
|
|
}
|
|
if (!(rdata_dict = _getdns_dict_create_with_mf(mf)))
|
|
return NULL;
|
|
|
|
if (i->rr_type + 10 <= i->nxt && !canonicalize) {
|
|
bin_size = i->nxt - (i->rr_type + 10);
|
|
bin_data = i->rr_type + 10;
|
|
if (_getdns_dict_set_const_bindata(
|
|
rdata_dict, "rdata_raw", bin_size, bin_data))
|
|
goto rdata_error;
|
|
}
|
|
if (canonicalize)
|
|
rdata_sz = 0;
|
|
|
|
for ( rdf = _getdns_rdf_iter_init(&rdf_storage, i)
|
|
; rdf; rdf = _getdns_rdf_iter_next(rdf)) {
|
|
if (canonicalize && !(rdf->rdd_pos->type & GETDNS_RDF_DNAME)) {
|
|
rdata_sz += rdf->nxt - rdf->pos;
|
|
}
|
|
if (rdf->rdd_pos->type & GETDNS_RDF_INTEGER) {
|
|
val_type = wf_int;
|
|
switch (rdf->rdd_pos->type & GETDNS_RDF_FIXEDSZ) {
|
|
case 1: int_val = *rdf->pos;
|
|
break;
|
|
case 2: int_val = gldns_read_uint16(rdf->pos);
|
|
break;
|
|
case 4: int_val = gldns_read_uint32(rdf->pos);
|
|
break;
|
|
default:
|
|
goto rdata_error;
|
|
}
|
|
} else if (rdf->rdd_pos->type & GETDNS_RDF_DNAME) {
|
|
val_type = wf_bindata;
|
|
|
|
bin_data = _getdns_rdf_if_or_as_decompressed(
|
|
rdf, ff_bytes, &bin_size);
|
|
|
|
if (canonicalize) {
|
|
if (bin_data != ff_bytes)
|
|
bin_data = memcpy(ff_bytes, bin_data, bin_size);
|
|
_dname_canonicalize2(ff_bytes);
|
|
rdata_sz += bin_size;
|
|
}
|
|
} else if (rdf->rdd_pos->type & GETDNS_RDF_BINDATA) {
|
|
val_type = wf_bindata;
|
|
if (rdf->rdd_pos->type & GETDNS_RDF_FIXEDSZ) {
|
|
bin_size = rdf->rdd_pos->type
|
|
& GETDNS_RDF_FIXEDSZ;
|
|
bin_data = rdf->pos;
|
|
|
|
} else switch(rdf->rdd_pos->type & GETDNS_RDF_LEN_VAL){
|
|
case 0x100:
|
|
bin_size = *rdf->pos;
|
|
bin_data = rdf->pos + 1;
|
|
break;
|
|
case 0x200:
|
|
bin_size = gldns_read_uint16(rdf->pos);
|
|
bin_data = rdf->pos + 2;
|
|
break;
|
|
default:
|
|
bin_size = rdf->nxt - rdf->pos;
|
|
bin_data = rdf->pos;
|
|
break;
|
|
}
|
|
} else if (rdf->rdd_pos->type == GETDNS_RDF_SPECIAL)
|
|
val_type = wf_special;
|
|
else
|
|
assert(((val_type = wf_int), 0));
|
|
|
|
if (! rdf->rdd_repeat) {
|
|
switch (val_type) {
|
|
case wf_int:
|
|
if (getdns_dict_set_int(rdata_dict,
|
|
rdf->rdd_pos->name, int_val))
|
|
goto rdata_error;
|
|
break;
|
|
case wf_bindata:
|
|
if (_getdns_dict_set_const_bindata(rdata_dict,
|
|
rdf->rdd_pos->name, bin_size, bin_data))
|
|
goto rdata_error;
|
|
break;
|
|
case wf_special:
|
|
if (rdf->rdd_pos->special->wire2dict(
|
|
rdata_dict, rdf->pos))
|
|
goto rdata_error;
|
|
default:
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
if (rdf->rdd_pos == rdf->rdd_repeat) {
|
|
/* list with rdf values */
|
|
|
|
if (! repeat_list && !(repeat_list =
|
|
_getdns_list_create_with_mf(mf)))
|
|
|
|
goto rdata_error;
|
|
|
|
switch (val_type) {
|
|
case wf_int:
|
|
if (_getdns_list_append_int(repeat_list,
|
|
int_val))
|
|
goto rdata_error;
|
|
break;
|
|
case wf_bindata:
|
|
if (_getdns_list_append_const_bindata(
|
|
repeat_list, bin_size, bin_data))
|
|
goto rdata_error;
|
|
break;
|
|
case wf_special:
|
|
if (rdf->rdd_pos->special->wire2list(
|
|
repeat_list, rdf->pos))
|
|
goto rdata_error;
|
|
default:
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
if (rdf->rdd_pos == rdf->rdd_repeat + 1) {
|
|
|
|
if (repeat_dict) {
|
|
if (! repeat_list && !(repeat_list =
|
|
_getdns_list_create_with_mf(mf)))
|
|
goto rdata_error;
|
|
|
|
if (_getdns_list_append_this_dict(
|
|
repeat_list, repeat_dict))
|
|
goto rdata_error;
|
|
|
|
repeat_dict = NULL;
|
|
}
|
|
if (!(repeat_dict =
|
|
_getdns_dict_create_with_mf(mf)))
|
|
goto rdata_error;
|
|
}
|
|
assert(repeat_dict);
|
|
switch (val_type) {
|
|
case wf_int:
|
|
if (getdns_dict_set_int(repeat_dict,
|
|
rdf->rdd_pos->name, int_val))
|
|
goto rdata_error;
|
|
break;
|
|
case wf_bindata:
|
|
if (_getdns_dict_set_const_bindata(repeat_dict,
|
|
rdf->rdd_pos->name, bin_size, bin_data))
|
|
goto rdata_error;
|
|
break;
|
|
case wf_special:
|
|
if (rdf->rdd_pos->special->wire2dict(
|
|
repeat_dict, rdf->pos))
|
|
goto rdata_error;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (repeat_dict) {
|
|
if (!repeat_list && !(repeat_list =
|
|
_getdns_list_create_with_mf(mf)))
|
|
goto rdata_error;
|
|
if (_getdns_list_append_this_dict(repeat_list, repeat_dict))
|
|
goto rdata_error;
|
|
repeat_dict = NULL;
|
|
}
|
|
if (repeat_list) {
|
|
if (_getdns_dict_set_this_list(rdata_dict,
|
|
rdf_storage.rdd_repeat->name, repeat_list))
|
|
goto rdata_error;
|
|
repeat_list = NULL;
|
|
}
|
|
if (_getdns_dict_set_this_dict(rr_dict, "rdata", rdata_dict))
|
|
goto rdata_error;
|
|
|
|
if (canonicalize && rdata_sz) {
|
|
if (!(data = GETDNS_XMALLOC(
|
|
*mf, uint8_t, owner_len + 10 + rdata_sz)))
|
|
return rr_dict;
|
|
|
|
gldns_buffer_init_frm_data(&gbuf, data, owner_len+10+rdata_sz);
|
|
if (_getdns_rr_dict2wire(rr_dict, &gbuf) ||
|
|
gldns_buffer_position(&gbuf) != owner_len + 10 + rdata_sz ||
|
|
!(bindata = GETDNS_MALLOC(*mf, struct getdns_bindata))) {
|
|
GETDNS_FREE(*mf, data);
|
|
return rr_dict;
|
|
}
|
|
bindata->size = rdata_sz;
|
|
bindata->data = memmove(data, data + owner_len + 10, rdata_sz);
|
|
(void) _getdns_dict_set_this_bindata(rr_dict,
|
|
"/rdata/rdata_raw", bindata);
|
|
}
|
|
return rr_dict;
|
|
|
|
rdata_error:
|
|
getdns_list_destroy(repeat_list);
|
|
getdns_dict_destroy(repeat_dict);
|
|
getdns_dict_destroy(rdata_dict);
|
|
error:
|
|
getdns_dict_destroy(rr_dict);
|
|
return NULL;
|
|
}
|
|
|
|
getdns_dict *
|
|
_getdns_rr_iter2rr_dict(struct mem_funcs *mf, _getdns_rr_iter *i)
|
|
{
|
|
return _getdns_rr_iter2rr_dict_canonical(mf, i, NULL);
|
|
}
|
|
|
|
|
|
static inline getdns_dict *
|
|
set_dict(getdns_dict **var, getdns_dict *value)
|
|
{
|
|
if (*var)
|
|
getdns_dict_destroy(*var);
|
|
return *var = value;
|
|
}
|
|
|
|
static inline int has_all_numeric_label(const uint8_t *dname)
|
|
{
|
|
size_t i;
|
|
|
|
while (*dname && !(*dname & 0xc0)) {
|
|
for (i = 1; i <= *dname; i++) {
|
|
if (!isdigit(dname[i]))
|
|
break;
|
|
}
|
|
if (i > *dname)
|
|
return 1;
|
|
dname += *dname + 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
typedef struct _srv_rr {
|
|
_getdns_rr_iter i;
|
|
unsigned running_sum;
|
|
} _srv_rr;
|
|
|
|
typedef struct _srvs {
|
|
size_t capacity;
|
|
size_t count;
|
|
_srv_rr *rrs;
|
|
} _srvs;
|
|
|
|
static int _grow_srvs(struct mem_funcs *mf, _srvs *srvs)
|
|
{
|
|
_srv_rr *new_rrs;
|
|
|
|
if (!(new_rrs = GETDNS_XREALLOC(
|
|
*mf, srvs->rrs, _srv_rr, srvs->capacity * 2)))
|
|
return 0; /* Memory error */
|
|
|
|
srvs->capacity *= 2;
|
|
srvs->rrs = new_rrs;
|
|
return 1; /* Success */
|
|
}
|
|
|
|
#define SET_WIRE_INT(X,Y) if (getdns_dict_set_int(header, #X , (int) \
|
|
GLDNS_ ## Y ## _WIRE(req->response))) goto error
|
|
#define SET_WIRE_BIT(X,Y) if (getdns_dict_set_int(header, #X , \
|
|
GLDNS_ ## Y ## _WIRE(req->response) ? 1 : 0)) goto error
|
|
#define SET_WIRE_CNT(X,Y) if (getdns_dict_set_int(header, #X , (int) \
|
|
GLDNS_ ## Y (req->response))) goto error
|
|
|
|
static getdns_dict *
|
|
_getdns_create_reply_dict(getdns_context *context, getdns_network_req *req,
|
|
getdns_list *just_addrs, int *rrsigs_in_answer, _srvs *srvs)
|
|
{
|
|
/* turn a packet into this glorious structure
|
|
*
|
|
* { # This is the first reply
|
|
* "header": { "id": 23456, "qr": 1, "opcode": 0, ... },
|
|
* "question": { "qname": <bindata for "www.example.com">, "qtype": 1, "qclass": 1 },
|
|
* "answer":
|
|
* [
|
|
* {
|
|
* "name": <bindata for "www.example.com">,
|
|
* "type": 1,
|
|
* "class": 1,
|
|
* "ttl": 33000,
|
|
* "rdata":
|
|
* {
|
|
* "ipv4_address": <bindata of 0x0a0b0c01>
|
|
* "rdata_raw": <bindata of 0x0a0b0c01>
|
|
* }
|
|
* }
|
|
* ],
|
|
* "authority":
|
|
* [
|
|
* {
|
|
* "name": <bindata for "ns1.example.com">,
|
|
* "type": 1,
|
|
* "class": 1,
|
|
* "ttl": 600,
|
|
* "rdata":
|
|
* {
|
|
* "ipv4_address": <bindata of 0x65439876>
|
|
* "rdata_raw": <bindata of 0x65439876>
|
|
* }
|
|
* }
|
|
* ]
|
|
* "additional": [],
|
|
* "canonical_name": <bindata for "www.example.com">,
|
|
* "answer_type": GETDNS_NAMETYPE_DNS
|
|
* }
|
|
*
|
|
*/
|
|
getdns_return_t r = GETDNS_RETURN_GOOD;
|
|
getdns_dict *result = getdns_dict_create_with_context(context);
|
|
getdns_dict *question = NULL;
|
|
getdns_list *sections[16] = { NULL, NULL
|
|
, getdns_list_create_with_context(context)
|
|
, NULL
|
|
, getdns_list_create_with_context(context)
|
|
, NULL, NULL, NULL
|
|
, getdns_list_create_with_context(context)
|
|
, NULL, NULL, NULL, NULL, NULL, NULL, NULL
|
|
};
|
|
getdns_dict *rr_dict = NULL;
|
|
_getdns_rr_iter rr_iter_storage, *rr_iter;
|
|
_getdns_rdf_iter rdf_iter_storage, *rdf_iter;
|
|
size_t bin_size;
|
|
const uint8_t *bin_data;
|
|
_getdns_section section;
|
|
uint8_t owner_name_space[256], query_name_space[256];
|
|
const uint8_t *owner_name, *query_name;
|
|
size_t owner_name_len = sizeof(owner_name_space),
|
|
query_name_len = sizeof(query_name_space);
|
|
int all_numeric_label;
|
|
uint16_t rr_type;
|
|
getdns_dict *header = NULL;
|
|
getdns_list *bad_dns = NULL;
|
|
_getdns_rrset_spc answer_spc;
|
|
_getdns_rrset *answer;
|
|
|
|
if (!result)
|
|
goto error;
|
|
|
|
if (!(header = getdns_dict_create_with_context(context)))
|
|
goto error;
|
|
|
|
SET_WIRE_INT(id, ID);
|
|
SET_WIRE_BIT(qr, QR);
|
|
SET_WIRE_BIT(aa, AA);
|
|
SET_WIRE_BIT(tc, TC);
|
|
SET_WIRE_BIT(rd, RD);
|
|
SET_WIRE_BIT(cd, CD);
|
|
SET_WIRE_BIT(ra, RA);
|
|
SET_WIRE_BIT(ad, AD);
|
|
SET_WIRE_INT(opcode, OPCODE);
|
|
SET_WIRE_INT(rcode, RCODE);
|
|
SET_WIRE_BIT(z, Z);
|
|
|
|
SET_WIRE_CNT(qdcount, QDCOUNT);
|
|
SET_WIRE_CNT(ancount, ANCOUNT);
|
|
SET_WIRE_CNT(nscount, NSCOUNT);
|
|
SET_WIRE_CNT(arcount, ARCOUNT);
|
|
|
|
/* header */
|
|
if ((r = _getdns_dict_set_this_dict(result, "header", header)))
|
|
goto error;
|
|
header = NULL;
|
|
|
|
if (req->query &&
|
|
(rr_iter = _getdns_rr_iter_init(&rr_iter_storage, req->query
|
|
, req->response - req->query)))
|
|
|
|
query_name = _getdns_owner_if_or_as_decompressed(
|
|
rr_iter, query_name_space, &query_name_len);
|
|
else
|
|
query_name = NULL;
|
|
|
|
for ( rr_iter = _getdns_rr_iter_init(&rr_iter_storage
|
|
, req->response
|
|
, req->response_len)
|
|
; rr_iter
|
|
; rr_iter = _getdns_rr_iter_next(rr_iter)) {
|
|
|
|
if (!set_dict(&rr_dict,
|
|
_getdns_rr_iter2rr_dict(&context->mf, rr_iter)))
|
|
continue;
|
|
|
|
section = _getdns_rr_iter_section(rr_iter);
|
|
if (section == SECTION_QUESTION) {
|
|
if (!query_name)
|
|
query_name
|
|
= _getdns_owner_if_or_as_decompressed(
|
|
rr_iter, query_name_space, &query_name_len);
|
|
|
|
if (_getdns_dict_set_this_dict(result, "question", rr_dict))
|
|
goto error;
|
|
else rr_dict = NULL;
|
|
continue;
|
|
}
|
|
rr_type = gldns_read_uint16(rr_iter->rr_type);
|
|
if (section > SECTION_QUESTION &&
|
|
rr_type == GETDNS_RRTYPE_RRSIG && rrsigs_in_answer)
|
|
*rrsigs_in_answer = 1;
|
|
|
|
if (section != SECTION_ANSWER) {
|
|
if (_getdns_list_append_this_dict(
|
|
sections[section], rr_dict))
|
|
goto error;
|
|
else rr_dict = NULL;
|
|
continue;
|
|
}
|
|
if (req->follow_redirects == GETDNS_REDIRECTS_DO_NOT_FOLLOW) {
|
|
owner_name_len = sizeof(owner_name_space);
|
|
owner_name = _getdns_owner_if_or_as_decompressed(
|
|
rr_iter, owner_name_space, &owner_name_len);
|
|
|
|
if (!_getdns_dname_equal(query_name, owner_name))
|
|
continue;
|
|
}
|
|
if (_getdns_list_append_this_dict(
|
|
sections[SECTION_ANSWER], rr_dict))
|
|
goto error;
|
|
else rr_dict = NULL;
|
|
|
|
if (srvs->capacity && rr_type == GETDNS_RRTYPE_SRV) {
|
|
if (srvs->count >= srvs->capacity &&
|
|
!_grow_srvs(&context->mf, srvs))
|
|
goto error;
|
|
|
|
srvs->rrs[srvs->count++].i = *rr_iter;
|
|
continue;
|
|
}
|
|
if (rr_type != GETDNS_RRTYPE_A && rr_type != GETDNS_RRTYPE_AAAA)
|
|
continue;
|
|
|
|
if (!just_addrs)
|
|
continue;
|
|
|
|
if (!(rdf_iter = _getdns_rdf_iter_init(
|
|
&rdf_iter_storage, rr_iter)))
|
|
continue;
|
|
|
|
bin_size = rdf_iter->nxt - rdf_iter->pos;
|
|
bin_data = rdf_iter->pos;
|
|
if (!set_dict(&rr_dict, getdns_dict_create_with_context(context)) ||
|
|
|
|
getdns_dict_util_set_string(rr_dict, "address_type",
|
|
rr_type == GETDNS_RRTYPE_A ? "IPv4" : "IPv6" ) ||
|
|
|
|
_getdns_dict_set_const_bindata(
|
|
rr_dict, "address_data", bin_size, bin_data) ||
|
|
|
|
_getdns_list_append_this_dict(just_addrs, rr_dict)) {
|
|
|
|
goto error;
|
|
}
|
|
rr_dict = NULL;
|
|
}
|
|
if (!_getdns_dict_set_this_list(result, "answer",
|
|
sections[SECTION_ANSWER]))
|
|
sections[SECTION_ANSWER] = NULL;
|
|
else goto error;
|
|
|
|
if (!_getdns_dict_set_this_list(result, "authority",
|
|
sections[SECTION_AUTHORITY]))
|
|
sections[SECTION_AUTHORITY] = NULL;
|
|
else goto error;
|
|
|
|
if (!_getdns_dict_set_this_list(result, "additional",
|
|
sections[SECTION_ADDITIONAL]))
|
|
sections[SECTION_ADDITIONAL] = NULL;
|
|
else goto error;
|
|
|
|
/* other stuff
|
|
* Note that spec doesn't explicitely mention these.
|
|
* They are only showcased in the response dict example */
|
|
if (getdns_dict_set_int(result, "answer_type", GETDNS_NAMETYPE_DNS))
|
|
goto error;
|
|
|
|
answer = _getdns_rrset_answer(&answer_spc, req->response
|
|
, req->response_len);
|
|
|
|
if (answer_spc.rrset.name &&
|
|
_getdns_dict_set_const_bindata(result, "canonical_name"
|
|
, answer_spc.name_len
|
|
, answer_spc.rrset.name))
|
|
goto error;
|
|
|
|
if (!req->owner->add_warning_for_bad_dns)
|
|
goto success;
|
|
|
|
if (!(bad_dns = getdns_list_create_with_context(context)))
|
|
goto error;
|
|
|
|
if ( !answer
|
|
&& req->request_type != GETDNS_RRTYPE_CNAME
|
|
&& query_name
|
|
&& answer_spc.rrset.name
|
|
&& !_getdns_dname_equal(query_name, answer_spc.rrset.name)
|
|
&& _getdns_list_append_int(bad_dns
|
|
, GETDNS_BAD_DNS_CNAME_RETURNED_FOR_OTHER_TYPE))
|
|
goto error;
|
|
|
|
all_numeric_label = 0;
|
|
for ( rr_iter = _getdns_rr_iter_init(&rr_iter_storage
|
|
, req->response
|
|
, req->response_len)
|
|
; rr_iter && !all_numeric_label
|
|
; rr_iter = _getdns_rr_iter_next(rr_iter)) {
|
|
|
|
owner_name = _getdns_owner_if_or_as_decompressed(
|
|
rr_iter, owner_name_space, &owner_name_len);
|
|
|
|
if (has_all_numeric_label(owner_name)) {
|
|
all_numeric_label = 1;
|
|
break;
|
|
}
|
|
if (_getdns_rr_iter_section(rr_iter) ==
|
|
SECTION_QUESTION)
|
|
continue;
|
|
|
|
for ( rdf_iter = _getdns_rdf_iter_init(&rdf_iter_storage, rr_iter)
|
|
; rdf_iter; rdf_iter = _getdns_rdf_iter_next(rdf_iter)) {
|
|
|
|
if (!(rdf_iter->rdd_pos->type & GETDNS_RDF_DNAME))
|
|
continue;
|
|
|
|
owner_name = _getdns_rdf_if_or_as_decompressed(
|
|
rdf_iter, owner_name_space, &owner_name_len);
|
|
|
|
if (has_all_numeric_label(owner_name)) {
|
|
all_numeric_label = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (all_numeric_label &&
|
|
_getdns_list_append_int(bad_dns, GETDNS_BAD_DNS_ALL_NUMERIC_LABEL))
|
|
goto error;
|
|
|
|
if (_getdns_dict_set_this_list(result, "bad_dns", bad_dns))
|
|
goto error;
|
|
else bad_dns = NULL;
|
|
|
|
goto success;
|
|
error:
|
|
getdns_dict_destroy(result);
|
|
result = NULL;
|
|
success:
|
|
getdns_dict_destroy(header);
|
|
getdns_dict_destroy(rr_dict);
|
|
getdns_list_destroy(sections[SECTION_ADDITIONAL]);
|
|
getdns_list_destroy(sections[SECTION_AUTHORITY]);
|
|
getdns_list_destroy(sections[SECTION_ANSWER]);
|
|
getdns_dict_destroy(question);
|
|
getdns_list_destroy(bad_dns);
|
|
return result;
|
|
}
|
|
|
|
static getdns_dict *
|
|
_getdns_create_call_reporting_dict(
|
|
getdns_context *context, getdns_network_req *netreq)
|
|
{
|
|
getdns_bindata qname;
|
|
getdns_dict *netreq_debug;
|
|
getdns_dict *address_debug = NULL;
|
|
|
|
assert(netreq);
|
|
|
|
/* It is the responsibility of the caller to free this */
|
|
if (!(netreq_debug = getdns_dict_create_with_context(context)))
|
|
return NULL;
|
|
|
|
qname.data = netreq->owner->name;
|
|
qname.size = netreq->owner->name_len;
|
|
|
|
if (getdns_dict_set_bindata(netreq_debug, "query_name", &qname) ||
|
|
getdns_dict_set_int( netreq_debug, "query_type"
|
|
, netreq->request_type ) ||
|
|
|
|
/* Safe, because uint32_t facilitates RRT's of almost 50 days*/
|
|
getdns_dict_set_int(netreq_debug, "run_time/ms",
|
|
(uint32_t)(( netreq->debug_end_time
|
|
- netreq->debug_start_time)/1000))) {
|
|
|
|
getdns_dict_destroy(netreq_debug);
|
|
return NULL;
|
|
|
|
} else if (!netreq->upstream) {
|
|
if (getdns_dict_set_int( netreq_debug, "resolution_type", GETDNS_RESOLUTION_RECURSING)) {
|
|
getdns_dict_destroy(netreq_debug);
|
|
return NULL;
|
|
}
|
|
/* Nothing more for full recursion */
|
|
return netreq_debug;
|
|
}
|
|
|
|
if (getdns_dict_set_int( netreq_debug, "resolution_type", GETDNS_RESOLUTION_STUB)) {
|
|
getdns_dict_destroy(netreq_debug);
|
|
return NULL;
|
|
}
|
|
/* Stub resolver debug data */
|
|
_getdns_sockaddr_to_dict(
|
|
context, &netreq->upstream->addr, &address_debug);
|
|
|
|
if (_getdns_dict_set_this_dict(netreq_debug,"query_to",address_debug)){
|
|
getdns_dict_destroy(address_debug);
|
|
return NULL;
|
|
}
|
|
getdns_transport_list_t transport = netreq->upstream->transport;
|
|
/* Same upstream is used for UDP and TCP, so netreq keeps track of what
|
|
was actually used for the last successful query.*/
|
|
if (transport == GETDNS_TRANSPORT_TCP && netreq->debug_udp == 1) {
|
|
transport = GETDNS_TRANSPORT_UDP;
|
|
if (getdns_dict_set_int( netreq_debug, "udp_responses_for_this_upstream",
|
|
netreq->upstream->udp_responses)) {
|
|
getdns_dict_destroy(netreq_debug);
|
|
return NULL;
|
|
}
|
|
if (getdns_dict_set_int( netreq_debug, "udp_timeouts_for_this_upstream",
|
|
netreq->upstream->udp_timeouts)) {
|
|
getdns_dict_destroy(netreq_debug);
|
|
return NULL;
|
|
}
|
|
}
|
|
if (getdns_dict_set_int( netreq_debug, "transport", transport)) {
|
|
getdns_dict_destroy(netreq_debug);
|
|
return NULL;
|
|
}
|
|
if (transport != GETDNS_TRANSPORT_UDP) {
|
|
/* Report the idle timeout actually used on the connection. Must trim,
|
|
maximum used in practice is 6553500ms, but this is stored in a uint64_t.*/
|
|
if (netreq->upstream->keepalive_timeout > UINT32_MAX) {
|
|
if (getdns_dict_set_int( netreq_debug, "idle timeout in ms (overflow)", UINT32_MAX)) {
|
|
getdns_dict_destroy(netreq_debug);
|
|
return NULL;
|
|
}
|
|
} else{
|
|
uint32_t idle_timeout = netreq->upstream->keepalive_timeout;
|
|
if (getdns_dict_set_int( netreq_debug, "idle timeout in ms", idle_timeout)) {
|
|
getdns_dict_destroy(netreq_debug);
|
|
return NULL;
|
|
}
|
|
}
|
|
/* The running totals are only updated when a connection is closed.
|
|
Since it is open as we have just used it, calcualte the value on the fly */
|
|
if (getdns_dict_set_int( netreq_debug, "responses_on_this_connection",
|
|
netreq->upstream->responses_received)) {
|
|
getdns_dict_destroy(netreq_debug);
|
|
return NULL;
|
|
}
|
|
if (getdns_dict_set_int( netreq_debug, "timeouts_on_this_connection",
|
|
netreq->upstream->responses_timeouts)) {
|
|
getdns_dict_destroy(netreq_debug);
|
|
return NULL;
|
|
}
|
|
if (getdns_dict_set_int( netreq_debug, "responses_for_this_upstream",
|
|
netreq->upstream->responses_received +
|
|
netreq->upstream->total_responses)) {
|
|
getdns_dict_destroy(netreq_debug);
|
|
return NULL;
|
|
}
|
|
if (getdns_dict_set_int( netreq_debug, "timeouts_for_this_upstream",
|
|
netreq->upstream->responses_timeouts +
|
|
netreq->upstream->total_timeouts)) {
|
|
getdns_dict_destroy(netreq_debug);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (netreq->upstream->transport != GETDNS_TRANSPORT_TLS)
|
|
return netreq_debug;
|
|
|
|
/* Only include the auth status if TLS was used */
|
|
if (getdns_dict_util_set_string(netreq_debug, "tls_auth_status",
|
|
_getdns_auth_str(netreq->debug_tls_auth_status))){
|
|
|
|
getdns_dict_destroy(netreq_debug);
|
|
return NULL;
|
|
}
|
|
return netreq_debug;
|
|
}
|
|
|
|
static inline int _srv_prio(_getdns_rr_iter *x)
|
|
{ return x->nxt < x->rr_type+12
|
|
? 65536 : (int)gldns_read_uint16(x->rr_type+10); }
|
|
|
|
static inline int _srv_weight(_getdns_rr_iter *x)
|
|
{ return x->nxt < x->rr_type+14 ? 0 : (int)gldns_read_uint16(x->rr_type+12); }
|
|
|
|
|
|
static int _srv_cmp(const void *a, const void *b)
|
|
{
|
|
return _srv_prio((_getdns_rr_iter *)a)
|
|
- _srv_prio((_getdns_rr_iter *)b);
|
|
}
|
|
|
|
static void _rfc2782_sort(_srv_rr *start, _srv_rr *end)
|
|
{
|
|
uint32_t running_sum, n;
|
|
_srv_rr *i, *j, swap;
|
|
|
|
/* First move all SRVs with weight 0 to the beginning of the list */
|
|
|
|
for (i = start; i < end && _srv_weight(&i->i) == 0; i++)
|
|
; /* pass */
|
|
|
|
for (j = i + 1; j < end; j++) {
|
|
if (_srv_weight(&j->i) == 0) {
|
|
swap = *i;
|
|
*i = *j;
|
|
*j = swap;
|
|
i++;
|
|
}
|
|
}
|
|
/* Now all SRVs are at the beginning, calculate running_sum */
|
|
running_sum = 0;
|
|
for (i = start; i < end; i++) {
|
|
running_sum += _srv_weight(&i->i);
|
|
i->running_sum = running_sum;
|
|
}
|
|
n = arc4random_uniform(running_sum);
|
|
for (i = start; i < end; i++) {
|
|
if (i->running_sum >= n)
|
|
break;
|
|
}
|
|
if (i > start && i < end) {
|
|
swap = *start;
|
|
*start = *i;
|
|
*i = swap;
|
|
_rfc2782_sort(start + 1, end);
|
|
}
|
|
}
|
|
|
|
static getdns_list *
|
|
_create_srv_addrs(getdns_context *context, _srvs *srvs)
|
|
{
|
|
getdns_list *srv_addrs;
|
|
size_t i, j;
|
|
|
|
qsort(srvs->rrs, srvs->count, sizeof(_srv_rr), _srv_cmp);
|
|
/* The SRVs are now sorted by priority (lowest first). Now determine
|
|
* server selection order for the SRVs with the same priority with
|
|
* the algorithm described in RFC2782:
|
|
*
|
|
* To select a target to be contacted next, arrange all SRV RRs
|
|
* (that have not been ordered yet) in any order, except that all
|
|
* those with weight 0 are placed at the beginning of the list.
|
|
*
|
|
* Compute the sum of the weights of those RRs, and with each RR
|
|
* associate the running sum in the selected order. Then choose a
|
|
* uniform random number between 0 and the sum computed
|
|
* (inclusive), and select the RR whose running sum value is the
|
|
* first in the selected order which is greater than or equal to
|
|
* the random number selected. The target host specified in the
|
|
* selected SRV RR is the next one to be contacted by the client.
|
|
* Remove this SRV RR from the set of the unordered SRV RRs and
|
|
* apply the described algorithm to the unordered SRV RRs to select
|
|
* the next target host. Continue the ordering process until there
|
|
* are no unordered SRV RRs. This process is repeated for each
|
|
* Priority.
|
|
*/
|
|
for (i = 0; i < srvs->count; i = j) {
|
|
/* Determine range of SRVs with same priority */
|
|
for ( j = i + 1
|
|
; j < srvs->count && _srv_prio(&srvs->rrs[i].i)
|
|
== _srv_prio(&srvs->rrs[j].i)
|
|
; j++)
|
|
; /* pass */
|
|
|
|
if (j == i + 1)
|
|
continue;
|
|
|
|
/* SRVs with same prio range from i till j (exclusive). */
|
|
_rfc2782_sort(srvs->rrs + i, srvs->rrs + j);
|
|
}
|
|
if (!(srv_addrs = getdns_list_create_with_context(context)))
|
|
return NULL;
|
|
|
|
for (i = 0; i < srvs->count; i++) {
|
|
_getdns_rr_iter *rr = &srvs->rrs[i].i;
|
|
getdns_dict *d;
|
|
_getdns_rdf_iter rdf_storage, *rdf;
|
|
|
|
const uint8_t *dname;
|
|
uint8_t dname_spc[256];
|
|
size_t dname_sz = sizeof(dname_spc);
|
|
|
|
_getdns_rrset a;
|
|
_getdns_rrtype_iter a_rr_spc, *a_rr;
|
|
int addresses_found = 0;
|
|
|
|
if ( !(d = getdns_dict_create_with_context(context))
|
|
|| !(rdf = _getdns_rdf_iter_init_at(&rdf_storage, rr, 2))
|
|
|| !(rdf->rdd_pos->type & GETDNS_RDF_INTEGER)
|
|
|| (rdf->rdd_pos->type & GETDNS_RDF_FIXEDSZ) != 2
|
|
|| getdns_dict_set_int(d, "port", (uint32_t)
|
|
gldns_read_uint16(rdf->pos))
|
|
|
|
|| !(rdf = _getdns_rdf_iter_init_at(&rdf_storage, rr, 3))
|
|
|| !(rdf->rdd_pos->type & GETDNS_RDF_DNAME)
|
|
|| !(dname = _getdns_rdf_if_or_as_decompressed(
|
|
rdf, dname_spc, &dname_sz))
|
|
|| _getdns_dict_set_const_bindata(
|
|
d, "domain_name", dname_sz, dname)) {
|
|
|
|
/* error */
|
|
getdns_dict_destroy(d);
|
|
continue;
|
|
}
|
|
|
|
a.name = dname;
|
|
a.rr_class = rr_iter_class(rr);
|
|
a.rr_type = GETDNS_RRTYPE_AAAA;
|
|
a.pkt = rr->pkt;
|
|
a.pkt_len = rr->pkt_end - rr->pkt;
|
|
a.sections = SECTION_ADDITIONAL;
|
|
|
|
for ( a_rr = _getdns_rrtype_iter_init(&a_rr_spc, &a)
|
|
; a_rr ; a_rr = _getdns_rrtype_iter_next(a_rr)) {
|
|
|
|
if ( a_rr->rr_i.nxt - (a_rr->rr_i.rr_type + 10) == 16
|
|
&& !getdns_dict_util_set_string(
|
|
d, "address_type", "IPv6")
|
|
&& !_getdns_dict_set_const_bindata(
|
|
d, "address_data", 16,
|
|
a_rr->rr_i.rr_type + 10)
|
|
&& !_getdns_list_append_dict(srv_addrs, d))
|
|
addresses_found++;
|
|
}
|
|
a.rr_type = GETDNS_RRTYPE_A;
|
|
|
|
for ( a_rr = _getdns_rrtype_iter_init(&a_rr_spc, &a)
|
|
; a_rr ; a_rr = _getdns_rrtype_iter_next(a_rr)) {
|
|
|
|
if ( a_rr->rr_i.nxt - (a_rr->rr_i.rr_type + 10) ==4
|
|
&& !getdns_dict_util_set_string(
|
|
d, "address_type", "IPv4")
|
|
&& !_getdns_dict_set_const_bindata(
|
|
d, "address_data", 4,
|
|
a_rr->rr_i.rr_type + 10)
|
|
&& !_getdns_list_append_dict(srv_addrs, d))
|
|
addresses_found++;
|
|
}
|
|
if ( addresses_found
|
|
|| _getdns_list_append_this_dict(srv_addrs, d))
|
|
getdns_dict_destroy(d);
|
|
}
|
|
return srv_addrs;
|
|
}
|
|
|
|
getdns_dict *
|
|
_getdns_create_getdns_response(getdns_dns_req *completed_request)
|
|
{
|
|
getdns_dict *result;
|
|
getdns_list *just_addrs = NULL;
|
|
getdns_list *srv_addrs = NULL;
|
|
getdns_list *replies_full;
|
|
getdns_list *replies_tree;
|
|
getdns_list *call_reporting = NULL;
|
|
getdns_network_req *netreq, **netreq_p;
|
|
int rrsigs_in_answer = 0;
|
|
getdns_dict *reply;
|
|
getdns_bindata *canonical_name = NULL;
|
|
int nreplies = 0, nanswers = 0, nsecure = 0, ninsecure = 0, nbogus = 0;
|
|
getdns_dict *netreq_debug;
|
|
_srvs srvs = { 0, 0, NULL };
|
|
|
|
/* info (bools) about dns_req */
|
|
int dnssec_return_status;
|
|
getdns_context *context;
|
|
|
|
assert(completed_request);
|
|
|
|
context = completed_request->context;
|
|
if (!(result = getdns_dict_create_with_context(context)))
|
|
return NULL;
|
|
|
|
dnssec_return_status = completed_request->dnssec_return_status ||
|
|
completed_request->dnssec_return_only_secure ||
|
|
completed_request->dnssec_return_all_statuses
|
|
#ifdef DNSSEC_ROADBLOCK_AVOIDANCE
|
|
|| completed_request->dnssec_roadblock_avoidance
|
|
#endif
|
|
;
|
|
|
|
if (completed_request->netreqs[0]->request_type == GETDNS_RRTYPE_A ||
|
|
completed_request->netreqs[0]->request_type == GETDNS_RRTYPE_AAAA)
|
|
just_addrs = getdns_list_create_with_context(
|
|
completed_request->context);
|
|
|
|
else if (
|
|
completed_request->netreqs[0]->request_type == GETDNS_RRTYPE_SRV) {
|
|
|
|
srvs.capacity = 100;
|
|
if (!(srvs.rrs = GETDNS_XMALLOC(
|
|
context->mf, _srv_rr, srvs.capacity))) {
|
|
srvs.capacity = 0;
|
|
goto error_free_result;
|
|
}
|
|
}
|
|
if (getdns_dict_set_int(result, GETDNS_STR_KEY_ANSWER_TYPE,
|
|
GETDNS_NAMETYPE_DNS))
|
|
goto error_free_result;
|
|
|
|
if (!(replies_full = getdns_list_create_with_context(context)))
|
|
goto error_free_result;
|
|
|
|
if (!(replies_tree = getdns_list_create_with_context(context)))
|
|
goto error_free_replies_full;
|
|
|
|
if (completed_request->return_call_reporting &&
|
|
!(call_reporting = getdns_list_create_with_context(context)))
|
|
goto error_free_replies_full;
|
|
|
|
for ( netreq_p = completed_request->netreqs
|
|
; (netreq = *netreq_p) ; netreq_p++) {
|
|
|
|
if (call_reporting && ( netreq->response_len
|
|
|| netreq->state == NET_REQ_TIMED_OUT)) {
|
|
if (!(netreq_debug =
|
|
_getdns_create_call_reporting_dict(context,netreq)))
|
|
goto error;
|
|
|
|
if (_getdns_list_append_this_dict(
|
|
call_reporting, netreq_debug))
|
|
goto error;
|
|
|
|
netreq_debug = NULL;
|
|
}
|
|
if (! netreq->response_len)
|
|
continue;
|
|
|
|
if (netreq->tsig_status == GETDNS_DNSSEC_INSECURE)
|
|
_getdns_network_validate_tsig(netreq);
|
|
|
|
nreplies++;
|
|
if (netreq->dnssec_status == GETDNS_DNSSEC_SECURE)
|
|
nsecure++;
|
|
else if (netreq->dnssec_status != GETDNS_DNSSEC_BOGUS)
|
|
ninsecure++;
|
|
|
|
if (dnssec_return_status &&
|
|
netreq->dnssec_status == GETDNS_DNSSEC_BOGUS)
|
|
nbogus++;
|
|
|
|
|
|
if (! completed_request->dnssec_return_all_statuses &&
|
|
! completed_request->dnssec_return_validation_chain) {
|
|
if (dnssec_return_status &&
|
|
netreq->dnssec_status == GETDNS_DNSSEC_BOGUS)
|
|
continue;
|
|
else if (completed_request->dnssec_return_only_secure
|
|
&& netreq->dnssec_status != GETDNS_DNSSEC_SECURE)
|
|
continue;
|
|
else if (netreq->tsig_status == GETDNS_DNSSEC_BOGUS)
|
|
continue;
|
|
}
|
|
if (!(reply = _getdns_create_reply_dict(context,
|
|
netreq, just_addrs, &rrsigs_in_answer, &srvs)))
|
|
goto error;
|
|
|
|
if (!canonical_name) {
|
|
if (!getdns_dict_get_bindata(
|
|
reply, "canonical_name", &canonical_name) &&
|
|
getdns_dict_set_bindata(
|
|
result, "canonical_name", canonical_name))
|
|
goto error;
|
|
}
|
|
/* TODO: Check instead if canonical_name for request_type
|
|
* is in the answer section.
|
|
*/
|
|
if (GLDNS_RCODE_NOERROR ==
|
|
GLDNS_RCODE_WIRE(netreq->response))
|
|
nanswers++;
|
|
|
|
if (dnssec_return_status ||
|
|
completed_request->dnssec_return_validation_chain) {
|
|
|
|
if (getdns_dict_set_int(reply, "dnssec_status",
|
|
netreq->dnssec_status))
|
|
goto error;
|
|
}
|
|
if (netreq->tsig_status != GETDNS_DNSSEC_INDETERMINATE) {
|
|
if (getdns_dict_set_int(reply, "tsig_status",
|
|
netreq->tsig_status))
|
|
goto error;
|
|
}
|
|
if (_getdns_list_append_this_dict(replies_tree, reply)) {
|
|
getdns_dict_destroy(reply);
|
|
goto error;
|
|
}
|
|
if (_getdns_list_append_const_bindata(replies_full,
|
|
netreq->response_len, netreq->response))
|
|
goto error;
|
|
}
|
|
if (_getdns_dict_set_this_list(result, "replies_tree", replies_tree))
|
|
goto error;
|
|
replies_tree = NULL;
|
|
|
|
if (!canonical_name &&
|
|
_getdns_dict_set_const_bindata(result, "canonical_name",
|
|
completed_request->name_len, completed_request->name))
|
|
goto error;
|
|
|
|
if (call_reporting) {
|
|
if (_getdns_dict_set_this_list(
|
|
result, "call_reporting", call_reporting))
|
|
goto error_free_call_reporting;
|
|
call_reporting = NULL;
|
|
}
|
|
if (_getdns_dict_set_this_list(result, "replies_full", replies_full))
|
|
goto error_free_replies_full;
|
|
replies_full = NULL;
|
|
|
|
if (just_addrs) {
|
|
if (_getdns_dict_set_this_list(
|
|
result, GETDNS_STR_KEY_JUST_ADDRS, just_addrs))
|
|
goto error_free_result;
|
|
just_addrs = NULL;
|
|
}
|
|
if (srvs.capacity) {
|
|
if (!(srv_addrs = _create_srv_addrs(context, &srvs)) ||
|
|
_getdns_dict_set_this_list(
|
|
result, "srv_addresses", srv_addrs))
|
|
goto error_free_result;
|
|
|
|
GETDNS_FREE(context->mf, srvs.rrs);
|
|
}
|
|
if (getdns_dict_set_int(result, GETDNS_STR_KEY_STATUS,
|
|
nreplies == 0 ? GETDNS_RESPSTATUS_ALL_TIMEOUT :
|
|
completed_request->dnssec_return_only_secure && nsecure == 0 && ninsecure > 0
|
|
? GETDNS_RESPSTATUS_NO_SECURE_ANSWERS :
|
|
completed_request->dnssec_return_only_secure && nsecure == 0 && nbogus > 0
|
|
? GETDNS_RESPSTATUS_ALL_BOGUS_ANSWERS :
|
|
nanswers == 0 ? GETDNS_RESPSTATUS_NO_NAME
|
|
: GETDNS_RESPSTATUS_GOOD))
|
|
goto error_free_result;
|
|
|
|
return result;
|
|
error:
|
|
/* cleanup */
|
|
getdns_list_destroy(replies_tree);
|
|
error_free_call_reporting:
|
|
getdns_list_destroy(call_reporting);
|
|
error_free_replies_full:
|
|
getdns_list_destroy(replies_full);
|
|
error_free_result:
|
|
if (srvs.capacity)
|
|
GETDNS_FREE(context->mf, srvs.rrs);
|
|
getdns_list_destroy(srv_addrs);
|
|
getdns_list_destroy(just_addrs);
|
|
getdns_dict_destroy(result);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
#ifdef HAVE_LIBUNBOUND
|
|
getdns_return_t
|
|
getdns_apply_network_result(getdns_network_req* netreq,
|
|
int rcode, void *pkt, int pkt_len, int sec, char* why_bogus)
|
|
{
|
|
(void)why_bogus;
|
|
|
|
netreq->dnssec_status = sec == 0 ? GETDNS_DNSSEC_INSECURE
|
|
: sec == 2 ? GETDNS_DNSSEC_SECURE
|
|
: GETDNS_DNSSEC_BOGUS;
|
|
|
|
if (pkt) {
|
|
if (netreq->max_udp_payload_size < pkt_len)
|
|
netreq->response = GETDNS_XMALLOC(
|
|
netreq->owner->context->mf,
|
|
uint8_t, pkt_len
|
|
);
|
|
(void) memcpy(netreq->response, pkt,
|
|
(netreq->response_len = pkt_len));
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
if (rcode == GETDNS_RCODE_SERVFAIL) {
|
|
/* Likely to be caused by timeout from a synchronous
|
|
* lookup. Don't forge a packet.
|
|
*/
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
/* Likely to be because libunbound refused the request
|
|
* so ub_res->answer_packet=NULL, ub_res->answer_len=0
|
|
* So we need to create an answer packet.
|
|
*/
|
|
gldns_write_uint16(netreq->response , 0); /* query_id */
|
|
gldns_write_uint16(netreq->response + 2, 0); /* reset all flags */
|
|
gldns_write_uint16(netreq->response + GLDNS_QDCOUNT_OFF, 1);
|
|
gldns_write_uint16(netreq->response + GLDNS_ANCOUNT_OFF, 0);
|
|
gldns_write_uint16(netreq->response + GLDNS_NSCOUNT_OFF, 0);
|
|
gldns_write_uint16(netreq->response + GLDNS_ARCOUNT_OFF, 0);
|
|
|
|
GLDNS_OPCODE_SET(netreq->response, 3);
|
|
GLDNS_QR_SET(netreq->response);
|
|
GLDNS_RD_SET(netreq->response);
|
|
GLDNS_RA_SET(netreq->response);
|
|
GLDNS_RCODE_SET(netreq->response, rcode);
|
|
|
|
(void) memcpy( netreq->response + GLDNS_HEADER_SIZE
|
|
, netreq->owner->name, netreq->owner->name_len);
|
|
|
|
gldns_write_uint16( netreq->response + GLDNS_HEADER_SIZE
|
|
+ netreq->owner->name_len
|
|
, netreq->request_type);
|
|
gldns_write_uint16( netreq->response + GLDNS_HEADER_SIZE
|
|
+ netreq->owner->name_len + 2
|
|
, netreq->owner->request_class);
|
|
|
|
netreq->response_len = GLDNS_HEADER_SIZE + netreq->owner->name_len + 4;
|
|
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
#endif
|
|
|
|
|
|
getdns_return_t
|
|
_getdns_validate_dname(const char* dname) {
|
|
int len;
|
|
int label_len;
|
|
const char* s;
|
|
if (dname == NULL) {
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
}
|
|
len = strlen(dname);
|
|
if (len > GETDNS_MAX_DNAME_LEN * 4 || len == 0) {
|
|
return GETDNS_RETURN_BAD_DOMAIN_NAME;
|
|
}
|
|
if (len == 1 && dname[0] == '.') {
|
|
/* root is ok */
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
/* By specification [RFC1035] the total length of a DNS label is
|
|
* restricted to 63 octets and must be larger than 0 (except for the
|
|
* final root-label). The total length of a domain name (i.e., label
|
|
* octets and label length octets) is restricted to 255 octets or less.
|
|
* With a fully qualified domain name this includes the last label
|
|
* length octet for the root label. In a normalized representation the
|
|
* number of labels (including the root) plus the number of octets in
|
|
* each label may not be larger than 255.
|
|
*/
|
|
len = 0;
|
|
label_len = 0;
|
|
for (s = dname; *s; ++s) {
|
|
switch (*s) {
|
|
case '.':
|
|
if (label_len > GETDNS_MAX_LABEL_LEN ||
|
|
label_len == 0) {
|
|
return GETDNS_RETURN_BAD_DOMAIN_NAME;
|
|
}
|
|
label_len = 0;
|
|
len += 1;
|
|
break;
|
|
case '\\':
|
|
s += 1;
|
|
if (isdigit(s[0])) {
|
|
/* octet value */
|
|
if (! isdigit(s[1]) && ! isdigit(s[2]))
|
|
return GETDNS_RETURN_BAD_DOMAIN_NAME;
|
|
|
|
if ((s[0] - '0') * 100 +
|
|
(s[1] - '0') * 10 + (s[2] - '0') > 255)
|
|
return GETDNS_RETURN_BAD_DOMAIN_NAME;
|
|
|
|
s += 2;
|
|
}
|
|
/* else literal char (1 octet) */
|
|
label_len++;
|
|
len += 1;
|
|
break;
|
|
default:
|
|
label_len++;
|
|
len += 1;
|
|
break;
|
|
}
|
|
}
|
|
if (len > GETDNS_MAX_DNAME_LEN || label_len > GETDNS_MAX_LABEL_LEN) {
|
|
return GETDNS_RETURN_BAD_DOMAIN_NAME;
|
|
}
|
|
return GETDNS_RETURN_GOOD;
|
|
} /* _getdns_validate_dname */
|
|
|
|
|
|
static void _getdns_reply2wire_buf(gldns_buffer *buf, getdns_dict *reply)
|
|
{
|
|
getdns_dict *rr_dict, *q_dict, *h_dict;
|
|
getdns_list *section;
|
|
size_t i, pkt_start;
|
|
uint16_t ancount, nscount;
|
|
uint32_t qtype, qclass = GETDNS_RRCLASS_IN, rcode = GETDNS_RCODE_NOERROR;
|
|
getdns_bindata *qname;
|
|
|
|
|
|
pkt_start = gldns_buffer_position(buf);
|
|
/* Empty header */
|
|
gldns_buffer_write_u32(buf, 0);
|
|
gldns_buffer_write_u32(buf, 0);
|
|
gldns_buffer_write_u32(buf, 0);
|
|
|
|
if ( !getdns_dict_get_dict(reply, "question", &q_dict)
|
|
&& !getdns_dict_get_int(q_dict, "qtype", &qtype)
|
|
&& !getdns_dict_get_bindata(q_dict, "qname", &qname)) {
|
|
|
|
gldns_buffer_write(buf, qname->data, qname->size);
|
|
gldns_buffer_write_u16(buf, (uint16_t)qtype);
|
|
gldns_buffer_write_u16(buf, (uint16_t)qclass);
|
|
gldns_buffer_write_u16_at(
|
|
buf, pkt_start + GLDNS_QDCOUNT_OFF, 1);
|
|
}
|
|
|
|
if ( !getdns_dict_get_dict(reply, "header", &h_dict)
|
|
&& !getdns_dict_get_int(h_dict, "rcode", &rcode)) {
|
|
|
|
GLDNS_RCODE_SET(gldns_buffer_at(buf, pkt_start), rcode);
|
|
}
|
|
if (!getdns_dict_get_list(reply, "answer", §ion)) {
|
|
for ( i = 0, ancount = 0
|
|
; !getdns_list_get_dict(section, i, &rr_dict)
|
|
; i++ ) {
|
|
|
|
if (!_getdns_rr_dict2wire(rr_dict, buf))
|
|
ancount++;
|
|
}
|
|
gldns_buffer_write_u16_at(
|
|
buf, pkt_start + GLDNS_ANCOUNT_OFF, ancount);
|
|
}
|
|
if (!getdns_dict_get_list(reply, "authority", §ion)) {
|
|
for ( i = 0, nscount = 0
|
|
; !getdns_list_get_dict(section, i, &rr_dict)
|
|
; i++ ) {
|
|
|
|
if (!_getdns_rr_dict2wire(rr_dict, buf))
|
|
nscount++;
|
|
}
|
|
gldns_buffer_write_u16_at(
|
|
buf, pkt_start + GLDNS_NSCOUNT_OFF, nscount);
|
|
}
|
|
}
|
|
|
|
static void _getdns_list2wire_buf(gldns_buffer *buf, getdns_list *l)
|
|
{
|
|
getdns_dict *rr_dict;
|
|
size_t i, pkt_start;
|
|
uint16_t ancount;
|
|
uint32_t qtype, qclass = GETDNS_RRCLASS_IN;
|
|
getdns_bindata *qname;
|
|
|
|
pkt_start = gldns_buffer_position(buf);
|
|
/* Empty header */
|
|
gldns_buffer_write_u32(buf, 0);
|
|
gldns_buffer_write_u32(buf, 0);
|
|
gldns_buffer_write_u32(buf, 0);
|
|
|
|
for ( i = 0
|
|
; !getdns_list_get_dict(l, i, &rr_dict)
|
|
; i++ ) {
|
|
|
|
if (getdns_dict_get_int(rr_dict, "qtype", &qtype) ||
|
|
getdns_dict_get_bindata(rr_dict, "qname", &qname))
|
|
continue;
|
|
(void) getdns_dict_get_int(rr_dict, "qclass", &qclass);
|
|
gldns_buffer_write(buf, qname->data, qname->size);
|
|
gldns_buffer_write_u16(buf, (uint16_t)qtype);
|
|
gldns_buffer_write_u16(buf, (uint16_t)qclass);
|
|
gldns_buffer_write_u16_at(buf, pkt_start+GLDNS_QDCOUNT_OFF, 1);
|
|
break;
|
|
}
|
|
for ( i = 0, ancount = 0
|
|
; !getdns_list_get_dict(l, i, &rr_dict)
|
|
; i++ ) {
|
|
|
|
if (!_getdns_rr_dict2wire(rr_dict, buf))
|
|
ancount++;
|
|
}
|
|
gldns_buffer_write_u16_at(buf, pkt_start+GLDNS_ANCOUNT_OFF, ancount);
|
|
}
|
|
|
|
uint8_t *_getdns_list2wire(
|
|
getdns_list *l, uint8_t *buf, size_t *buf_len, struct mem_funcs *mf)
|
|
{
|
|
gldns_buffer gbuf;
|
|
size_t sz;
|
|
|
|
gldns_buffer_init_frm_data(&gbuf, buf, *buf_len);
|
|
_getdns_list2wire_buf(&gbuf, l);
|
|
|
|
if ((sz = gldns_buffer_position(&gbuf)) <= *buf_len) {
|
|
*buf_len = sz;
|
|
return buf;
|
|
}
|
|
if (!(buf = GETDNS_XMALLOC(*mf, uint8_t, (*buf_len = sz))))
|
|
return NULL;
|
|
|
|
gldns_buffer_init_frm_data(&gbuf, buf, sz);
|
|
_getdns_list2wire_buf(&gbuf, l);
|
|
return buf;
|
|
}
|
|
|
|
uint8_t *_getdns_reply2wire(
|
|
getdns_dict *r, uint8_t *buf, size_t *buf_len, struct mem_funcs *mf)
|
|
{
|
|
gldns_buffer gbuf;
|
|
size_t sz;
|
|
|
|
gldns_buffer_init_frm_data(&gbuf, buf, *buf_len);
|
|
_getdns_reply2wire_buf(&gbuf, r);
|
|
|
|
if ((sz = gldns_buffer_position(&gbuf)) <= *buf_len) {
|
|
*buf_len = sz;
|
|
return buf;
|
|
}
|
|
if (!(buf = GETDNS_XMALLOC(*mf, uint8_t, (*buf_len = sz))))
|
|
return NULL;
|
|
|
|
gldns_buffer_init_frm_data(&gbuf, buf, sz);
|
|
_getdns_reply2wire_buf(&gbuf, r);
|
|
return buf;
|
|
}
|
|
|
|
void _getdns_wire2list(uint8_t *pkt, size_t pkt_len, getdns_list *l)
|
|
{
|
|
_getdns_rr_iter rr_spc, *rr;
|
|
getdns_dict *rr_dict;
|
|
|
|
for ( rr = _getdns_rr_iter_init(&rr_spc, pkt, pkt_len)
|
|
; rr ; rr = _getdns_rr_iter_next(rr)) {
|
|
|
|
if (!(rr_dict = _getdns_rr_iter2rr_dict(&l->mf, rr)))
|
|
continue;
|
|
|
|
if (_getdns_list_append_this_dict(l, rr_dict))
|
|
getdns_dict_destroy(rr_dict);
|
|
}
|
|
}
|
|
|
|
const char * _getdns_auth_str(getdns_auth_state_t auth) {
|
|
static const char*
|
|
getdns_auth_str_array[] = {
|
|
GETDNS_STR_AUTH_NONE,
|
|
GETDNS_STR_AUTH_FAILED,
|
|
GETDNS_STR_AUTH_OK
|
|
};
|
|
return getdns_auth_str_array[auth];
|
|
}
|
|
|
|
/* util-internal.c */
|