Compare commits

...
11 Commits
Author SHA1 Message Date
Simon Kelley 424c4a8a53 Merge branch 'unsigned' 2015-01-07 22:01:14 +00:00
Simon Kelley 97e618a0e3 DNSSEC: do top-down search for limit of secure delegation. 2015-01-07 21:55:43 +00:00
Yousong Zhou d8dbd903d0 Fix race condition issue in makefile. 2015-01-05 17:03:35 +00:00
Yousong Zhou 81c538efce Implement makefile dependencies on COPTS variable. 2015-01-03 16:36:14 +00:00
Matthias Andree d310ab7ecb Fix build failure in new inotify code on BSD. 2014-12-27 15:36:38 +00:00
Simon Kelley 0b1008d367 Bad packet protection. 2014-12-27 15:33:32 +00:00
Glen Huang 32fc6dbe03 Add --ignore-address option. 2014-12-27 15:28:12 +00:00
Simon Kelley 83d2ed09fc Initialise return value. 2014-12-23 18:42:38 +00:00
Simon Kelley fbc5205702 Fix problems validating NSEC3 and wildcards. 2014-12-23 15:46:08 +00:00
Simon Kelley cbc6524234 Make caching work for CNAMEs pointing to A/AAAA records shadowed in /etc/hosts
If the answer to an upstream query is a CNAME which points to an
A/AAAA record which also exists in /etc/hosts and friends, then
caching is suppressed, to avoid inconsistent answers. This is
now modified to allow caching when the upstream and local A/AAAA
records have the same value.
2014-12-21 21:21:53 +00:00
Simon Kelley 094b5c3d90 Fix crash in DNSSEC code when attempting to verify large RRs. 2014-12-21 16:11:52 +00:00
11 changed files with 519 additions and 330 deletions

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@ src/*.mo
src/dnsmasq.pot
src/dnsmasq
src/dnsmasq_baseline
src/.configured
src/.copts_*
contrib/wrt/dhcp_lease_time
contrib/wrt/dhcp_release
debian/base/
+21 -1
View File
@@ -19,8 +19,28 @@ version 2.73
the answers given by --interface-name. Note that reverse queries
(ie looking for names, given addresses) are not affected.
Thanks to Michael Gorbach for the suggestion.
Fix crash in DNSSEC code with long RRs. Thanks to Marco Davids
for the bug report.
Add --ignore-address option. Ignore replies to A-record
queries which include the specified address. No error is
generated, dnsmasq simply continues to listen for another
reply. This is useful to defeat blocking strategies which
rely on quickly supplying a forged answer to a DNS
request for certain domains, before the correct answer can
arrive. Thanks to Glen Huang for the patch.
Revisit the part of DNSSEC validation which determines if an
unsigned answer is legit, or is in some part of the DNS
tree which should be signed. Dnsmasq now works from the
DNS root downward looking for the limit of signed
delegations, rather than working bottom up. This is
both more correct, and less likely to trip over broken
nameservers in the unsigned parts of the DNS tree
which don't respond well to DNSSEC queries.
version 2.72
Add ra-advrouter mode, for RFC-3775 mobile IPv6 support.
+8 -4
View File
@@ -64,6 +64,8 @@ nettle_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG
gmp_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC NO_GMP --copy -lgmp`
sunos_libs = `if uname | grep SunOS >/dev/null 2>&1; then echo -lsocket -lnsl -lposix4; fi`
version = -DVERSION='\"`$(top)/bld/get-version $(top)`\"'
copts_conf = .copts_$(shell $(CC) -DDNSMASQ_COMPILE_OPTS $(COPTS) -E $(top)/$(SRC)/dnsmasq.h | \
( md5sum 2>/dev/null || md5 ) | cut -f 1 -d ' ')
objs = cache.o rfc1035.o util.o option.o forward.o network.o \
dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \
@@ -83,7 +85,7 @@ all : $(BUILDDIR)
mostly_clean :
rm -f $(BUILDDIR)/*.mo $(BUILDDIR)/*.pot
rm -f $(BUILDDIR)/.configured $(BUILDDIR)/*.o $(BUILDDIR)/dnsmasq.a $(BUILDDIR)/dnsmasq
rm -f $(BUILDDIR)/.copts_* $(BUILDDIR)/*.o $(BUILDDIR)/dnsmasq.a $(BUILDDIR)/dnsmasq
clean : mostly_clean
rm -f $(BUILDDIR)/dnsmasq_baseline
@@ -139,17 +141,19 @@ bloatcheck : $(BUILDDIR)/dnsmasq_baseline mostly_clean all
# rules below are targets in recusive makes with cwd=$(BUILDDIR)
.configured: $(hdrs)
@rm -f *.o
$(copts_conf): $(hdrs)
@rm -f *.o .copts_*
@touch $@
$(objs:.o=.c) $(hdrs):
ln -s $(top)/$(SRC)/$@ .
$(objs): $(copts_conf) $(hdrs)
.c.o:
$(CC) $(CFLAGS) $(COPTS) $(i18n) $(build_cflags) $(RPM_OPT_FLAGS) -c $<
dnsmasq : .configured $(hdrs) $(objs)
dnsmasq : $(objs)
$(CC) $(LDFLAGS) -o $@ $(objs) $(build_libs) $(LIBS)
dnsmasq.pot : $(objs:.o=.c) $(hdrs)
+6
View File
@@ -293,6 +293,12 @@ an advertising web page in response to queries for unregistered names,
instead of the correct NXDOMAIN response. This option tells dnsmasq to
fake the correct response when it sees this behaviour. As at Sept 2003
the IP address being returned by Verisign is 64.94.110.11
.TP
.B \-B, --ignore-address=<ipaddr>
Ignore replies to A-record queries which include the specified address.
No error is generated, dnsmasq simply continues to listen for another reply.
This is useful to defeat blocking strategies which rely on quickly supplying a
forged answer to a DNS request for certain domain, before the correct answer can arrive.
.TP
.B \-f, --filterwin2k
Later versions of windows make periodic DNS requests which don't get sensible answers from
+25 -9
View File
@@ -322,7 +322,7 @@ static int is_expired(time_t now, struct crec *crecp)
return 1;
}
static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsigned short flags)
static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t now, unsigned short flags)
{
/* Scan and remove old entries.
If (flags & F_FORWARD) then remove any forward entries for name and any expired
@@ -331,8 +331,8 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign
entries in the whole cache.
If (flags == 0) remove any expired entries in the whole cache.
In the flags & F_FORWARD case, the return code is valid, and returns zero if the
name exists in the cache as a HOSTS or DHCP entry (these are never deleted)
In the flags & F_FORWARD case, the return code is valid, and returns a non-NULL pointer
to a cache entry if the name exists in the cache as a HOSTS or DHCP entry (these are never deleted)
We take advantage of the fact that hash chains have stuff in the order <reverse>,<other>,<immortal>
so that when we hit an entry which isn't reverse and is immortal, we're done. */
@@ -361,7 +361,7 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign
(((crecp->flags | flags) & F_CNAME) && !(crecp->flags & (F_DNSKEY | F_DS))))
{
if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
return 0;
return crecp;
*up = crecp->hash_next;
cache_unlink(crecp);
cache_free(crecp);
@@ -378,7 +378,7 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign
crecp->addr.sig.type_covered == addr->addr.dnssec.type))
{
if (crecp->flags & F_CONFIG)
return 0;
return crecp;
*up = crecp->hash_next;
cache_unlink(crecp);
cache_free(crecp);
@@ -423,7 +423,7 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign
up = &crecp->hash_next;
}
return 1;
return NULL;
}
/* Note: The normal calling sequence is
@@ -471,10 +471,26 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
return NULL;
/* First remove any expired entries and entries for the name/address we
are currently inserting. Fail if we attempt to delete a name from
/etc/hosts or DHCP. */
if (!cache_scan_free(name, addr, now, flags))
are currently inserting. */
if ((new = cache_scan_free(name, addr, now, flags)))
{
/* We're trying to insert a record over one from
/etc/hosts or DHCP, or other config. If the
existing record is for an A or AAAA and
the record we're trying to insert is the same,
just drop the insert, but don't error the whole process. */
if ((flags & (F_IPV4 | F_IPV6)) && (flags & F_FORWARD))
{
if ((flags & F_IPV4) && (new->flags & F_IPV4) &&
new->addr.addr.addr.addr4.s_addr == addr->addr.addr4.s_addr)
return new;
#ifdef HAVE_IPV6
else if ((flags & F_IPV6) && (new->flags & F_IPV6) &&
IN6_ARE_ADDR_EQUAL(&new->addr.addr.addr.addr6, &addr->addr.addr6))
return new;
#endif
}
insert_error = 1;
return NULL;
}
+9 -5
View File
@@ -569,8 +569,9 @@ struct hostsfile {
#define STAT_SECURE_WILDCARD 7
#define STAT_NO_SIG 8
#define STAT_NO_DS 9
#define STAT_NEED_DS_NEG 10
#define STAT_CHASE_CNAME 11
#define STAT_NO_NS 10
#define STAT_NEED_DS_NEG 11
#define STAT_CHASE_CNAME 12
#define FREC_NOREBIND 1
#define FREC_CHECKING_DISABLED 2
@@ -604,7 +605,9 @@ struct frec {
#ifdef HAVE_DNSSEC
int class, work_counter;
struct blockdata *stash; /* Saved reply, whilst we validate */
size_t stash_len;
struct blockdata *orig_domain; /* domain of original query, whilst
we're seeing is if in unsigned domain */
size_t stash_len, name_start, name_len;
struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
struct frec *blocking_query; /* Query which is blocking us. */
#endif
@@ -930,7 +933,7 @@ extern struct daemon {
char *runfile;
char *lease_change_command;
struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces;
struct bogus_addr *bogus_addr;
struct bogus_addr *bogus_addr, *ignore_addr;
struct server *servers;
struct ipsets *ipsets;
int log_fac; /* log facility */
@@ -1093,6 +1096,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
time_t now, int *ad_reqd, int *do_bit);
int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
struct bogus_addr *addr, time_t now);
int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr);
unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
size_t *len, unsigned char **p, int *is_sign);
int check_for_local_domain(char *name, time_t now);
@@ -1125,7 +1129,7 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr);
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class);
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer);
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer, int *nons);
int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname);
int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen);
size_t filter_rrsigs(struct dns_header *header, size_t plen);
+201 -163
View File
@@ -456,16 +456,27 @@ static u16 *get_desc(int type)
/* Return bytes of canonicalised rdata, when the return value is zero, the remaining
data, pointed to by *p, should be used raw. */
static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff,
static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen,
unsigned char **p, u16 **desc)
{
int d = **desc;
(*desc)++;
/* No more data needs mangling */
if (d == (u16)-1)
return 0;
{
/* If there's more data than we have space for, just return what fits,
we'll get called again for more chunks */
if (end - *p > bufflen)
{
memcpy(buff, *p, bufflen);
*p += bufflen;
return bufflen;
}
return 0;
}
(*desc)++;
if (d == 0 && extract_name(header, plen, p, buff, 1, 0))
/* domain-name, canonicalise */
@@ -560,7 +571,7 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
if (left1 != 0)
memmove(buff1, buff1 + len1 - left1, left1);
if ((len1 = get_rdata(header, plen, end1, buff1 + left1, &p1, &dp1)) == 0)
if ((len1 = get_rdata(header, plen, end1, buff1 + left1, MAXDNAME - left1, &p1, &dp1)) == 0)
{
quit = 1;
len1 = end1 - p1;
@@ -571,7 +582,7 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
if (left2 != 0)
memmove(buff2, buff2 + len2 - left2, left2);
if ((len2 = get_rdata(header, plen, end2, buff2 + left2, &p2, &dp2)) == 0)
if ((len2 = get_rdata(header, plen, end2, buff2 + left2, MAXDNAME - left2, &p2, &dp2)) == 0)
{
quit = 1;
len2 = end2 - p2;
@@ -604,6 +615,7 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
Return code:
STAT_SECURE if it validates.
STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
(In this case *wildcard_out points to the "body" of the wildcard within name.)
STAT_NO_SIG no RRsigs found.
STAT_INSECURE RRset empty.
STAT_BOGUS signature is wrong, bad packet.
@@ -614,8 +626,8 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
name is unchanged on exit. keyname is used as workspace and trashed.
*/
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class,
int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in)
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type,
char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in)
{
static unsigned char **rrset = NULL, **sigs = NULL;
static int rrset_sz = 0, sig_sz = 0;
@@ -625,10 +637,13 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
struct crec *crecp = NULL;
int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag;
u16 *rr_desc = get_desc(type);
if (wildcard_out)
*wildcard_out = NULL;
if (!(p = skip_questions(header, plen)))
return STAT_BOGUS;
name_labels = count_labels(name); /* For 4035 5.3.2 check */
/* look for RRSIGs for this RRset and get pointers to each RR in the set. */
@@ -787,8 +802,16 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
{
int k;
for (k = name_labels - labels; k != 0; k--)
while (*name_start != '.' && *name_start != 0)
name_start++;
{
while (*name_start != '.' && *name_start != 0)
name_start++;
if (k != 1 && *name_start == '.')
name_start++;
}
if (wildcard_out)
*wildcard_out = name_start+1;
name_start--;
*name_start = '*';
}
@@ -808,7 +831,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
/* canonicalise rdata and calculate length of same, use name buffer as workspace */
cp = p;
dp = rr_desc;
for (len = 0; (seg = get_rdata(header, plen, end, name, &cp, &dp)) != 0; len += seg);
for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME, &cp, &dp)) != 0; len += seg);
len += end - cp;
len = htons(len);
hash->update(ctx, 2, (unsigned char *)&len);
@@ -816,7 +839,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
/* Now canonicalise again and digest. */
cp = p;
dp = rr_desc;
while ((seg = get_rdata(header, plen, end, name, &cp, &dp)))
while ((seg = get_rdata(header, plen, end, name, MAXDNAME, &cp, &dp)))
hash->update(ctx, seg, (unsigned char *)name);
if (cp != end)
hash->update(ctx, end - cp, cp);
@@ -852,8 +875,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
/* The DNS packet is expected to contain the answer to a DNSKEY query.
Put all DNSKEYs in the answer which are valid into the cache.
return codes:
STAT_INSECURE No DNSKEYs in reply.
STAT_SECURE At least one valid DNSKEY found and in cache.
STAT_SECURE At least one valid DNSKEY found and in cache.
STAT_BOGUS No DNSKEYs found, which can be validated with DS,
or self-sign for DNSKEY RRset is not valid, bad packet.
STAT_NEED_DS DS records to validate a key not found, name in keyname
@@ -873,11 +895,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
GETSHORT(qtype, p);
GETSHORT(qclass, p);
if (qtype != T_DNSKEY || qclass != class)
if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
return STAT_BOGUS;
if (ntohs(header->ancount) == 0)
return STAT_INSECURE;
/* See if we have cached a DS record which validates this key */
if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
@@ -963,7 +982,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
if (recp1->addr.ds.keylen == (int)hash->digest_size &&
(ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) &&
memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, key, rdlen - 4, algo, keytag) == STAT_SECURE)
validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE)
{
valid = 1;
break;
@@ -1080,17 +1099,17 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
/* The DNS packet is expected to contain the answer to a DS query
Put all DSs in the answer which are valid into the cache.
return codes:
STAT_INSECURE no DS in reply or not signed.
STAT_SECURE At least one valid DS found and in cache.
STAT_NO_DS It's proved there's no DS here.
STAT_BOGUS At least one DS found, which fails validation, bad packet.
STAT_NO_NS It's proved there's no DS _or_ NS here.
STAT_BOGUS no DS in reply or not signed, fails validation, bad packet.
STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname
*/
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
{
unsigned char *p = (unsigned char *)(header+1);
int qtype, qclass, val, i, neganswer;
int qtype, qclass, val, i, neganswer, nons;
if (ntohs(header->qdcount) != 1 ||
!(p = skip_name(p, header, plen, 4)))
@@ -1102,32 +1121,39 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
if (qtype != T_DS || qclass != class)
val = STAT_BOGUS;
else
val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, &neganswer);
if (val == STAT_NO_SIG)
val = STAT_INSECURE;
val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, &neganswer, &nons);
/* Note dnssec_validate_reply() will have cached positive answers */
if (val == STAT_NO_SIG || val == STAT_INSECURE)
val = STAT_BOGUS;
p = (unsigned char *)(header+1);
extract_name(header, plen, &p, name, 1, 4);
p += 4; /* qtype, qclass */
if (!(p = skip_section(p, ntohs(header->ancount), header, plen)))
return STAT_BOGUS;
val = STAT_BOGUS;
if (val == STAT_BOGUS)
log_query(F_UPSTREAM, name, NULL, "BOGUS DS");
if ((val == STAT_SECURE || val == STAT_INSECURE) && neganswer)
{
int rdlen, flags = F_FORWARD | F_DS | F_NEG;
log_query(F_UPSTREAM, name, NULL, "BOGUS DS");
return STAT_BOGUS;
}
/* By here, the answer is proved secure, and a positive answer has been cached. */
if (val == STAT_SECURE && neganswer)
{
int rdlen, flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
unsigned long ttl, minttl = ULONG_MAX;
struct all_addr a;
if (RCODE(header) == NXDOMAIN)
flags |= F_NXDOMAIN;
if (val == STAT_SECURE)
flags |= F_DNSSECOK;
/* We only cache validated DS records, DNSSECOK flag hijacked
to store presence/absence of NS. */
if (nons)
flags &= ~F_DNSSECOK;
for (i = ntohs(header->nscount); i != 0; i--)
{
@@ -1173,10 +1199,12 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
a.addr.dnssec.class = class;
cache_insert(name, &a, now, ttl, flags);
cache_end_insert();
cache_end_insert();
log_query(F_UPSTREAM, name, NULL, nons ? "no delegation" : "no DS");
}
return (val == STAT_SECURE) ? STAT_NO_DS : STAT_INSECURE;
return nons ? STAT_NO_NS : STAT_NO_DS;
}
return val;
@@ -1300,12 +1328,15 @@ static int find_nsec_records(struct dns_header *header, size_t plen, unsigned ch
}
static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
char *workspace1, char *workspace2, char *name, int type)
char *workspace1, char *workspace2, char *name, int type, int *nons)
{
int i, rc, rdlen;
unsigned char *p, *psave;
int offset = (type & 0xff) >> 3;
int mask = 0x80 >> (type & 0x07);
if (nons)
*nons = 0;
/* Find NSEC record that proves name doesn't exist */
for (i = 0; i < nsec_count; i++)
@@ -1332,6 +1363,10 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
rdlen -= p - psave;
/* rdlen is now length of type map, and p points to it */
/* If we can prove that there's no NS record, return that information. */
if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) == 0)
*nons = 1;
while (rdlen >= 2)
{
if (!CHECK_LEN(header, p, plen, rdlen))
@@ -1432,14 +1467,98 @@ static int base32_decode(char *in, unsigned char *out)
return p - out;
}
static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type,
char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons)
{
int i, hash_len, salt_len, base32_len, rdlen;
unsigned char *p, *psave;
for (i = 0; i < nsec_count; i++)
if ((p = nsecs[i]))
{
if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
return 0;
p += 8; /* class, type, TTL */
GETSHORT(rdlen, p);
psave = p;
p += 4; /* algo, flags, iterations */
salt_len = *p++; /* salt_len */
p += salt_len; /* salt */
hash_len = *p++; /* p now points to next hashed name */
if (!CHECK_LEN(header, p, plen, hash_len))
return 0;
if (digest_len == base32_len && hash_len == base32_len)
{
int rc = memcmp(workspace2, digest, digest_len);
if (rc == 0)
{
/* We found an NSEC3 whose hashed name exactly matches the query, so
we just need to check the type map. p points to the RR data for the record. */
int offset = (type & 0xff) >> 3;
int mask = 0x80 >> (type & 0x07);
p += hash_len; /* skip next-domain hash */
rdlen -= p - psave;
if (!CHECK_LEN(header, p, plen, rdlen))
return 0;
/* If we can prove that there's no NS record, return that information. */
if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) == 0)
*nons = 1;
while (rdlen >= 2)
{
if (p[0] == type >> 8)
{
/* Does the NSEC3 say our type exists? */
if (offset < p[1] && (p[offset+2] & mask) != 0)
return STAT_BOGUS;
break; /* finshed checking */
}
rdlen -= p[1];
p += p[1];
}
return 1;
}
else if (rc <= 0)
{
/* Normal case, hash falls between NSEC3 name-hash and next domain name-hash,
wrap around case, name-hash falls between NSEC3 name-hash and end */
if (memcmp(p, digest, digest_len) > 0 || memcmp(workspace2, p, digest_len) > 0)
return 1;
}
else
{
/* wrap around case, name falls between start and next domain name */
if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0)
return 1;
}
}
}
return 0;
}
static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
char *workspace1, char *workspace2, char *name, int type)
char *workspace1, char *workspace2, char *name, int type, char *wildname, int *nons)
{
unsigned char *salt, *p, *digest;
int digest_len, i, iterations, salt_len, hash_len, base32_len, algo = 0;
int digest_len, i, iterations, salt_len, base32_len, algo = 0;
struct nettle_hash const *hash;
char *closest_encloser, *next_closest, *wildcard;
if (nons)
*nons = 0;
/* Look though the NSEC3 records to find the first one with
an algorithm we support (currently only algo == 1).
@@ -1509,7 +1628,14 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
if (!(hash = hash_find("sha1")))
return STAT_BOGUS;
/* Now, we need the "closest encloser NSEC3" */
if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)
return STAT_BOGUS;
if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons))
return STAT_SECURE;
/* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3"
or an answer inferred from a wildcard record. */
closest_encloser = name;
next_closest = NULL;
@@ -1518,6 +1644,9 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
if (*closest_encloser == '.')
closest_encloser++;
if (wildname && hostname_isequal(closest_encloser, wildname))
break;
if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0)
return STAT_BOGUS;
@@ -1540,132 +1669,39 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
}
while ((closest_encloser = strchr(closest_encloser, '.')));
/* No usable NSEC3s */
if (i == nsec_count)
if (!closest_encloser)
return STAT_BOGUS;
if (!next_closest)
{
/* We found an NSEC3 whose hashed name exactly matches the query, so
Now we just need to check the type map. p points to the RR data for the record. */
int rdlen;
unsigned char *psave;
int offset = (type & 0xff) >> 3;
int mask = 0x80 >> (type & 0x07);
p += 8; /* class, type, TTL */
GETSHORT(rdlen, p);
psave = p;
p += 5 + salt_len; /* algo, flags, iterations, salt_len, salt */
hash_len = *p++;
if (!CHECK_LEN(header, p, plen, hash_len))
return STAT_BOGUS; /* bad packet */
p += hash_len;
rdlen -= p - psave;
while (rdlen >= 2)
{
if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_BOGUS;
if (p[0] == type >> 8)
{
/* Does the NSEC3 say our type exists? */
if (offset < p[1] && (p[offset+2] & mask) != 0)
return STAT_BOGUS;
break; /* finshed checking */
}
rdlen -= p[1];
p += p[1];
}
return STAT_SECURE;
}
/* Look for NSEC3 that proves the non-existence of the next-closest encloser */
if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)
return STAT_BOGUS;
for (i = 0; i < nsec_count; i++)
if ((p = nsecs[i]))
{
if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
return STAT_BOGUS;
p += 15 + salt_len; /* class, type, TTL, rdlen, algo, flags, iterations, salt_len, salt */
hash_len = *p++; /* p now points to next hashed name */
if (!CHECK_LEN(header, p, plen, hash_len))
return STAT_BOGUS;
if (digest_len == base32_len && hash_len == base32_len)
{
if (memcmp(workspace2, digest, digest_len) <= 0)
{
/* Normal case, hash falls between NSEC3 name-hash and next domain name-hash,
wrap around case, name-hash falls between NSEC3 name-hash and end */
if (memcmp(p, digest, digest_len) > 0 || memcmp(workspace2, p, digest_len) > 0)
return STAT_SECURE;
}
else
{
/* wrap around case, name falls between start and next domain name */
if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0)
return STAT_SECURE;
}
}
}
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL))
return STAT_BOGUS;
/* Finally, check that there's no seat of wildcard synthesis */
if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest)
return STAT_BOGUS;
if (!wildname)
{
if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest)
return STAT_BOGUS;
wildcard--;
*wildcard = '*';
if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
return STAT_BOGUS;
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL))
return STAT_BOGUS;
}
wildcard--;
*wildcard = '*';
if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
return STAT_BOGUS;
for (i = 0; i < nsec_count; i++)
if ((p = nsecs[i]))
{
if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
return STAT_BOGUS;
p += 15 + salt_len; /* class, type, TTL, rdlen, algo, flags, iterations, salt_len, salt */
hash_len = *p++; /* p now points to next hashed name */
if (!CHECK_LEN(header, p, plen, hash_len))
return STAT_BOGUS;
if (digest_len == base32_len && hash_len == base32_len)
{
if (memcmp(workspace2, digest, digest_len) <= 0)
{
/* Normal case, hash falls between NSEC3 name-hash and next domain name-hash,
wrap around case, name-hash falls between NSEC3 name-hash and end */
if (memcmp(p, digest, digest_len) > 0 || memcmp(workspace2, p, digest_len) > 0)
return STAT_SECURE;
}
else
{
/* wrap around case, name falls between start and next domain name */
if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0)
return STAT_SECURE;
}
}
}
return STAT_BOGUS;
return STAT_SECURE;
}
/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */
/* Returns are the same as validate_rrset, plus the class if the missing key is in *class */
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer)
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname,
int *class, int *neganswer, int *nons)
{
unsigned char *ans_start, *qname, *p1, *p2, **nsecs;
int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype;
@@ -1781,8 +1817,9 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
struct all_addr a;
struct blockdata *key;
struct crec *crecp;
rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0);
char *wildname;
rc = validate_rrset(now, header, plen, class1, type1, name, keyname, &wildname, NULL, 0, 0, 0);
if (rc == STAT_SECURE_WILDCARD)
{
@@ -1794,10 +1831,11 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
return STAT_BOGUS; /* No NSECs or bad packet */
if (nsec_type == T_NSEC)
rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1, NULL);
else
rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename,
keyname, name, type1, wildname, NULL);
if (rc != STAT_SECURE)
return rc;
}
@@ -1920,9 +1958,9 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
return STAT_BOGUS;
if (nsec_type == T_NSEC)
return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype);
return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, nons);
else
return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype);
return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, NULL, nons);
}
/* Chase the CNAME chain in the packet until the first record which _doesn't validate.
@@ -1969,7 +2007,7 @@ int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char
return STAT_INSECURE;
/* validate CNAME chain, return if insecure or need more data */
rc = validate_rrset(now, header, plen, class, type, name, keyname, NULL, 0, 0, 0);
rc = validate_rrset(now, header, plen, class, type, name, keyname, NULL, NULL, 0, 0, 0);
if (rc != STAT_SECURE)
{
if (rc == STAT_NO_SIG)
+194 -142
View File
@@ -26,8 +26,9 @@ static void free_frec(struct frec *f);
#ifdef HAVE_DNSSEC
static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n,
int class, char *name, char *keyname, struct server *server, int *keycount);
static int do_check_sign(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
static int send_check_sign(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname);
static int do_check_sign(struct frec *forward, int status, time_t now, char *name, char *keyname);
static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen,
char *name, char *keyname);
#endif
@@ -724,6 +725,10 @@ void reply_query(int fd, int family, time_t now)
if (!(forward = lookup_frec(ntohs(header->id), hash)))
return;
if (daemon->ignore_addr && RCODE(header) == NOERROR &&
check_for_ignored_address(header, n, daemon->ignore_addr))
return;
if ((RCODE(header) == SERVFAIL || RCODE(header) == REFUSED) &&
!option_bool(OPT_ORDER) &&
forward->forwardall == 0)
@@ -811,18 +816,22 @@ void reply_query(int fd, int family, time_t now)
else if (forward->flags & FREC_DS_QUERY)
{
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
if (status == STAT_NO_DS)
status = STAT_INSECURE;
if (status == STAT_NO_DS || status == STAT_NO_NS)
status = STAT_BOGUS;
}
else if (forward->flags & FREC_CHECK_NOSIGN)
status = do_check_sign(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
{
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
if (status != STAT_NEED_KEY)
status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname);
}
else
{
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL);
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL);
if (status == STAT_NO_SIG)
{
if (option_bool(OPT_DNSSEC_NO_SIGN))
status = send_check_sign(now, header, n, daemon->namebuff, daemon->keyname);
status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname);
else
status = STAT_INSECURE;
}
@@ -857,6 +866,7 @@ void reply_query(int fd, int family, time_t now)
new->blocking_query = NULL;
new->sentto = server;
new->rfd4 = NULL;
new->orig_domain = NULL;
#ifdef HAVE_IPV6
new->rfd6 = NULL;
#endif
@@ -885,7 +895,9 @@ void reply_query(int fd, int family, time_t now)
new->new_id = get_id();
header->id = htons(new->new_id);
/* Save query for retransmission */
new->stash = blockdata_alloc((char *)header, nn);
if (!(new->stash = blockdata_alloc((char *)header, nn)))
return;
new->stash_len = nn;
/* Don't resend this. */
@@ -942,18 +954,22 @@ void reply_query(int fd, int family, time_t now)
else if (forward->flags & FREC_DS_QUERY)
{
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
if (status == STAT_NO_DS)
status = STAT_INSECURE;
if (status == STAT_NO_DS || status == STAT_NO_NS)
status = STAT_BOGUS;
}
else if (forward->flags & FREC_CHECK_NOSIGN)
status = do_check_sign(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
{
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
if (status != STAT_NEED_KEY)
status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname);
}
else
{
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL);
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL);
if (status == STAT_NO_SIG)
{
if (option_bool(OPT_DNSSEC_NO_SIGN))
status = send_check_sign(now, header, n, daemon->namebuff, daemon->keyname);
status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname);
else
status = STAT_INSECURE;
}
@@ -1315,70 +1331,80 @@ void receive_query(struct listener *listen, time_t now)
/* UDP: we've got an unsigned answer, return STAT_INSECURE if we can prove there's no DS
and therefore the answer shouldn't be signed, or STAT_BOGUS if it should be, or
STAT_NEED_DS_NEG and keyname if we need to do the query. */
static int send_check_sign(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname)
static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen,
char *name, char *keyname)
{
struct crec *crecp;
char *name_start = name;
int status = dnssec_chase_cname(now, header, plen, name, keyname);
if (status != STAT_INSECURE)
return status;
/* Store the domain we're trying to check. */
forward->name_start = strlen(name);
forward->name_len = forward->name_start + 1;
if (!(forward->orig_domain = blockdata_alloc(name, forward->name_len)))
return STAT_BOGUS;
return do_check_sign(forward, 0, now, name, keyname);
}
/* We either have a a reply (header non-NULL, or we need to start by looking in the cache */
static int do_check_sign(struct frec *forward, int status, time_t now, char *name, char *keyname)
{
/* get domain we're checking back from blockdata store, it's stored on the original query. */
while (forward->dependent)
forward = forward->dependent;
blockdata_retrieve(forward->orig_domain, forward->name_len, name);
while (1)
{
crecp = cache_find_by_name(NULL, name_start, now, F_DS);
if (crecp && (crecp->flags & F_DNSSECOK))
return (crecp->flags & F_NEG) ? STAT_INSECURE : STAT_BOGUS;
if (crecp && (crecp->flags & F_NEG) && (name_start = strchr(name_start, '.')))
{
name_start++; /* chop a label off and try again */
continue;
}
char *p;
/* Reached the root */
if (!name_start)
if (status == 0)
{
struct crec *crecp;
/* Haven't received answer, see if in cache */
if (!(crecp = cache_find_by_name(NULL, &name[forward->name_start], now, F_DS)))
{
/* put name of DS record we're missing into keyname */
strcpy(keyname, &name[forward->name_start]);
/* and wait for reply to arrive */
return STAT_NEED_DS_NEG;
}
/* F_DNSSECOK misused in DS cache records to non-existance of NS record */
if (!(crecp->flags & F_NEG))
status = STAT_SECURE;
else if (crecp->flags & F_DNSSECOK)
status = STAT_NO_DS;
else
status = STAT_NO_NS;
}
/* Have entered non-signed part of DNS tree. */
if (status == STAT_NO_DS)
return STAT_INSECURE;
if (status == STAT_BOGUS)
return STAT_BOGUS;
strcpy(keyname, name_start);
return STAT_NEED_DS_NEG;
}
}
/* There's a proven DS record, or we're within a zone, where there doesn't need
to be a DS record. Add a name and try again.
If we've already tried the whole name, then fail */
/* Got answer to DS query from send_check_sign, check for proven non-existence, or make the next DS query to try. */
static int do_check_sign(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
{
char *name_start;
unsigned char *p;
int status;
/* In this case only, a SERVFAIL reply allows us to continue up the tree, looking for a
suitable NSEC reply to DS queries. */
if (RCODE(header) != SERVFAIL)
{
status = dnssec_validate_ds(now, header, plen, name, keyname, class);
if (forward->name_start == 0)
return STAT_BOGUS;
if (status != STAT_INSECURE)
{
if (status == STAT_NO_DS)
status = STAT_INSECURE;
return status;
}
for (p = &name[forward->name_start-2]; (*p != '.') && (p != name); p--);
if (p != name)
p++;
forward->name_start = p - name;
status = 0; /* force to cache when we iterate. */
}
p = (unsigned char *)(header+1);
if (extract_name(header, plen, &p, name, 1, 4) &&
(name_start = strchr(name, '.')))
{
name_start++; /* chop a label off and try again */
strcpy(keyname, name_start);
return STAT_NEED_DS_NEG;
}
return STAT_BOGUS;
}
/* Move toward the root, until we find a signed non-existance of a DS, in which case
@@ -1390,9 +1416,10 @@ static int tcp_check_for_unsigned_zone(time_t now, struct dns_header *header, s
size_t m;
unsigned char *packet, *payload;
u16 *length;
unsigned char *p = (unsigned char *)(header+1);
int status;
char *name_start = name;
int status, name_len;
struct blockdata *block;
char *name_start;
/* Get first insecure entry in CNAME chain */
status = tcp_key_recurse(now, STAT_CHASE_CNAME, header, plen, class, name, keyname, server, keycount);
@@ -1405,95 +1432,113 @@ static int tcp_check_for_unsigned_zone(time_t now, struct dns_header *header, s
payload = &packet[2];
header = (struct dns_header *)payload;
length = (u16 *)packet;
/* Stash the name away, since the buffer will be trashed when we recurse */
name_len = strlen(name) + 1;
name_start = name + name_len - 1;
if (!(block = blockdata_alloc(name, name_len)))
{
free(packet);
return STAT_BOGUS;
}
while (1)
{
unsigned char *newhash, hash[HASH_SIZE];
unsigned char c1, c2;
struct crec *crecp = cache_find_by_name(NULL, name_start, now, F_DS);
struct crec *crecp;
if (--(*keycount) == 0)
{
free(packet);
blockdata_free(block);
return STAT_BOGUS;
}
if (crecp && (crecp->flags & F_DNSSECOK))
{
free(packet);
return (crecp->flags & F_NEG) ? STAT_INSECURE : STAT_BOGUS;
while ((crecp = cache_find_by_name(NULL, name_start, now, F_DS)))
{
if ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK))
{
/* Found a secure denial of DS - delegation is indeed insecure */
free(packet);
blockdata_free(block);
return STAT_INSECURE;
}
/* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation.
Add another label and continue. */
if (name_start == name)
{
free(packet);
blockdata_free(block);
return STAT_BOGUS; /* run out of labels */
}
name_start -= 2;
while (*name_start != '.' && name_start != name)
name_start--;
if (name_start != name)
name_start++;
}
/* If we have cached insecurely that a DS doesn't exist,
ise that is a hit for where to start looking for the secure one */
if (crecp && (crecp->flags & F_NEG) && (name_start = strchr(name_start, '.')))
{
name_start++; /* chop a label off and try again */
continue;
}
/* reached the root */
if (!name_start)
{
free(packet);
return STAT_BOGUS;
}
/* Can't find it in the cache, have to send a query */
m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr);
/* We rely on the question section coming back unchanged, ensure it is with the hash. */
if ((newhash = hash_questions(header, (unsigned int)m, name)))
*length = htons(m);
if (read_write(server->tcpfd, packet, m + sizeof(u16), 0) &&
read_write(server->tcpfd, &c1, 1, 1) &&
read_write(server->tcpfd, &c2, 1, 1) &&
read_write(server->tcpfd, payload, (c1 << 8) | c2, 1))
{
memcpy(hash, newhash, HASH_SIZE);
*length = htons(m);
m = (c1 << 8) | c2;
if (read_write(server->tcpfd, packet, m + sizeof(u16), 0) &&
read_write(server->tcpfd, &c1, 1, 1) &&
read_write(server->tcpfd, &c2, 1, 1) &&
read_write(server->tcpfd, payload, (c1 << 8) | c2, 1))
/* Note this trashes all three name workspaces */
status = tcp_key_recurse(now, STAT_NEED_DS_NEG, header, m, class, name, keyname, server, keycount);
if (status == STAT_NO_DS)
{
m = (c1 << 8) | c2;
newhash = hash_questions(header, (unsigned int)m, name);
if (newhash && memcmp(hash, newhash, HASH_SIZE) == 0)
{
/* In this case only, a SERVFAIL reply allows us to continue up the tree, looking for a
suitable NSEC reply to DS queries. */
if (RCODE(header) == SERVFAIL)
status = STAT_INSECURE;
else
/* Note this trashes all three name workspaces */
status = tcp_key_recurse(now, STAT_NEED_DS_NEG, header, m, class, name, keyname, server, keycount);
/* We've found a DS which proves the bit of the DNS where the
original query is, is unsigned, so the answer is OK,
if unvalidated. */
if (status == STAT_NO_DS)
{
free(packet);
return STAT_INSECURE;
}
/* No DS, not got to DNSSEC-land yet, go up. */
if (status == STAT_INSECURE)
{
p = (unsigned char *)(header+1);
if (extract_name(header, plen, &p, name, 1, 4) &&
(name_start = strchr(name, '.')))
{
name_start++; /* chop a label off and try again */
continue;
}
}
}
/* Found a secure denial of DS - delegation is indeed insecure */
free(packet);
blockdata_free(block);
return STAT_INSECURE;
}
if (status == STAT_BOGUS)
{
free(packet);
blockdata_free(block);
return STAT_BOGUS;
}
/* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation.
Add another label and continue. */
/* Get name we're checking back. */
blockdata_retrieve(block, name_len, name);
if (name_start == name)
{
free(packet);
blockdata_free(block);
return STAT_BOGUS; /* run out of labels */
}
name_start -= 2;
while (*name_start != '.' && name_start != name)
name_start--;
if (name_start != name)
name_start++;
}
else
{
/* IO failure */
free(packet);
blockdata_free(block);
return STAT_BOGUS; /* run out of labels */
}
free(packet);
return STAT_BOGUS;
}
}
@@ -1512,14 +1557,14 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
{
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
if (status == STAT_NEED_DS && new_status == STAT_NO_DS)
new_status = STAT_INSECURE;
if (status == STAT_NEED_DS && (new_status == STAT_NO_DS || new_status == STAT_NO_NS))
new_status = STAT_BOGUS;
}
else if (status == STAT_CHASE_CNAME)
new_status = dnssec_chase_cname(now, header, n, name, keyname);
else
{
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL);
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL, NULL);
if (new_status == STAT_NO_SIG)
{
@@ -1572,14 +1617,14 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
{
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
if (status == STAT_NEED_DS && new_status == STAT_NO_DS)
new_status = STAT_INSECURE; /* Validated no DS */
if (status == STAT_NEED_DS && (new_status == STAT_NO_DS || new_status == STAT_NO_NS))
new_status = STAT_BOGUS; /* Validated no DS */
}
else if (status == STAT_CHASE_CNAME)
new_status = dnssec_chase_cname(now, header, n, name, keyname);
else
{
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL);
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL, NULL);
if (new_status == STAT_NO_SIG)
{
@@ -1957,6 +2002,7 @@ static struct frec *allocate_frec(time_t now)
f->dependent = NULL;
f->blocking_query = NULL;
f->stash = NULL;
f->orig_domain = NULL;
#endif
daemon->frec_list = f;
}
@@ -2025,6 +2071,12 @@ static void free_frec(struct frec *f)
f->stash = NULL;
}
if (f->orig_domain)
{
blockdata_free(f->orig_domain);
f->orig_domain = NULL;
}
/* Anything we're waiting on is pointless now, too */
if (f->blocking_query)
free_frec(f->blocking_query);
+2 -2
View File
@@ -15,10 +15,10 @@
*/
#include "dnsmasq.h"
#include <sys/inotify.h>
#ifdef HAVE_LINUX_NETWORK
#include <sys/inotify.h>
/* the strategy is to set a inotify on the directories containing
resolv files, for any files in the directory which are close-write
or moved into the directory.
+15 -3
View File
@@ -147,6 +147,7 @@ struct myoption {
#define LOPT_LOCAL_SERVICE 335
#define LOPT_DNSSEC_TIME 336
#define LOPT_LOOP_DETECT 337
#define LOPT_IGNORE_ADDR 338
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -181,6 +182,7 @@ static const struct myoption opts[] =
{ "local-service", 0, 0, LOPT_LOCAL_SERVICE },
{ "bogus-priv", 0, 0, 'b' },
{ "bogus-nxdomain", 1, 0, 'B' },
{ "ignore-address", 1, 0, LOPT_IGNORE_ADDR },
{ "selfmx", 0, 0, 'e' },
{ "filterwin2k", 0, 0, 'f' },
{ "pid-file", 2, 0, 'x' },
@@ -457,6 +459,7 @@ static struct {
{ LOPT_QUIET_RA, OPT_QUIET_RA, NULL, gettext_noop("Do not log RA."), NULL },
{ LOPT_LOCAL_SERVICE, OPT_LOCAL_SERVICE, NULL, gettext_noop("Accept queries only from directly-connected networks"), NULL },
{ LOPT_LOOP_DETECT, OPT_LOOP_DETECT, NULL, gettext_noop("Detect and remove DNS forwarding loops"), NULL },
{ LOPT_IGNORE_ADDR, ARG_DUP, "<ipaddr>", gettext_noop("Ignore DNS responses containing ipaddr."), NULL },
{ 0, 0, NULL, NULL, NULL }
};
@@ -2119,14 +2122,23 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
break;
case 'B': /* --bogus-nxdomain */
{
case LOPT_IGNORE_ADDR: /* --ignore-address */
{
struct in_addr addr;
unhide_metas(arg);
if (arg && (inet_pton(AF_INET, arg, &addr) > 0))
{
struct bogus_addr *baddr = opt_malloc(sizeof(struct bogus_addr));
baddr->next = daemon->bogus_addr;
daemon->bogus_addr = baddr;
if (option == 'B')
{
baddr->next = daemon->bogus_addr;
daemon->bogus_addr = baddr;
}
else
{
baddr->next = daemon->ignore_addr;
daemon->ignore_addr = baddr;
}
baddr->addr = addr;
}
else
+37
View File
@@ -1328,6 +1328,43 @@ int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
return 0;
}
int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr)
{
unsigned char *p;
int i, qtype, qclass, rdlen;
struct bogus_addr *baddrp;
/* skip over questions */
if (!(p = skip_questions(header, qlen)))
return 0; /* bad packet */
for (i = ntohs(header->ancount); i != 0; i--)
{
if (!(p = skip_name(p, header, qlen, 10)))
return 0; /* bad packet */
GETSHORT(qtype, p);
GETSHORT(qclass, p);
p += 4; /* TTL */
GETSHORT(rdlen, p);
if (qclass == C_IN && qtype == T_A)
{
if (!CHECK_LEN(header, p, qlen, INADDRSZ))
return 0;
for (baddrp = baddr; baddrp; baddrp = baddrp->next)
if (memcmp(&baddrp->addr, p, INADDRSZ) == 0)
return 1;
}
if (!ADD_RDLEN(header, p, qlen, rdlen))
return 0;
}
return 0;
}
int add_resource_record(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp,
unsigned long ttl, int *offset, unsigned short type, unsigned short class, char *format, ...)
{