diff --git a/src/auth.c b/src/auth.c index c8b89b5..93f9077 100644 --- a/src/auth.c +++ b/src/auth.c @@ -103,7 +103,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n { char *name = daemon->namebuff; unsigned char *p, *ansp; - int qtype, qclass; + int qtype, qclass, rc; int nameoffset, axfroffset = 0; int q, anscount = 0, authcount = 0; struct crec *crecp; @@ -283,11 +283,11 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } for (rec = daemon->mxnames; rec; rec = rec->next) - if (!rec->issrv && hostname_isequal(name, rec->name)) + if (!rec->issrv && (rc = hostname_issubdomain(name, rec->name))) { nxdomain = 0; - if (qtype == T_MX) + if (rc == 2 && qtype == T_MX) { found = 1; log_query(F_CONFIG | F_RRNAME, name, NULL, ""); @@ -298,11 +298,11 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } for (move = NULL, up = &daemon->mxnames, rec = daemon->mxnames; rec; rec = rec->next) - if (rec->issrv && hostname_isequal(name, rec->name)) + if (rec->issrv && (rc = hostname_issubdomain(name, rec->name))) { nxdomain = 0; - if (qtype == T_SRV) + if (rc == 2 && qtype == T_SRV) { found = 1; log_query(F_CONFIG | F_RRNAME, name, NULL, ""); @@ -333,10 +333,10 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } for (txt = daemon->rr; txt; txt = txt->next) - if (hostname_isequal(name, txt->name)) + if ((rc = hostname_issubdomain(name, txt->name))) { nxdomain = 0; - if (txt->class == qtype) + if (rc == 2 && txt->class == qtype) { found = 1; log_query(F_CONFIG | F_RRNAME, name, NULL, ""); @@ -347,10 +347,10 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } for (txt = daemon->txt; txt; txt = txt->next) - if (txt->class == C_IN && hostname_isequal(name, txt->name)) + if (txt->class == C_IN && (rc = hostname_issubdomain(name, txt->name))) { nxdomain = 0; - if (qtype == T_TXT) + if (rc == 2 && qtype == T_TXT) { found = 1; log_query(F_CONFIG | F_RRNAME, name, NULL, ""); @@ -361,10 +361,10 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } for (na = daemon->naptr; na; na = na->next) - if (hostname_isequal(name, na->name)) + if ((rc = hostname_issubdomain(name, na->name))) { nxdomain = 0; - if (qtype == T_NAPTR) + if (rc == 2 && qtype == T_NAPTR) { found = 1; log_query(F_CONFIG | F_RRNAME, name, NULL, ""); @@ -384,13 +384,13 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n #endif for (intr = daemon->int_names; intr; intr = intr->next) - if (hostname_isequal(name, intr->name)) + if ((rc = hostname_issubdomain(name, intr->name))) { struct addrlist *addrlist; nxdomain = 0; - if (flag) + if (rc == 2 && flag) for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) if (((addrlist->flags & ADDRLIST_IPV6) ? T_AAAA : T_A) == qtype && (local_query || filter_zone(zone, flag, &addrlist->addr))) @@ -567,6 +567,8 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n goto cname_restart; } + else if (cache_find_non_terminal(name, now)) + nxdomain = 0; log_query(flag | F_NEG | (nxdomain ? F_NXDOMAIN : 0) | F_FORWARD | F_AUTH, name, NULL, NULL); } diff --git a/src/cache.c b/src/cache.c index 9e0923d..e64335a 100644 --- a/src/cache.c +++ b/src/cache.c @@ -25,6 +25,8 @@ static int insert_error; static union bigname *big_free = NULL; static int bignames_left, hash_size; +static void make_non_terminals(struct crec *source); + /* type->string mapping: this is also used by the name-hash function as a mixing table. */ static const struct { unsigned int type; @@ -648,6 +650,20 @@ void cache_end_insert(void) new_chain = NULL; } +int cache_find_non_terminal(char *name, time_t now) +{ + struct crec *crecp; + + for (crecp = *hash_bucket(name); crecp; crecp = crecp->hash_next) + if (!is_outdated_cname_pointer(crecp) && + !is_expired(now, crecp) && + (crecp->flags & F_FORWARD) && + hostname_isequal(name, cache_get_name(crecp))) + return 1; + + return 0; +} + struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned int prot) { struct crec *ans; @@ -818,6 +834,8 @@ static void add_hosts_cname(struct crec *target) crec->addr.cname.uid = target->uid; crec->uid = UID_NONE; cache_hash(crec); + make_non_terminals(crec); + add_hosts_cname(crec); /* handle chains */ } } @@ -889,6 +907,7 @@ static void add_hosts_entry(struct crec *cache, struct all_addr *addr, int addrl cache->uid = index; memcpy(&cache->addr.addr, addr, addrlen); cache_hash(cache); + make_non_terminals(cache); /* don't need to do alias stuff for second and subsequent addresses. */ if (!nameexists) @@ -1102,6 +1121,7 @@ void cache_reload(void) cache->addr.cname.uid = SRC_INTERFACE; cache->uid = UID_NONE; cache_hash(cache); + make_non_terminals(cache); add_hosts_cname(cache); /* handle chains */ } @@ -1119,6 +1139,7 @@ void cache_reload(void) cache->addr.ds.digest = ds->digest_type; cache->uid = ds->class; cache_hash(cache); + make_non_terminals(cache); } #endif @@ -1234,6 +1255,7 @@ static void add_dhcp_cname(struct crec *target, time_t ttd) aliasc->addr.cname.uid = target->uid; aliasc->uid = UID_NONE; cache_hash(aliasc); + make_non_terminals(aliasc); add_dhcp_cname(aliasc, ttd); } } @@ -1322,12 +1344,104 @@ void cache_add_dhcp_entry(char *host_name, int prot, crec->name.namep = host_name; crec->uid = UID_NONE; cache_hash(crec); + make_non_terminals(crec); add_dhcp_cname(crec, ttd); } } #endif +/* Called when we put a local or DHCP name into the cache. + Creates empty cache entries for subnames (ie, + for three.two.one, for two.one and one), without + F_IPV4 or F_IPV6 or F_CNAME set. These convert + NXDOMAIN answers to NoData ones. */ +static void make_non_terminals(struct crec *source) +{ + char *name = cache_get_name(source); + struct crec* crecp, *tmp, **up; + int type = F_HOSTS | F_CONFIG; +#ifdef HAVE_DHCP + if (source->flags & F_DHCP) + type = F_DHCP; +#endif + + /* First delete any empty entries for our new real name. Note that + we only delete empty entries deriving from DHCP for a new DHCP-derived + entry and vice-versa for HOSTS and CONFIG. This ensures that + non-terminals from DHCP go when we reload DHCP and + for HOSTS/CONFIG when we re-read. */ + for (up = hash_bucket(name), crecp = *up; crecp; crecp = tmp) + { + tmp = crecp->hash_next; + + if (!is_outdated_cname_pointer(crecp) && + (crecp->flags & F_FORWARD) && + (crecp->flags & type) && + !(crecp->flags & (F_IPV4 | F_IPV6 | F_CNAME)) && + hostname_isequal(name, cache_get_name(crecp))) + { + *up = crecp->hash_next; +#ifdef HAVE_DHCP + if (type & F_DHCP) + { + crecp->next = dhcp_spare; + dhcp_spare = crecp; + } + else +#endif + free(crecp); + break; + } + else + up = &crecp->hash_next; + } + + while ((name = strchr(name, '.'))) + { + name++; + + /* Look for one existing, don't need another */ + for (crecp = *hash_bucket(name); crecp; crecp = crecp->hash_next) + if (!is_outdated_cname_pointer(crecp) && + (crecp->flags & F_FORWARD) && + (crecp->flags & type) && + hostname_isequal(name, cache_get_name(crecp))) + break; + + if (crecp) + { + /* If the new name expires later, transfer that time to + empty non-terminal entry. */ + if (!(crecp->flags & F_IMMORTAL)) + { + if (source->flags & F_IMMORTAL) + crecp->flags |= F_IMMORTAL; + else if (difftime(crecp->ttd, source->ttd) < 0) + crecp->ttd = source->ttd; + } + continue; + } + +#ifdef HAVE_DHCP + if ((source->flags & F_DHCP) && dhcp_spare) + { + crecp = dhcp_spare; + dhcp_spare = dhcp_spare->next; + } + else +#endif + crecp = whine_malloc(sizeof(struct crec)); + + *crecp = *source; + crecp->flags &= ~(F_IPV4 | F_IPV6 | F_CNAME | F_REVERSE); + crecp->flags |= F_NAMEP; + crecp->name.namep = name; + + cache_hash(crecp); + } +} + #ifndef NO_ID int cache_make_stat(struct txt_record *t) { @@ -1443,7 +1557,6 @@ static char *sanitise(char *name) void dump_cache(time_t now) { struct server *serv, *serv1; - char *t = ""; my_syslog(LOG_INFO, _("time %lu"), (unsigned long)now); my_syslog(LOG_INFO, _("cache size %d, %d/%d cache insertions re-used unexpired cache entries."), @@ -1489,6 +1602,7 @@ void dump_cache(time_t now) for (i=0; ihash_next) { + char *t = " "; char *a = daemon->addrbuff, *p = daemon->namebuff, *n = cache_get_name(cache); *a = 0; if (strlen(n) == 0 && !(cache->flags & F_REVERSE)) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 201fc80..712fe56 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1135,6 +1135,7 @@ void next_uid(struct crec *crecp); void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg); char *record_source(unsigned int index); char *querystr(char *desc, unsigned short type); +int cache_find_non_terminal(char *name, time_t now); struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr, time_t now, unsigned int prot); @@ -1250,6 +1251,7 @@ void *whine_malloc(size_t size); int sa_len(union mysockaddr *addr); int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2); int hostname_isequal(const char *a, const char *b); +int hostname_issubdomain(char *a, char *b); time_t dnsmasq_time(void); int netmask_length(struct in_addr mask); int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask); diff --git a/src/rfc1035.c b/src/rfc1035.c index e179865..67f8fa9 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -986,40 +986,35 @@ size_t setup_reply(struct dns_header *header, size_t qlen, /* check if name matches local names ie from /etc/hosts or DHCP or local mx names. */ int check_for_local_domain(char *name, time_t now) { - struct crec *crecp; struct mx_srv_record *mx; struct txt_record *txt; struct interface_name *intr; struct ptr_record *ptr; struct naptr *naptr; - /* Note: the call to cache_find_by_name is intended to find any record which matches - ie A, AAAA, CNAME. */ - - if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME | F_NO_RR)) && - (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) - return 1; - for (naptr = daemon->naptr; naptr; naptr = naptr->next) - if (hostname_isequal(name, naptr->name)) + if (hostname_issubdomain(name, naptr->name)) return 1; for (mx = daemon->mxnames; mx; mx = mx->next) - if (hostname_isequal(name, mx->name)) + if (hostname_issubdomain(name, mx->name)) return 1; for (txt = daemon->txt; txt; txt = txt->next) - if (hostname_isequal(name, txt->name)) + if (hostname_issubdomain(name, txt->name)) return 1; for (intr = daemon->int_names; intr; intr = intr->next) - if (hostname_isequal(name, intr->name)) + if (hostname_issubdomain(name, intr->name)) return 1; for (ptr = daemon->ptr; ptr; ptr = ptr->next) - if (hostname_isequal(name, ptr->name)) + if (hostname_issubdomain(name, ptr->name)) return 1; - + + if (cache_find_non_terminal(name, now)) + return 1; + return 0; } diff --git a/src/util.c b/src/util.c index 532bc16..b27d50e 100644 --- a/src/util.c +++ b/src/util.c @@ -355,6 +355,44 @@ int hostname_isequal(const char *a, const char *b) return 1; } +/* is b equal to or a subdomain of a return 2 for equal, 1 for subdomain */ +int hostname_issubdomain(char *a, char *b) +{ + char *ap, *bp; + unsigned int c1, c2; + + /* move to the end */ + for (ap = a; *ap; ap++); + for (bp = b; *bp; bp++); + + /* a shorter than b or a empty. */ + if ((bp - b) < (ap - a) || ap == a) + return 0; + + do + { + c1 = (unsigned char) *(--ap); + c2 = (unsigned char) *(--bp); + + if (c1 >= 'A' && c1 <= 'Z') + c1 += 'a' - 'A'; + if (c2 >= 'A' && c2 <= 'Z') + c2 += 'a' - 'A'; + + if (c1 != c2) + return 0; + } while (ap != a); + + if (bp == b) + return 2; + + if (*(--bp) == '.') + return 1; + + return 0; +} + + time_t dnsmasq_time(void) { #ifdef HAVE_BROKEN_RTC