diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/blockdata.c | 121 | ||||
-rw-r--r-- | src/cache.c | 45 | ||||
-rw-r--r-- | src/dnsmasq.c | 14 | ||||
-rw-r--r-- | src/dnsmasq.h | 20 | ||||
-rw-r--r-- | src/dnssec.c | 87 | ||||
-rw-r--r-- | src/option.c | 37 | ||||
-rw-r--r-- | src/rfc1035.c | 160 | ||||
-rw-r--r-- | src/rrfilter.c | 87 | ||||
-rw-r--r-- | src/util.c | 17 |
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; +} @@ -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; } |