Add --cache-rr to enable caching of arbitrary RR types.

This commit is contained in:
Simon Kelley
2023-03-23 17:15:35 +00:00
parent 88fc6c8023
commit 638c7c4d20
9 changed files with 416 additions and 174 deletions

View File

@@ -19,7 +19,7 @@
static struct blockdata *keyblock_free;
static unsigned int blockdata_count, blockdata_hwm, blockdata_alloced;
static void blockdata_expand(int n)
static void add_blocks(int n)
{
struct blockdata *new = whine_malloc(n * sizeof(struct blockdata));
@@ -47,7 +47,7 @@ void blockdata_init(void)
/* Note that daemon->cachesize is enforced to have non-zero size if OPT_DNSSEC_VALID is set */
if (option_bool(OPT_DNSSEC_VALID))
blockdata_expand(daemon->cachesize);
add_blocks(daemon->cachesize);
}
void blockdata_report(void)
@@ -58,50 +58,61 @@ void blockdata_report(void)
blockdata_alloced * sizeof(struct blockdata));
}
static struct blockdata *new_block(void)
{
struct blockdata *block;
if (!keyblock_free)
add_blocks(50);
if (keyblock_free)
{
block = keyblock_free;
keyblock_free = block->next;
blockdata_count++;
if (blockdata_hwm < blockdata_count)
blockdata_hwm = blockdata_count;
block->next = NULL;
return block;
}
return NULL;
}
static struct blockdata *blockdata_alloc_real(int fd, char *data, size_t len)
{
struct blockdata *block, *ret = NULL;
struct blockdata **prev = &ret;
size_t blen;
while (len > 0)
do
{
if (!keyblock_free)
blockdata_expand(50);
if (keyblock_free)
{
block = keyblock_free;
keyblock_free = block->next;
blockdata_count++;
}
else
if (!(block = new_block()))
{
/* failed to alloc, free partial chain */
blockdata_free(ret);
return NULL;
}
if (blockdata_hwm < blockdata_count)
blockdata_hwm = blockdata_count;
if ((blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len) > 0)
{
if (data)
{
memcpy(block->key, data, blen);
data += blen;
}
else if (!read_write(fd, block->key, blen, 1))
{
/* failed read free partial chain */
blockdata_free(ret);
return NULL;
}
}
blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
if (data)
{
memcpy(block->key, data, blen);
data += blen;
}
else if (!read_write(fd, block->key, blen, 1))
{
/* failed read free partial chain */
blockdata_free(ret);
return NULL;
}
len -= blen;
*prev = block;
prev = &block->next;
block->next = NULL;
}
} while (len != 0);
return ret;
}
@@ -111,6 +122,58 @@ struct blockdata *blockdata_alloc(char *data, size_t len)
return blockdata_alloc_real(0, data, len);
}
/* Add data to the end of the block.
newlen is length of new data, NOT total new length.
Use blockdata_alloc(NULL, 0) to make empty block to add to. */
int blockdata_expand(struct blockdata *block, size_t oldlen, char *data, size_t newlen)
{
struct blockdata *b;
/* find size of current final block */
for (b = block; oldlen > KEYBLOCK_LEN && b; b = b->next, oldlen -= KEYBLOCK_LEN);
/* chain to short for length, something is broken */
if (oldlen > KEYBLOCK_LEN)
{
blockdata_free(block);
return 0;
}
while (1)
{
struct blockdata *new;
size_t blocksize = KEYBLOCK_LEN - oldlen;
size_t size = (newlen <= blocksize) ? newlen : blocksize;
if (size != 0)
{
memcpy(&b->key[oldlen], data, size);
data += size;
newlen -= size;
}
/* full blocks from now on. */
oldlen = 0;
if (newlen == 0)
break;
if ((new = new_block()))
{
b->next = new;
b = new;
}
else
{
/* failed to alloc, free partial chain */
blockdata_free(block);
return 0;
}
}
return 1;
}
void blockdata_free(struct blockdata *blocks)
{
struct blockdata *tmp;

View File

@@ -29,6 +29,7 @@ static void make_non_terminals(struct crec *source);
static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class,
time_t now, unsigned long ttl, unsigned int flags);
static void dump_cache_entry(struct crec *cache, time_t now);
static char *querystr(char *desc, unsigned short type);
/* type->string mapping: this is also used by the name-hash function as a mixing table. */
/* taken from https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml */
@@ -133,6 +134,17 @@ static void cache_link(struct crec *crecp);
static void rehash(int size);
static void cache_hash(struct crec *crecp);
unsigned short rrtype(char *in)
{
int i;
for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++)
if (strcasecmp(in, typestr[i].name) == 0)
return typestr[i].type;
return 0;
}
void next_uid(struct crec *crecp)
{
static unsigned int uid = 0;
@@ -265,6 +277,8 @@ static void cache_blockdata_free(struct crec *crecp)
{
if (crecp->flags & F_SRV)
blockdata_free(crecp->addr.srv.target);
else if (crecp->flags & F_RR)
blockdata_free(crecp->addr.rr.rrdata);
#ifdef HAVE_DNSSEC
else if (crecp->flags & F_DNSKEY)
blockdata_free(crecp->addr.key.keydata);
@@ -459,7 +473,8 @@ static struct crec *cache_scan_free(char *name, union all_addr *addr, unsigned s
{
/* Don't delete DNSSEC in favour of a CNAME, they can co-exist */
if ((flags & crecp->flags & (F_IPV4 | F_IPV6 | F_SRV | F_NXDOMAIN)) ||
(((crecp->flags | flags) & F_CNAME) && !(crecp->flags & (F_DNSKEY | F_DS))))
(((crecp->flags | flags) & F_CNAME) && !(crecp->flags & (F_DNSKEY | F_DS))) ||
((crecp->flags & flags & F_RR) && addr->rr.rrtype == crecp->addr.rr.rrtype))
{
if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
return crecp;
@@ -776,7 +791,7 @@ void cache_end_insert(void)
read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->ttd, sizeof(new_chain->ttd), 0);
read_write(daemon->pipe_to_parent, (unsigned char *)&flags, sizeof(flags), 0);
if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV))
if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV | F_RR))
read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0);
if (flags & F_SRV)
{
@@ -784,6 +799,12 @@ void cache_end_insert(void)
if (!(flags & F_NEG))
blockdata_write(new_chain->addr.srv.target, new_chain->addr.srv.targetlen, daemon->pipe_to_parent);
}
if (flags & F_RR)
{
/* A negative RR entry is possible and has no data, obviously. */
if (!(flags & F_NEG))
blockdata_write(new_chain->addr.rr.rrdata, new_chain->addr.rr.datalen, daemon->pipe_to_parent);
}
#ifdef HAVE_DNSSEC
if (flags & F_DNSKEY)
{
@@ -848,16 +869,18 @@ int cache_recv_insert(time_t now, int fd)
ttl = difftime(ttd, now);
if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV))
if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV | F_RR))
{
unsigned short class = C_IN;
if (!read_write(fd, (unsigned char *)&addr, sizeof(addr), 1))
return 0;
if ((flags & F_SRV) && !(flags & F_NEG) && !(addr.srv.target = blockdata_read(fd, addr.srv.targetlen)))
return 0;
if ((flags & F_RR) && !(flags & F_NEG) && !(addr.rr.rrdata = blockdata_read(fd, addr.rr.datalen)))
return 0;
#ifdef HAVE_DNSSEC
if (flags & F_DNSKEY)
{
@@ -1587,7 +1610,7 @@ static void make_non_terminals(struct crec *source)
if (!is_outdated_cname_pointer(crecp) &&
(crecp->flags & F_FORWARD) &&
(crecp->flags & type) &&
!(crecp->flags & (F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_DNSKEY | F_DS)) &&
!(crecp->flags & (F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_DNSKEY | F_DS | F_RR)) &&
hostname_isequal(name, cache_get_name(crecp)))
{
*up = crecp->hash_next;
@@ -1644,7 +1667,7 @@ static void make_non_terminals(struct crec *source)
if (crecp)
{
crecp->flags = (source->flags | F_NAMEP) & ~(F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_DNSKEY | F_DS | F_REVERSE);
crecp->flags = (source->flags | F_NAMEP) & ~(F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_RR | F_DNSKEY | F_DS | F_REVERSE);
if (!(crecp->flags & F_IMMORTAL))
crecp->ttd = source->ttd;
crecp->name.namep = name;
@@ -1792,6 +1815,8 @@ static void dump_cache_entry(struct crec *cache, time_t now)
blockdata_retrieve(cache->addr.srv.target, targetlen, a + len);
a[len + targetlen] = 0;
}
else if (cache->flags & F_RR)
sprintf(a, "%s", querystr(NULL, cache->addr.rr.rrtype));
#ifdef HAVE_DNSSEC
else if (cache->flags & F_DS)
{
@@ -1820,6 +1845,8 @@ static void dump_cache_entry(struct crec *cache, time_t now)
t = "C";
else if (cache->flags & F_SRV)
t = "V";
else if (cache->flags & F_RR)
t = "T";
#ifdef HAVE_DNSSEC
else if (cache->flags & F_DS)
t = "S";
@@ -2078,6 +2105,8 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg,
sprintf(portstring, "#%u", type);
}
}
else if (flags & F_RR)
dest = querystr(NULL, addr->rr.rrtype);
else
dest = arg;
}

View File

@@ -125,17 +125,11 @@ int main (int argc, char **argv)
{
/* Note that both /000 and '.' are allowed within labels. These get
represented in presentation format using NAME_ESCAPE as an escape
character when in DNSSEC mode.
In theory, if all the characters in a name were /000 or
character. In theory, if all the characters in a name were /000 or
'.' or NAME_ESCAPE then all would have to be escaped, so the
presentation format would be twice as long as the spec.
daemon->namebuff was previously allocated by the option-reading
code before we knew if we're in DNSSEC mode, so reallocate here. */
free(daemon->namebuff);
daemon->namebuff = safe_malloc(MAXDNAME * 2);
daemon->keyname = safe_malloc(MAXDNAME * 2);
daemon->workspacename = safe_malloc(MAXDNAME * 2);
presentation format would be twice as long as the spec. */
daemon->keyname = safe_malloc((MAXDNAME * 2) + 1);
daemon->workspacename = safe_malloc((MAXDNAME * 2) + 1);
/* one char flag per possible RR in answer section (may get extended). */
daemon->rr_status_sz = 64;
daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz);

View File

@@ -282,7 +282,8 @@ struct event_desc {
#define OPT_STRIP_MAC 70
#define OPT_NORR 71
#define OPT_NO_IDENT 72
#define OPT_LAST 73
#define OPT_CACHE_RR 73
#define OPT_LAST 74
#define OPTION_BITS (sizeof(unsigned int)*8)
#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
@@ -337,7 +338,7 @@ union all_addr {
/* for arbitrary RR record. */
struct {
struct blockdata *rrdata;
u16 rrtype;
unsigned short rrtype, datalen;
} rr;
};
@@ -663,6 +664,11 @@ struct iname {
struct iname *next;
};
struct rrlist {
unsigned short rr;
struct rrlist *next;
};
/* subnet parameters from command line */
struct mysubnet {
union mysockaddr addr;
@@ -1128,6 +1134,7 @@ extern struct daemon {
struct naptr *naptr;
struct txt_record *txt, *rr;
struct ptr_record *ptr;
struct rrlist *cache_rr, filter_rr;
struct host_record *host_records, *host_records_tail;
struct cname *cnames;
struct auth_zone *auth_zones;
@@ -1309,6 +1316,7 @@ struct server_details {
/* cache.c */
void cache_init(void);
unsigned short rrtype(char *in);
void next_uid(struct crec *crecp);
void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg, unsigned short type);
char *record_source(unsigned int index);
@@ -1342,6 +1350,8 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size,
void blockdata_init(void);
void blockdata_report(void);
struct blockdata *blockdata_alloc(char *data, size_t len);
int blockdata_expand(struct blockdata *block, size_t oldlen,
char *data, size_t newlen);
void *blockdata_retrieve(struct blockdata *block, size_t len, void *data);
struct blockdata *blockdata_read(int fd, size_t len);
void blockdata_write(struct blockdata *block, size_t len, int fd);
@@ -1423,6 +1433,7 @@ void rand_init(void);
unsigned short rand16(void);
u32 rand32(void);
u64 rand64(void);
int rr_on_list(struct rrlist *list, unsigned short rr);
int legal_hostname(char *name);
char *canonicalise(char *in, int *nomem);
unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit);
@@ -1817,13 +1828,16 @@ int do_poll(int timeout);
/* rrfilter.c */
size_t rrfilter(struct dns_header *header, size_t *plen, int mode);
u16 *rrfilter_desc(int type);
short *rrfilter_desc(int type);
int expand_workspace(unsigned char ***wkspc, int *szp, int new);
int to_wire(char *name);
void from_wire(char *name);
/* modes. */
#define RRFILTER_EDNS0 0
#define RRFILTER_DNSSEC 1
#define RRFILTER_A 2
#define RRFILTER_AAAA 3
/* edns0.c */
unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
size_t *len, unsigned char **p, int *is_sign, int *is_last);

View File

@@ -24,81 +24,6 @@
#define SERIAL_LT -1
#define SERIAL_GT 1
/* Convert from presentation format to wire format, in place.
Also map UC -> LC.
Note that using extract_name to get presentation format
then calling to_wire() removes compression and maps case,
thus generating names in canonical form.
Calling to_wire followed by from_wire is almost an identity,
except that the UC remains mapped to LC.
Note that both /000 and '.' are allowed within labels. These get
represented in presentation format using NAME_ESCAPE as an escape
character. In theory, if all the characters in a name were /000 or
'.' or NAME_ESCAPE then all would have to be escaped, so the
presentation format would be twice as long as the spec (1024).
The buffers are all declared as 2049 (allowing for the trailing zero)
for this reason.
*/
static int to_wire(char *name)
{
unsigned char *l, *p, *q, term;
int len;
for (l = (unsigned char*)name; *l != 0; l = p)
{
for (p = l; *p != '.' && *p != 0; p++)
if (*p >= 'A' && *p <= 'Z')
*p = *p - 'A' + 'a';
else if (*p == NAME_ESCAPE)
{
for (q = p; *q; q++)
*q = *(q+1);
(*p)--;
}
term = *p;
if ((len = p - l) != 0)
memmove(l+1, l, len);
*l = len;
p++;
if (term == 0)
*p = 0;
}
return l + 1 - (unsigned char *)name;
}
/* Note: no compression allowed in input. */
static void from_wire(char *name)
{
unsigned char *l, *p, *last;
int len;
for (last = (unsigned char *)name; *last != 0; last += *last+1);
for (l = (unsigned char *)name; *l != 0; l += len+1)
{
len = *l;
memmove(l, l+1, len);
for (p = l; p < l + len; p++)
if (*p == '.' || *p == 0 || *p == NAME_ESCAPE)
{
memmove(p+1, p, 1 + last - p);
len++;
*p++ = NAME_ESCAPE;
(*p)++;
}
l[len] = '.';
}
if ((char *)l != name)
*(l-1) = 0;
}
/* Input in presentation format */
static int count_labels(char *name)
{
@@ -225,7 +150,7 @@ static int is_check_date(unsigned long curtime)
On returning 0, the end has been reached.
*/
struct rdata_state {
u16 *desc;
short *desc;
size_t c;
unsigned char *end, *ip, *op;
char *buff;
@@ -246,7 +171,7 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state
{
d = *(state->desc);
if (d == (u16)-1)
if (d == -1)
{
/* all the bytes to the end. */
if ((state->c = state->end - state->ip) != 0)
@@ -294,7 +219,7 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state
/* Bubble sort the RRset into the canonical order. */
static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx,
static int sort_rrset(struct dns_header *header, size_t plen, short *rr_desc, int rrsetidx,
unsigned char **rrset, char *buff1, char *buff2)
{
int swap, i, j;
@@ -331,7 +256,7 @@ static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
is the identity function and we can compare
the RRs directly. If not we compare the
canonicalised RRs one byte at a time. */
if (*rr_desc == (u16)-1)
if (*rr_desc == -1)
{
int rdmin = rdlen1 > rdlen2 ? rdlen2 : rdlen1;
int cmp = memcmp(state1.ip, state2.ip, rdmin);
@@ -524,7 +449,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
unsigned char *p;
int rdlen, j, name_labels, algo, labels, key_tag;
struct crec *crecp = NULL;
u16 *rr_desc = rrfilter_desc(type);
short *rr_desc = rrfilter_desc(type);
u32 sig_expiration, sig_inception;
int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP;
@@ -671,7 +596,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
If canonicalisation is not needed, a simple insertion into the hash works.
*/
if (*rr_desc == (u16)-1)
if (*rr_desc == -1)
{
len = htons(rdlen);
hash->update(ctx, 2, (unsigned char *)&len);

View File

@@ -186,6 +186,7 @@ struct myoption {
#define LOPT_STALE_CACHE 377
#define LOPT_NORR 378
#define LOPT_NO_IDENT 379
#define LOPT_CACHE_RR 380
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -239,6 +240,7 @@ static const struct myoption opts[] =
{ "local-ttl", 1, 0, 'T' },
{ "no-negcache", 0, 0, 'N' },
{ "no-round-robin", 0, 0, LOPT_NORR },
{ "cache-rr", 1, 0, LOPT_CACHE_RR },
{ "addn-hosts", 1, 0, 'H' },
{ "hostsdir", 1, 0, LOPT_HOST_INOTIFY },
{ "query-port", 1, 0, 'Q' },
@@ -566,13 +568,14 @@ static struct {
{ LOPT_DHCPTTL, ARG_ONE, "<ttl>", gettext_noop("Set TTL in DNS responses with DHCP-derived addresses."), NULL },
{ LOPT_REPLY_DELAY, ARG_ONE, "<integer>", gettext_noop("Delay DHCP replies for at least number of seconds."), NULL },
{ LOPT_RAPID_COMMIT, OPT_RAPID_COMMIT, NULL, gettext_noop("Enables DHCPv4 Rapid Commit option."), NULL },
{ LOPT_DUMPFILE, ARG_ONE, "<path>", gettext_noop("Path to debug packet dump file"), NULL },
{ LOPT_DUMPMASK, ARG_ONE, "<hex>", gettext_noop("Mask which packets to dump"), NULL },
{ LOPT_DUMPFILE, ARG_ONE, "<path>", gettext_noop("Path to debug packet dump file."), NULL },
{ LOPT_DUMPMASK, ARG_ONE, "<hex>", gettext_noop("Mask which packets to dump."), NULL },
{ LOPT_SCRIPT_TIME, OPT_LEASE_RENEW, NULL, gettext_noop("Call dhcp-script when lease expiry changes."), NULL },
{ LOPT_UMBRELLA, ARG_ONE, "[=<optspec>]", gettext_noop("Send Cisco Umbrella identifiers including remote IP."), NULL },
{ LOPT_QUIET_TFTP, OPT_QUIET_TFTP, NULL, gettext_noop("Do not log routine TFTP."), NULL },
{ LOPT_NORR, OPT_NORR, NULL, gettext_noop("Suppress round-robin ordering of DNS records."), NULL },
{ LOPT_NO_IDENT, OPT_NO_IDENT, NULL, gettext_noop("Do not add CHAOS TXT records."), NULL },
{ LOPT_CACHE_RR, ARG_DUP, "RRtype", gettext_noop("Cache this DNS resource record type."), NULL },
{ 0, 0, NULL, NULL, NULL }
};
@@ -3465,6 +3468,27 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
}
}
break;
case LOPT_CACHE_RR:
while (1) {
int type;
struct rrlist *new;
comma = split(arg);
if (!atoi_check(arg, &type) && (type = rrtype(arg)) == 0)
ret_err(_("bad RR type"));
new = opt_malloc(sizeof(struct rrlist));
new->rr = type;
new->next = daemon->cache_rr;
daemon->cache_rr = new;
if (!comma) break;
arg = comma;
}
break;
#ifdef HAVE_DHCP
case 'X': /* --dhcp-lease-max */
@@ -5733,10 +5757,15 @@ void read_opts(int argc, char **argv, char *compile_opts)
{
size_t argbuf_size = MAXDNAME;
char *argbuf = opt_malloc(argbuf_size);
char *buff = opt_malloc(MAXDNAME);
/* Note that both /000 and '.' are allowed within labels. These get
represented in presentation format using NAME_ESCAPE as an escape
character. In theory, if all the characters in a name were /000 or
'.' or NAME_ESCAPE then all would have to be escaped, so the
presentation format would be twice as long as the spec. */
char *buff = opt_malloc((MAXDNAME * 2) + 1);
int option, testmode = 0;
char *arg, *conffile = NULL;
opterr = 0;
daemon = opt_malloc(sizeof(struct daemon));

View File

@@ -89,23 +89,14 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
if (isExtract)
{
unsigned char c = *p;
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID))
if (c == 0 || c == '.' || c == NAME_ESCAPE)
{
if (c == 0 || c == '.' || c == NAME_ESCAPE)
{
*cp++ = NAME_ESCAPE;
*cp++ = c+1;
}
else
*cp++ = c;
*cp++ = NAME_ESCAPE;
*cp++ = c+1;
}
else
#endif
if (c != 0 && c != '.')
*cp++ = c;
else
return 0;
*cp++ = c;
}
else
{
@@ -118,10 +109,9 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
cp++;
if (c1 >= 'A' && c1 <= 'Z')
c1 += 'a' - 'A';
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && c1 == NAME_ESCAPE)
if (c1 == NAME_ESCAPE)
c1 = (*cp++)-1;
#endif
if (c2 >= 'A' && c2 <= 'Z')
c2 += 'a' - 'A';
@@ -502,12 +492,10 @@ static int find_soa(struct dns_header *header, size_t qlen, int *doctored)
}
/* Print TXT reply to log */
static int print_txt(struct dns_header *header, const size_t qlen, char *name,
unsigned char *p, const int ardlen, int secflag)
static int log_txt(char *name, unsigned char *p, const int ardlen, int secflag)
{
unsigned char *p1 = p;
if (!CHECK_LEN(header, p1, qlen, ardlen))
return 0;
/* Loop over TXT payload */
while ((p1 - p) < ardlen)
{
@@ -526,7 +514,7 @@ static int print_txt(struct dns_header *header, const size_t qlen, char *name,
}
*p3 = 0;
log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, (char*)p1, 0);
log_query(secflag | F_FORWARD, name, NULL, (char*)p1, 0);
/* restore */
memmove(p1 + 1, p1, i);
*p1 = len;
@@ -719,6 +707,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
}
else if (qtype == T_SRV)
flags |= F_SRV;
else if (qtype != T_CNAME && rr_on_list(daemon->cache_rr, qtype))
flags |= F_RR;
else
insert = 0; /* NOTE: do not cache data from CNAME queries. */
@@ -804,7 +794,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
#ifdef HAVE_DNSSEC
if (!option_bool(OPT_DNSSEC_VALID) || aqtype != T_RRSIG)
#endif
log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, NULL, aqtype);
log_query(secflag | F_FORWARD | F_UPSTREAM | F_RRNAME, name, NULL, NULL, aqtype);
}
else if (!(flags & F_NXDOMAIN))
{
@@ -829,6 +819,64 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
if (!extract_name(header, qlen, &tmp, name, 1, 0))
return 2;
}
else if (flags & F_RR)
{
short desc, *rrdesc = rrfilter_desc(aqtype);
unsigned char *tmp = namep;
if (!CHECK_LEN(header, p1, qlen, ardlen))
return 2; /* bad packet */
addr.rr.rrtype = aqtype;
addr.rr.datalen = 0;
/* The RR data may include names, and those names may include
compression, which will be rendered meaningless when
copied into another packet.
Here we go through a description of the packet type to
find the names, and extract them to a c-string and then
re-encode them to standalone DNS format without compression. */
if (!(addr.rr.rrdata = blockdata_alloc(NULL, 0)))
return 0;
do
{
desc = *rrdesc++;
if (desc == -1)
{
/* Copy the rest of the RR and end. */
if (!blockdata_expand(addr.rr.rrdata, addr.rr.datalen, (char *)p1, endrr - p1))
return 0;
addr.rr.datalen += endrr - p1;
}
else if (desc == 0)
{
/* Name, extract it then re-encode. */
int len;
if (!extract_name(header, qlen, &p1, name, 1, 0))
return 2;
len = to_wire(name);
if (!blockdata_expand(addr.rr.rrdata, addr.rr.datalen, name, len))
return 0;
addr.rr.datalen += len;
}
else
{
/* desc is length of a block of data to be used as-is */
if (desc > endrr - p1)
desc = endrr - p1;
if (!blockdata_expand(addr.rr.rrdata, addr.rr.datalen, (char *)p1, desc))
return 0;
addr.rr.datalen += desc;
p1 += desc;
}
} while (desc != -1);
/* we overwrote the original name, so get it back here. */
if (!extract_name(header, qlen, &tmp, name, 1, 0))
return 2;
}
else if (flags & (F_IPV4 | F_IPV6))
{
/* copy address into aligned storage */
@@ -876,8 +924,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
if (aqtype == T_TXT)
{
if (!print_txt(header, qlen, name, p1, ardlen, secflag))
return 2;
if (!CHECK_LEN(header, p1, qlen, ardlen))
return 2;
log_txt(name, p1, ardlen, secflag | F_UPSTREAM);
}
else
{
@@ -903,7 +953,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{
if (flags & F_NXDOMAIN)
{
flags &= ~(F_IPV4 | F_IPV6 | F_SRV);
flags &= ~(F_IPV4 | F_IPV6 | F_SRV | F_RR);
/* Can store NXDOMAIN reply for any qtype. */
insert = 1;
@@ -924,7 +974,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
if (ttl == 0)
ttl = cttl;
newc = cache_insert(name, NULL, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));
if (flags & F_RR)
addr.rr.rrtype = qtype;
newc = cache_insert(name, &addr, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));
if (newc && cpp)
{
next_uid(newc);
@@ -2044,7 +2097,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (!found)
{
if ((crecp = cache_find_by_name(NULL, name, now, F_SRV | F_NXDOMAIN | (dryrun ? F_NO_RR : 0))) &&
rd_bit && (!do_bit || (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK))))
rd_bit && (!do_bit || cache_validated(crecp)))
do
{
int stale_flag = 0;
@@ -2125,8 +2178,57 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (!dryrun)
log_query(F_CONFIG | F_NEG, name, &addr, NULL, 0);
}
}
if (!ans && qtype != T_ANY)
{
if ((crecp = cache_find_by_name(NULL, name, now, F_RR | F_NXDOMAIN | (dryrun ? F_NO_RR : 0))) &&
rd_bit && (!do_bit || cache_validated(crecp)))
do
{
int stale_flag = 0;
if (crecp->addr.rr.rrtype == qtype)
{
if (crec_isstale(crecp, now))
{
if (stale)
*stale = 1;
stale_flag = F_STALE;
}
if (!(crecp->flags & F_DNSSECOK))
sec_data = 0;
auth = 0;
ans = 1;
if (!dryrun)
{
char *rrdata = NULL;
if (!(crecp->flags & F_NEG))
{
rrdata = blockdata_retrieve(crecp->addr.rr.rrdata, crecp->addr.rr.datalen, NULL);
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
crec_ttl(crecp, now), NULL, qtype, C_IN, "t",
crecp->addr.rr.datalen, rrdata))
anscount++;
}
/* log after cache insertion as log_txt mangles rrdata */
if (qtype == T_TXT && !(crecp->flags & F_NEG))
log_txt(name, (unsigned char *)rrdata, crecp->addr.rr.datalen, crecp->flags & F_DNSSECOK);
else
log_query(stale_flag | crecp->flags, name, &crecp->addr, NULL, 0);
}
}
} while ((crecp = cache_find_by_name(crecp, name, now, F_RR)));
}
}
if (!ans)
{
/* We may know that the domain doesn't exist for any RRtype. */

View File

@@ -136,9 +136,9 @@ static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, i
if (class == C_IN)
{
u16 *d;
short *d;
for (pp = p, d = rrfilter_desc(type); *d != (u16)-1; d++)
for (pp = p, d = rrfilter_desc(type); *d != -1; d++)
{
if (*d != 0)
pp += *d;
@@ -285,7 +285,7 @@ size_t rrfilter(struct dns_header *header, size_t *plen, int mode)
}
/* This is used in the DNSSEC code too, hence it's exported */
u16 *rrfilter_desc(int type)
short *rrfilter_desc(int type)
{
/* List of RRtypes which include domains in the data.
0 -> domain
@@ -296,7 +296,7 @@ u16 *rrfilter_desc(int type)
anything which needs no mangling.
*/
static u16 rr_desc[] =
static short rr_desc[] =
{
T_NS, 0, -1,
T_MD, 0, -1,
@@ -321,10 +321,10 @@ u16 *rrfilter_desc(int type)
0, -1 /* wildcard/catchall */
};
u16 *p = rr_desc;
short *p = rr_desc;
while (*p != type && *p != 0)
while (*p++ != (u16)-1);
while (*p++ != -1);
return p+1;
}
@@ -352,3 +352,78 @@ int expand_workspace(unsigned char ***wkspc, int *szp, int new)
return 1;
}
/* Convert from presentation format to wire format, in place.
Also map UC -> LC.
Note that using extract_name to get presentation format
then calling to_wire() removes compression and maps case,
thus generating names in canonical form.
Calling to_wire followed by from_wire is almost an identity,
except that the UC remains mapped to LC.
Note that both /000 and '.' are allowed within labels. These get
represented in presentation format using NAME_ESCAPE as an escape
character. In theory, if all the characters in a name were /000 or
'.' or NAME_ESCAPE then all would have to be escaped, so the
presentation format would be twice as long as the spec (1024).
The buffers are all declared as 2049 (allowing for the trailing zero)
for this reason.
*/
int to_wire(char *name)
{
unsigned char *l, *p, *q, term;
int len;
for (l = (unsigned char*)name; *l != 0; l = p)
{
for (p = l; *p != '.' && *p != 0; p++)
if (*p >= 'A' && *p <= 'Z')
*p = *p - 'A' + 'a';
else if (*p == NAME_ESCAPE)
{
for (q = p; *q; q++)
*q = *(q+1);
(*p)--;
}
term = *p;
if ((len = p - l) != 0)
memmove(l+1, l, len);
*l = len;
p++;
if (term == 0)
*p = 0;
}
return l + 1 - (unsigned char *)name;
}
/* Note: no compression allowed in input. */
void from_wire(char *name)
{
unsigned char *l, *p, *last;
int len;
for (last = (unsigned char *)name; *last != 0; last += *last+1);
for (l = (unsigned char *)name; *l != 0; l += len+1)
{
len = *l;
memmove(l, l+1, len);
for (p = l; p < l + len; p++)
if (*p == '.' || *p == 0 || *p == NAME_ESCAPE)
{
memmove(p+1, p, 1 + last - p);
len++;
*p++ = NAME_ESCAPE;
(*p)++;
}
l[len] = '.';
}
if ((char *)l != name)
*(l-1) = 0;
}

View File

@@ -115,6 +115,19 @@ u64 rand64(void)
return (u64)out[outleft+1] + (((u64)out[outleft]) << 32);
}
int rr_on_list(struct rrlist *list, unsigned short rr)
{
while (list)
{
if (list->rr == rr)
return 1;
list = list->next;
}
return 0;
}
/* returns 1 if name is OK and ascii printable
* returns 2 if name should be processed by IDN */
static int check_name(char *in)
@@ -280,11 +293,9 @@ unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit)
if (limit && p + 1 > (unsigned char*)limit)
return NULL;
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && *sval == NAME_ESCAPE)
if (*sval == NAME_ESCAPE)
*p++ = (*(++sval))-1;
else
#endif
*p++ = *sval;
}