--- djbdns-1.05-original/query.c Sun Feb 11 21:11:45 2001 +++ djbdns-1.05/query.c Wed Mar 26 15:48:20 2003 @@ -91,6 +91,21 @@ } } +static int move_name_to_alias(struct query *z,uint32 ttl) +{ + int j ; + + if (z->alias[QUERY_MAXALIAS - 1]) return 0 ; + for (j = QUERY_MAXALIAS - 1;j > 0;--j) + z->alias[j] = z->alias[j - 1]; + for (j = QUERY_MAXALIAS - 1;j > 0;--j) + z->aliasttl[j] = z->aliasttl[j - 1]; + z->alias[0] = z->name[0]; + z->aliasttl[0] = ttl; + z->name[0] = 0; + return 1 ; +} + static int rqa(struct query *z) { int i; @@ -123,7 +138,6 @@ static char *t1 = 0; static char *t2 = 0; static char *t3 = 0; -static char *cname = 0; static char *referral = 0; static unsigned int *records = 0; @@ -179,15 +193,14 @@ uint16 datalen; char *control; char *d; + char *owner_name = 0 ; const char *dtype; unsigned int dlen; int flagout; - int flagcname; int flagreferral; int flagsoa; uint32 ttl; uint32 soattl; - uint32 cnamettl; int i; int j; int k; @@ -252,7 +265,10 @@ byte_copy(key,2,DNS_T_CNAME); cached = cache_get(key,dlen + 2,&cachedlen,&ttl); - if (cached) { + /* A previous explicit query might have caused an empty RRSet to have been + ** cached. Take care to ignore such a thing. + */ + if (cached && cachedlen) { if (typematch(DNS_T_CNAME,dtype)) { log_cachedanswer(d,DNS_T_CNAME); if (!rqa(z)) goto DIE; @@ -261,8 +277,11 @@ return 1; } log_cachedcname(d,cached); - if (!dns_domain_copy(&cname,cached)) goto DIE; - goto CNAME; + if (!z->level) { + if (!move_name_to_alias(z,ttl)) goto DIE ; + } + if (!dns_domain_copy(&z->name[z->level],cached)) goto DIE; + goto NEWNAME; } if (typematch(DNS_T_NS,dtype)) { @@ -351,7 +370,7 @@ } } - if (!typematch(DNS_T_ANY,dtype) && !typematch(DNS_T_AXFR,dtype) && !typematch(DNS_T_CNAME,dtype) && !typematch(DNS_T_NS,dtype) && !typematch(DNS_T_PTR,dtype) && !typematch(DNS_T_A,dtype) && !typematch(DNS_T_MX,dtype)) { + if (!typematch(DNS_T_ANY,dtype) && !typematch(DNS_T_AXFR,dtype) && !typematch(DNS_T_NS,dtype) && !typematch(DNS_T_PTR,dtype) && !typematch(DNS_T_A,dtype) && !typematch(DNS_T_MX,dtype)) { byte_copy(key,2,dtype); cached = cache_get(key,dlen + 2,&cachedlen,&ttl); if (cached && (cachedlen || byte_diff(dtype,2,DNS_T_ANY))) { @@ -471,29 +490,31 @@ if (rcode && (rcode != 3)) goto DIE; /* impossible; see irrelevant() */ flagout = 0; - flagcname = 0; flagreferral = 0; flagsoa = 0; soattl = 0; - cnamettl = 0; + if (!dns_domain_copy(&owner_name,d)) goto DIE; + /* This code assumes that the CNAME chain is presented in the correct + ** order. The example algorithm in RFC 1034 will actually result in this + ** being the case, but the words do not require it to be so. + */ for (j = 0;j < numanswers;++j) { pos = dns_packet_getname(buf,len,pos,&t1); if (!pos) goto DIE; pos = dns_packet_copy(buf,len,pos,header,10); if (!pos) goto DIE; - if (dns_domain_equal(t1,d)) + if (dns_domain_equal(t1,owner_name)) if (byte_equal(header + 2,2,DNS_C_IN)) { /* should always be true */ if (typematch(header,dtype)) flagout = 1; else if (typematch(header,DNS_T_CNAME)) { - if (!dns_packet_getname(buf,len,pos,&cname)) goto DIE; - flagcname = 1; - cnamettl = ttlget(header + 4); + if (!dns_packet_getname(buf,len,pos,&owner_name)) goto DIE; } } uint16_unpack_big(header + 8,&datalen); pos += datalen; } + dns_domain_free(&owner_name) ; posauthority = pos; for (j = 0;j < numauthority;++j) { @@ -515,15 +536,6 @@ } posglue = pos; - - if (!flagcname && !rcode && !flagout && flagreferral && !flagsoa) - if (dns_domain_equal(referral,control) || !dns_domain_suffix(referral,control)) { - log_lame(whichserver,control,referral); - byte_zero(whichserver,4); - goto HAVENS; - } - - if (records) { alloc_free(records); records = 0; } k = numanswers + numauthority + numglue; @@ -670,24 +682,36 @@ alloc_free(records); records = 0; + if (byte_diff(DNS_T_CNAME,2,dtype)) { + /* This code assumes that the CNAME chain is presented in the correct + ** order. The example algorithm in RFC 1034 will actually result in this + ** being the case, but the words do not require it to be so. + */ + pos = posanswers; + for (j = 0;j < numanswers;++j) { + pos = dns_packet_getname(buf,len,pos,&t1); if (!pos) goto DIE; + pos = dns_packet_copy(buf,len,pos,header,10); if (!pos) goto DIE; + + if (dns_domain_equal(t1,d)) + if (byte_equal(header + 2,2,DNS_C_IN)) { /* should always be true */ + if (typematch(header,DNS_T_CNAME)) { + ttl = ttlget(header + 4); + if (z->level == 0) { + if (!move_name_to_alias(z,ttl)) goto DIE ; + } + if (!dns_packet_getname(buf,len,pos,&z->name[z->level])) goto DIE; + d = z->name[z->level]; + if (!dns_domain_suffix(d,control) || !roots_same(d,control)) + goto NEWNAME ; /* Cannot trust the chain further - restart using current name */ + } + } - if (flagcname) { - ttl = cnamettl; - CNAME: - if (!z->level) { - if (z->alias[QUERY_MAXALIAS - 1]) goto DIE; - for (j = QUERY_MAXALIAS - 1;j > 0;--j) - z->alias[j] = z->alias[j - 1]; - for (j = QUERY_MAXALIAS - 1;j > 0;--j) - z->aliasttl[j] = z->aliasttl[j - 1]; - z->alias[0] = z->name[0]; - z->aliasttl[0] = ttl; - z->name[0] = 0; + uint16_unpack_big(header + 8,&datalen); + pos += datalen; } - if (!dns_domain_copy(&z->name[z->level],cname)) goto DIE; - goto NEWNAME; } + /* A "no such name" error applies to the end of any CNAME chain, not to the start. */ if (rcode == 3) { log_nxdomain(whichserver,d,soattl); cachegeneric(DNS_T_ANY,d,"",0,soattl); @@ -700,10 +724,26 @@ return 1; } + /* We check for a lame server _after_ we have cached any records that it + ** might have returned to us. This copes better with the incorrect + ** behaviour of one content DNS server software that doesn't return + ** complete CNAME chains but instead returns only the first link in a + ** chain followed by a lame delegation to the same server. + ** Also: We check for a lame server _after_ following the CNAME chain. The + ** delegation in a referral answer applies to the _end_ of the chain, not + ** to the beginning. + */ + if (!rcode && !flagout && flagreferral && !flagsoa) + if (dns_domain_equal(referral,control) || !dns_domain_suffix(referral,control)) { + log_lame(whichserver,control,referral); + byte_zero(whichserver,4); + goto HAVENS; + } + if (!flagout && flagsoa) + /* Don't save empty RRSets for those types that we use as special markers. */ if (byte_diff(DNS_T_ANY,2,dtype)) - if (byte_diff(DNS_T_AXFR,2,dtype)) - if (byte_diff(DNS_T_CNAME,2,dtype)) { + if (byte_diff(DNS_T_AXFR,2,dtype)) { save_start(); save_finish(dtype,d,soattl); log_nodata(whichserver,d,dtype,soattl); @@ -815,6 +855,7 @@ DIE: cleanup(z); if (records) { alloc_free(records); records = 0; } + dns_domain_free(&owner_name) ; return -1; }