summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Kelley <simon@thekelleys.org.uk>2023-03-23 17:15:35 +0000
committerSimon Kelley <simon@thekelleys.org.uk>2023-03-23 17:15:35 +0000
commit638c7c4d20004c0f320820098e29df62a27dd2a1 (patch)
treeeab879580d355225c9645f1aad13f41e6c6abc2d
parent88fc6c80236e946c7b719a8ef0bd0b08bdbc3295 (diff)
downloaddnsmasq-638c7c4d20004c0f320820098e29df62a27dd2a1.tar.gz
Add --cache-rr to enable caching of arbitrary RR types.
-rw-r--r--src/blockdata.c121
-rw-r--r--src/cache.c45
-rw-r--r--src/dnsmasq.c14
-rw-r--r--src/dnsmasq.h20
-rw-r--r--src/dnssec.c87
-rw-r--r--src/option.c37
-rw-r--r--src/rfc1035.c160
-rw-r--r--src/rrfilter.c87
-rw-r--r--src/util.c17
9 files changed, 415 insertions, 173 deletions
diff --git a/src/blockdata.c b/src/blockdata.c
index 4c26155..56698c7 100644
--- a/src/blockdata.c
+++ b/src/blockdata.c
@@ -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;
-
- 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))
+
+ if ((blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len) > 0)
{
- /* failed read free partial chain */
- blockdata_free(ret);
- return NULL;
+ 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;
diff --git a/src/cache.c b/src/cache.c
index 6ae6688..64fc69d 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -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;
}
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index bd3dcf5..6fb9571 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -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);
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 7d26460..376c630 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -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);
diff --git a/src/dnssec.c b/src/dnssec.c
index 219ba9a..aa196eb 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -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);
diff --git a/src/option.c b/src/option.c
index 2e208ba..87a321e 100644
--- a/src/option.c
+++ b/src/option.c
@@ -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));
diff --git a/src/rfc1035.c b/src/rfc1035.c
index ea21ffa..28579c6 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -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. */
diff --git a/src/rrfilter.c b/src/rrfilter.c
index 3a5547a..e4c56cb 100644
--- a/src/rrfilter.c
+++ b/src/rrfilter.c
@@ -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;
+}
diff --git a/src/util.c b/src/util.c
index e0ce67d..073d7ad 100644
--- a/src/util.c
+++ b/src/util.c
@@ -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;
}