Package: pdns-server
Version: 2.9.21.2-1
Severity: important

There is a bug in 2.9.21 releases that makes the server return wildcard
entries even if the zone has a valid entry for the queried name but
lacks the matching type.

This usually presents itself with AAAA queries which might not match the
actual zone entry. In such a case the wildcard cname is returned and
cached on the receiving end.

The bug is also reported in the upstream bugtracker as bug #125 and the
fix is to apply changesets 1081 and 1147. I'm attaching a dpatch file
that fixes the problem.

The bug is a regression from etch and should be pushed to lenny updates.

-- System Information:
Debian Release: 5.0
  APT prefers stable
  APT policy: (500, 'stable')
Architecture: i386 (i686)

Kernel: Linux 2.6.26-1-xen-686 (SMP w/1 CPU core)
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/bash

Versions of packages pdns-server depends on:
ii  adduser                3.110             add and remove users and groups
ii  debconf [debconf-2.0]  1.5.24            Debian configuration management sy
ii  libc6                  2.7-18            GNU C Library: Shared libraries
ii  libgcc1                1:4.3.2-1.1       GCC support library
ii  libstdc++6             4.3.2-1.1         The GNU Standard C++ Library v3
ii  ucf                    3.0016            Update Configuration File: preserv
ii  zlib1g                 1:1.2.3.3.dfsg-12 compression library - runtime

Versions of packages pdns-server recommends:
ii  pdns-doc                      2.9.21.2-1 PowerDNS manual

Versions of packages pdns-server suggests:
ii  pdns-backend-sqlite [pdns-bac 2.9.21.2-1 sqlite backend for PowerDNS
ii  pdns-recursor                 3.1.7-1    PowerDNS recursor

-- debconf information excluded
#! /bin/sh /usr/share/dpatch/dpatch-run
## wildcard-fix.dpatch by  <Sami Haahtinen <re...@ressukka.net>>
##
## All lines beginning with `## DP:' are a description of the patch.
## DP: Combined patch from changesets 1081 and 1147 to fix wildcard resolving

@DPATCH@
diff -urNad pdns-2.9.21.2~/pdns/packethandler.cc pdns-2.9.21.2/pdns/packethandler.cc
--- pdns-2.9.21.2~/pdns/packethandler.cc	2008-11-16 16:05:06.000000000 +0200
+++ pdns-2.9.21.2/pdns/packethandler.cc	2009-03-30 09:15:36.000000000 +0300
@@ -246,6 +246,7 @@
 
   string subdomain=target;
   string::size_type pos;
+
   while((pos=subdomain.find("."))!=string::npos) {
     subdomain=subdomain.substr(pos+1);
     // DLOG();
@@ -281,10 +282,11 @@
       }
     }
     if(found) {
-      DLOG(L<<"Wildcard match on '"<<string("*.")+subdomain<<"'"<<endl);
+      DLOG(L<<"Wildcard match on '"<<string("*.")+subdomain<<"'"<<", retargeted="<<retargeted<<endl);
       return retargeted ? 2 : 1;
     }
   }
+  DLOG(L<<"Returning no hit for '"<<string("*.")+subdomain<<"'"<<endl);
   return 0;
 }
 
@@ -361,8 +363,12 @@
         
     bool shortcut=p->qtype.getCode()!=QType::SOA && p->qtype.getCode()!=QType::ANY;
     int hits=0;
+    bool relevantNS=false;
 
     while(B.get(rr)) {
+      if(rr.qtype.getCode() == QType::NS && p->qtype.getCode() != QType::NS) { // possible retargeting
+	relevantNS=true;
+      }
       if(rr.qtype.getCode()!=QType::NS || p->qtype.getCode()==QType::NS)
 	hits++;
       if(!rfound && rr.qtype.getCode()==QType::CNAME) {
@@ -375,8 +381,10 @@
 	r->addRecord(rr);
       }
     }
-    if(hits && !found && !rfound && shortcut ) // we found matching qnames but not a qtype
+    if(hits && !relevantNS && !found && !rfound && shortcut ) { // we found matching qnames but not a qtype
+      DLOG(L<<"Found matching qname, but not the qtype"<<endl);
       return 2;
+    }
 
     if(rfound)
       return 1; // ANY lookup found the right answer immediately
@@ -620,6 +628,7 @@
     mret=makeCanonic(p, r, target); // traverse CNAME chain until we have a useful record (may actually give the correct answer!)
 
     if(mret==2) { // there is some data, but not of the correct type
+      r->clearRecords();
       DLOG(L<<"There is some data, but not of the correct type, adding SOA for NXRECORDSET"<<endl);
       SOAData sd;
       if(getAuth(p, &sd, target, 0)) {
@@ -630,6 +639,8 @@
 	rr.domain_id=sd.domain_id;
 	rr.d_place=DNSResourceRecord::AUTHORITY;
 	r->addRecord(rr);
+	if(mret == 2)
+	  goto sendit;
       }
     }
 
@@ -662,7 +673,6 @@
 	}
       }
     }
-
     noSameLevelNS=true;
 
     if(p->qtype.getCode()!=QType::SOA) { // regular direct lookup
@@ -699,7 +709,7 @@
     
     // not found yet, try wildcards (we only try here in case of recursion - we should check before we hand off)
 
-    if(p->d.rd && d_doRecursion && d_doWildcards) { 
+    if(mret != 2 && p->d.rd && d_doRecursion && d_doWildcards) { 
       int res=doWildcardRecords(p,r,target);
       if(res) { // had a result
 	// FIXME: wildCard may retarget us in the future
diff -urNad pdns-2.9.21.2~/pdns/packethandler.cc.orig pdns-2.9.21.2/pdns/packethandler.cc.orig
--- pdns-2.9.21.2~/pdns/packethandler.cc.orig	1970-01-01 02:00:00.000000000 +0200
+++ pdns-2.9.21.2/pdns/packethandler.cc.orig	2009-03-30 09:14:23.000000000 +0300
@@ -0,0 +1,875 @@
+ /*
+    PowerDNS Versatile Database Driven Nameserver
+    Copyright (C) 2002-2007  PowerDNS.COM BV
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License version 2 as 
+    published by the Free Software Foundation
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+#include "utility.hh"
+#include <string>
+#include <sys/types.h>
+#include <boost/algorithm/string.hpp>
+#include "dns.hh"
+#include "dnsbackend.hh"
+#include "ueberbackend.hh"
+#include "dnspacket.hh"
+#include "nameserver.hh"
+#include "distributor.hh"
+#include "logger.hh"
+#include "arguments.hh"
+#include "packethandler.hh"
+#include "statbag.hh"
+#include "resolver.hh"
+#include "communicator.hh"
+#include "dnsproxy.hh"
+
+extern StatBag S;
+extern PacketCache PC;  
+extern CommunicatorClass Communicator;
+extern DNSProxy *DP;
+
+int PacketHandler::s_count;
+extern string s_programname;
+
+PacketHandler::PacketHandler():B(s_programname)
+{
+  s_count++;
+  d_doFancyRecords = (arg()["fancy-records"]!="no");
+  d_doWildcards = (arg()["wildcards"]!="no");
+  d_doCNAME = (arg()["skip-cname"]=="no");
+  d_doRecursion= arg().mustDo("recursor");
+  d_logDNSDetails= arg().mustDo("log-dns-details");
+  d_doIPv6AdditionalProcessing = arg().mustDo("do-ipv6-additional-processing");
+}
+
+DNSBackend *PacketHandler::getBackend()
+{
+  return &B;
+}
+
+PacketHandler::~PacketHandler()
+{
+  --s_count;
+  DLOG(L<<Logger::Error<<"PacketHandler destructor called - "<<s_count<<" left"<<endl);
+}
+
+void PacketHandler::addRootReferral(DNSPacket* r)
+{  
+  // nobody reads what we output, but it appears to be the magic that shuts some nameservers up
+  static char*ips[]={"198.41.0.4", "192.228.79.201", "192.33.4.12", "128.8.10.90", "192.203.230.10", "192.5.5.241", "192.112.36.4", "128.63.2.53", 
+		     "192.36.148.17","192.58.128.30", "193.0.14.129", "198.32.64.12", "202.12.27.33"};
+  static char templ[40];
+  strncpy(templ,"a.root-servers.net", sizeof(templ) - 1);
+
+  // add . NS records
+  DNSResourceRecord rr;
+  rr.qtype=QType::NS;
+  rr.ttl=518400;
+  rr.d_place=DNSResourceRecord::AUTHORITY;
+  
+  for(char c='a';c<='m';++c) {
+    *templ=c;
+    rr.content=templ;
+    r->addRecord(rr);
+  }
+
+  if(boost::iequals(arg()["send-root-referral"], "lean"))
+     return;
+
+  // add the additional stuff
+  
+  rr.ttl=3600000;
+  rr.qtype=QType::A;
+  rr.d_place=DNSResourceRecord::ADDITIONAL;
+
+  for(char c='a';c<='m';++c) {
+    *templ=c;
+    rr.qname=templ;
+    rr.content=ips[c-'a'];
+    r->addRecord(rr);
+  }
+}
+
+int PacketHandler::findMboxFW(DNSPacket *p, DNSPacket *r, string &target)
+{
+  DNSResourceRecord rr;
+  bool wedoforward=false;
+
+  SOAData sd;
+  int zoneId;
+  if(!getAuth(p, &sd, target, &zoneId))
+    return false;
+
+  B.lookup("MBOXFW",string("%@")+target,p, zoneId);
+      
+  while(B.get(rr))
+    wedoforward=true;
+
+  if(wedoforward) {
+    r->clearRecords();
+    rr.content=arg()["smtpredirector"];
+    rr.priority=25;
+    rr.ttl=7200;
+    rr.qtype=QType::MX;
+    rr.qname=target;
+    
+    r->addRecord(rr);
+  }
+
+  return wedoforward;
+}
+
+int PacketHandler::findUrl(DNSPacket *p, DNSPacket *r, string &target)
+{
+  DNSResourceRecord rr;
+
+  bool found=false;
+      
+  B.lookup("URL",target,p); // search for a URL before we search for an A
+        
+  while(B.get(rr)) {
+    if(!found) 
+      r->clearRecords();
+    found=true;
+    DLOG(L << "Found a URL!" << endl);
+    rr.content=arg()["urlredirector"];
+    rr.qtype=QType::A; 
+    rr.qname=target;
+	  
+    r->addRecord(rr);
+  }  
+
+  if(found) 
+    return 1;
+
+  // now try CURL
+  
+  B.lookup("CURL",target,p); // search for a URL before we search for an A
+      
+  while(B.get(rr)) {
+    if(!found) 
+      r->clearRecords();
+    found=true;
+    DLOG(L << "Found a CURL!" << endl);
+    rr.content=arg()["urlredirector"];
+    rr.qtype=1; // A
+    rr.qname=target;
+    rr.ttl=300;
+    r->addRecord(rr);
+  }  
+
+  if(found)
+    return found;
+  return 0;
+}
+
+/** Returns 0 if nothing was found, -1 if an error occured or 1 if the search
+    was satisfied */
+int PacketHandler::doFancyRecords(DNSPacket *p, DNSPacket *r, string &target)
+{
+  DNSResourceRecord rr;
+
+  if(p->qtype.getCode()==QType::MX)  // check if this domain has smtp service from us
+    return findMboxFW(p,r,target);
+  
+  if(p->qtype.getCode()==QType::A)   // search for a URL record for an A
+    return findUrl(p,r,target);
+
+  return 0;
+}
+
+/** This catches version requests. Returns 1 if it was handled, 0 if it wasn't */
+int PacketHandler::doVersionRequest(DNSPacket *p, DNSPacket *r, string &target)
+{
+  DNSResourceRecord rr;
+  
+  // modes: anonymous, powerdns only, full, spoofed
+  const string mode=arg()["version-string"];
+  if(p->qtype.getCode()==QType::TXT && target=="version.bind") {// TXT
+    if(mode.empty() || mode=="full") 
+      rr.content="Served by POWERDNS "VERSION" $Id: packethandler.cc 1036 2007-04-19 20:43:14Z ahu $";
+    else if(mode=="anonymous") {
+      r->setRcode(RCode::ServFail);
+      return 1;
+    }
+    else if(mode=="powerdns")
+      rr.content="Served by PowerDNS - http://www.powerdns.com";;
+    else 
+      rr.content=mode;
+
+    rr.ttl=5;
+    rr.qname=target;
+    rr.qtype=QType::TXT; // TXT
+    r->addRecord(rr);
+    
+    return 1;
+  }
+  return 0;
+}
+
+/** Determines if we are authoritative for a zone, and at what level */
+bool PacketHandler::getAuth(DNSPacket *p, SOAData *sd, const string &target, int *zoneId)
+{
+  string subdomain(target);
+  do {
+    if( B.getSOA( subdomain, *sd, p ) ) {
+      sd->qname = subdomain;
+      if(zoneId)
+	*zoneId = sd->domain_id;
+      return true;
+    }
+  }
+  while( chopOff( subdomain ) );   // 'www.powerdns.org' -> 'powerdns.org' -> 'org' -> ''
+  return false;
+}
+
+/** returns 1 in case of a straight match, 2 in case of a wildcard CNAME (groan), 0 in case of no hit */
+int PacketHandler::doWildcardRecords(DNSPacket *p, DNSPacket *r, string &target)
+{
+  DNSResourceRecord rr;
+  bool found=false, retargeted=false;
+
+  // try chopping off domains and look for wildcard matches
+
+  // *.pietje.nl IN  A 1.2.3.4
+  // pietje.nl should now NOT match, but www.pietje.nl should
+
+  string subdomain=target;
+  string::size_type pos;
+  while((pos=subdomain.find("."))!=string::npos) {
+    subdomain=subdomain.substr(pos+1);
+    // DLOG();
+
+    string searchstr=string("*.")+subdomain;
+
+    B.lookup(QType(QType::ANY), searchstr,p); // start our search at the backend
+
+    while(B.get(rr)) { // read results
+      found=true;
+      if((p->qtype.getCode()==QType::ANY || rr.qtype==p->qtype) || rr.qtype.getCode()==QType::CNAME) {
+	rr.qname=target;
+	r->addRecord(rr);  // and add
+	if(rr.qtype.getCode()==QType::CNAME) {
+	  if(target==rr.content) {
+	    L<<Logger::Error<<"Ignoring wildcard CNAME '"<<rr.qname<<"' pointing at itself"<<endl;
+	    r->setRcode(RCode::ServFail);
+	    continue;
+	  }
+	  
+	  DLOG(L<<Logger::Error<<"Retargeting because of wildcard cname, from "<<target<<" to "<<rr.content<<endl);
+	  
+	  target=rr.content; // retarget 
+	  retargeted=true;
+	}
+      }
+      else if(d_doFancyRecords && arg().mustDo("wildcard-url") && p->qtype.getCode()==QType::A && rr.qtype.getName()=="URL") {
+	rr.content=arg()["urlredirector"];
+	rr.qtype=QType::A; 
+	rr.qname=target;
+	
+	r->addRecord(rr);
+      }
+    }
+    if(found) {
+      DLOG(L<<"Wildcard match on '"<<string("*.")+subdomain<<"'"<<endl);
+      return retargeted ? 2 : 1;
+    }
+  }
+  return 0;
+}
+
+/** dangling is declared true if we were unable to resolve everything */
+int PacketHandler::doAdditionalProcessingAndDropAA(DNSPacket *p, DNSPacket *r)
+{
+  DNSResourceRecord rr;
+  SOAData sd;
+
+  if(p->qtype.getCode()!=QType::AXFR) { // this packet needs additional processing
+    vector<DNSResourceRecord *> arrs=r->getAPRecords();
+    if(arrs.empty()) 
+      return 1;
+
+    DLOG(L<<Logger::Warning<<"This packet needs additional processing!"<<endl);
+
+    vector<DNSResourceRecord> crrs;
+
+    for(vector<DNSResourceRecord *>::const_iterator i=arrs.begin();
+	i!=arrs.end();	++i) 
+      crrs.push_back(**i);
+
+    // we now have a copy, push_back on packet might reallocate!
+
+    for(vector<DNSResourceRecord>::const_iterator i=crrs.begin();
+	i!=crrs.end();
+	++i) {
+      
+      if(!i->qname.empty() && i->qtype.getCode()==QType::NS && !B.getSOA(i->qname,sd,p)) { // drop AA in case of non-SOA-level NS answer, except for root referral
+	r->d.aa=false;
+	//	i->d_place=DNSResourceRecord::AUTHORITY; // XXX FIXME
+      }
+
+      QType qtypes[2];
+      qtypes[0]="A"; qtypes[1]="AAAA";
+      for(int n=0 ; n < d_doIPv6AdditionalProcessing + 1; ++n) {
+	B.lookup(qtypes[n],i->content,p);  
+	bool foundOne=false;
+	while(B.get(rr)) {
+	  foundOne=true;
+	  if(rr.domain_id!=i->domain_id && arg()["out-of-zone-additional-processing"]=="no") {
+	    DLOG(L<<Logger::Warning<<"Not including out-of-zone additional processing of "<<i->qname<<" ("<<rr.qname<<")"<<endl);
+	    continue; // not adding out-of-zone additional data
+	  }
+	  
+	  rr.d_place=DNSResourceRecord::ADDITIONAL;
+	  r->addRecord(rr);
+	}
+      }
+    }
+  }
+  return 1;
+}
+
+/* returns 1 if everything is done & ready, 0 if the search should continue, 2 if a 'NO-ERROR' response should be generated */
+int PacketHandler::makeCanonic(DNSPacket *p, DNSPacket *r, string &target)
+{
+  DNSResourceRecord rr;
+
+  bool found=false, rfound=false;
+
+  if(p->qtype.getCode()!=QType::CNAME && !d_doCNAME)
+    return 0;
+
+  // Traverse a CNAME chain if needed
+  for(int numloops=0;;numloops++) {
+    if(numloops==10) {
+      L<<Logger::Error<<"Detected a CNAME loop involving "<<target<<", sending SERVFAIL"<<endl;
+      r->setRcode(2);
+      return 1;
+    }
+
+    B.lookup(QType(QType::ANY),target,p);
+        
+    bool shortcut=p->qtype.getCode()!=QType::SOA && p->qtype.getCode()!=QType::ANY;
+    int hits=0;
+
+    while(B.get(rr)) {
+      if(rr.qtype.getCode()!=QType::NS || p->qtype.getCode()==QType::NS)
+	hits++;
+      if(!rfound && rr.qtype.getCode()==QType::CNAME) {
+	found=true;
+	r->addRecord(rr);
+	target=rr.content; // for retargeting
+      }
+      if(shortcut && !found && rr.qtype==p->qtype) {
+	rfound=true;
+	r->addRecord(rr);
+      }
+    }
+    if(hits && !found && !rfound && shortcut ) { // we found matching qnames but not a qtype
+      DLOG(L<<"Found matching qname, but not the qtype"<<endl);
+      return 2;
+    }
+
+    if(rfound)
+      return 1; // ANY lookup found the right answer immediately
+
+    if(found) {
+      if(p->qtype.getCode()==QType::CNAME) // they really wanted a CNAME!
+	return 1;
+      DLOG(L<<"Looping because of a CNAME to "<<target<<endl);
+      found=false;
+    }
+    else 
+      break;
+  }
+
+  // we now have what we really search for ready in 'target'
+  return 0;
+}
+
+/* Semantics:
+   
+- only one backend owns the SOA of a zone
+- only one AXFR per zone at a time - double startTransaction should fail
+- backends need to implement transaction semantics
+
+
+How BindBackend would implement this:
+   startTransaction makes a file 
+   feedRecord sends everything to that file 
+   commitTransaction moves that file atomically over the regular file, and triggers a reload
+   rollbackTransaction removes the file
+
+
+How PostgreSQLBackend would implement this:
+   startTransaction starts a sql transaction, which also deletes all records
+   feedRecord is an insert statement
+   commitTransaction commits the transaction
+   rollbackTransaction aborts it
+
+How MySQLBackend would implement this:
+   (good question!)
+   
+*/     
+
+int PacketHandler::trySuperMaster(DNSPacket *p)
+{
+  Resolver::res_t nsset;
+  try {
+    Resolver resolver;
+    uint32_t theirserial;
+    resolver.getSoaSerial(p->getRemote(),p->qdomain, &theirserial);  
+  
+    resolver.resolve(p->getRemote(),p->qdomain.c_str(), QType::NS);
+
+    nsset=resolver.result();
+  }
+  catch(ResolverException &re) {
+    L<<Logger::Error<<"Error resolving SOA or NS at: "<< p->getRemote() <<": "<<re.reason<<endl;
+    return RCode::ServFail;
+  }
+
+  string account;
+  DNSBackend *db;
+  if(!B.superMasterBackend(p->getRemote(), p->qdomain, nsset, &account, &db)) {
+   L<<Logger::Error<<"Unable to find backend willing to host "<<p->qdomain<<" for potential supermaster "<<p->getRemote()<<endl;
+    return RCode::Refused;
+  }
+  db->createSlaveDomain(p->getRemote(),p->qdomain,account);
+  Communicator.addSuckRequest(p->qdomain, p->getRemote());  
+  L<<Logger::Warning<<"Created new slave zone '"<<p->qdomain<<"' from supermaster "<<p->getRemote()<<", queued axfr"<<endl;
+  return RCode::NoError;
+}
+
+int PacketHandler::processNotify(DNSPacket *p)
+{
+  /* now what? 
+     was this notification from an approved address?
+     We determine our internal SOA id (via UeberBackend)
+     We determine the SOA at our (known) master
+     if master is higher -> do stuff
+  */
+  if(!arg().mustDo("slave")) {
+    L<<Logger::Error<<"Received NOTIFY for "<<p->qdomain<<" from "<<p->getRemote()<<" but slave support is disabled in the configuration"<<endl;
+    return RCode::NotImp;
+  }
+  DNSBackend *db=0;
+  DomainInfo di;
+  if(!B.getDomainInfo(p->qdomain, di) || !(db=di.backend)) {
+    L<<Logger::Error<<"Received NOTIFY for "<<p->qdomain<<" from "<<p->getRemote()<<" for which we are not authoritative"<<endl;
+    return trySuperMaster(p);
+  }
+    
+  if(!db->isMaster(p->qdomain, p->getRemote())) {
+    L<<Logger::Error<<"Received NOTIFY for "<<p->qdomain<<" from "<<p->getRemote()<<" which is not a master"<<endl;
+    return RCode::Refused;
+  }
+
+  uint32_t theirserial=0;
+
+  /* to quote Rusty Russell - this code is so bad that you can actually hear it suck */
+  /* this is an instant DoS, just spoof notifications from the address of the master and we block  */
+
+  Resolver resolver;
+  try {
+    resolver.getSoaSerial(p->getRemote(),p->qdomain, &theirserial);
+  }
+  catch(ResolverException& re) {
+    L<<Logger::Error<<re.reason<<endl;
+    return RCode::ServFail;
+  }
+	
+
+  if(theirserial<=di.serial) {
+    L<<Logger::Error<<"Received NOTIFY for "<<p->qdomain<<" from master "<<p->getRemote()<<", we are up to date: "<<
+      theirserial<<"<="<<di.serial<<endl;
+    return RCode::NoError;
+  }
+  else {
+    L<<Logger::Error<<"Received valid NOTIFY for "<<p->qdomain<<" (id="<<di.id<<") from master "<<p->getRemote()<<": "<<
+      theirserial<<" > "<<di.serial<<endl;
+
+    Communicator.addSuckRequest(p->qdomain, p->getRemote(),true); // priority
+  }
+  return -1; 
+}
+
+
+
+bool validDNSName(const string &name)
+{
+  string::size_type pos, length=name.length();
+  char c;
+  for(pos=0; pos < length; ++pos) {
+    c=name[pos];
+    if(!((c >= 'a' && c <= 'z') ||
+	 (c >= 'A' && c <= 'Z') ||
+	 (c >= '0' && c <= '9') ||
+	 c =='-' || c == '_' || c=='*' || c=='.' || c=='/' || c=='@'))
+      return false;
+  }
+  return true;
+}  
+
+DNSPacket *PacketHandler::question(DNSPacket *p)
+{
+  bool shouldRecurse=false;
+  DNSPacket *ret=questionOrRecurse(p, &shouldRecurse);
+  if(shouldRecurse) {
+    DP->sendPacket(p);
+  }
+  return ret;
+}
+
+//! Called by the Distributor to ask a question. Returns 0 in case of an error
+DNSPacket *PacketHandler::questionOrRecurse(DNSPacket *p, bool *shouldRecurse)
+{
+  *shouldRecurse=false;
+  DNSResourceRecord rr;
+  SOAData sd;
+  sd.db=0;
+  
+  string subdomain="";
+  string soa;
+  int retargetcount=0;
+  bool noSameLevelNS;
+
+  DNSPacket *r=0;
+  try {    
+    DLOG(L << Logger::Notice<<"Remote "<< p->remote.toString() <<" wants a type " << p->qtype.getName() << " ("<<p->qtype.getCode()<<") about '"<<p->qdomain << "'" << endl);
+
+// XXX FIXME Find out why this isn't working!
+#ifndef WIN32
+    if(p->d.qr) { // QR bit from dns packet (thanks RA from N)
+      L<<Logger::Error<<"Received an answer (non-query) packet from "<<p->getRemote()<<", dropping"<<endl;
+      S.inc("corrupt-packets");
+      return 0;
+    }
+#endif // WIN32
+
+    // XXX FIXME do this in DNSPacket::parse ?
+
+    if(!validDNSName(p->qdomain)) {
+      if(arg().mustDo("log-dns-details"))
+        L<<Logger::Error<<"Received a malformed qdomain from "<<p->getRemote()<<", '"<<p->qdomain<<"': sending servfail"<<endl;
+      S.inc("corrupt-packets");
+      r=p->replyPacket(); 
+      r->setRcode(RCode::ServFail);
+      return r;
+    }
+    if(p->d.opcode) { // non-zero opcode (again thanks RA!)
+      if(p->d.opcode==Opcode::Update) {
+	if(arg().mustDo("log-failed-updates"))
+	  L<<Logger::Notice<<"Received an UPDATE opcode from "<<p->getRemote()<<" for "<<p->qdomain<<", sending NOTIMP"<<endl;
+	r=p->replyPacket(); 
+	r->setRcode(RCode::NotImp); // notimp;
+	return r; 
+      }
+      else if(p->d.opcode==Opcode::Notify) {
+	int res=processNotify(p);
+	if(res>=0) {
+	  DNSPacket *r=p->replyPacket();
+	  r->setRcode(res);
+	  return r;
+	}
+	return 0;
+      }
+      
+      L<<Logger::Error<<"Received an unknown opcode "<<p->d.opcode<<" from "<<p->getRemote()<<" for "<<p->qdomain<<endl;
+
+      r=p->replyPacket(); 
+      r->setRcode(RCode::NotImp); 
+      return r; 
+    }
+    
+    r=p->replyPacket();  // generate an empty reply packet
+
+    if(p->qtype.getCode()==QType::IXFR) {
+      r->setRcode(RCode::NotImp);
+      return r;
+    }
+
+    bool found=false;
+    
+    string target=p->qdomain;
+    bool noCache=false;
+
+    if(doVersionRequest(p,r,target)) // catch version.bind requests
+      goto sendit;
+
+    if(p->qclass==255) // any class query 
+      r->setA(false);
+    else if(p->qclass!=1) // we only know about IN, so we don't find anything
+      goto sendit;
+
+    int mret;
+  retargeted:;
+    if(retargetcount++>10) {
+      L<<Logger::Error<<"Detected wildcard CNAME loop involving '"<<target<<"'"<<endl;
+      r->setRcode(RCode::ServFail);
+      goto sendit;
+    }
+    mret=makeCanonic(p, r, target); // traverse CNAME chain until we have a useful record (may actually give the correct answer!)
+
+    if(mret==2) { // there is some data, but not of the correct type
+      r->clearRecords();
+      DLOG(L<<"There is some data, but not of the correct type, adding SOA for NXRECORDSET"<<endl);
+      SOAData sd;
+      if(getAuth(p, &sd, target, 0)) {
+	rr.qname=sd.qname;
+	rr.qtype=QType::SOA;
+	rr.content=serializeSOAData(sd);
+	rr.ttl=sd.ttl;
+	rr.domain_id=sd.domain_id;
+	rr.d_place=DNSResourceRecord::AUTHORITY;
+	r->addRecord(rr);
+      }
+    }
+
+    if(mret == 1) 
+      goto sendit; // this might be the end of it (client requested a CNAME, or we found the answer already)
+
+    if(d_doFancyRecords) { // MBOXFW, URL <- fake records, emulated with MX and A
+      int res=doFancyRecords(p,r,target);
+      if(res) { // had a result
+	if(res<0) // it was an error
+	  r->setRcode(RCode::ServFail);
+	goto sendit;  
+      }
+    }
+    
+    // now ready to start the real direct search
+
+    if(p->qtype.getCode()==QType::SOA || p->qtype.getCode()==QType::ANY) { // this is special
+
+      if(B.getSOA(target,sd,p)) {
+	rr.qname=target;
+	rr.qtype=QType::SOA;
+	rr.content=serializeSOAData(sd);
+	rr.ttl=sd.ttl;
+	rr.domain_id=sd.domain_id;
+	rr.d_place=DNSResourceRecord::ANSWER;
+	r->addRecord(rr);
+	if(p->qtype.getCode()==QType::SOA) { // we are done
+	  goto sendit;
+	}
+      }
+    }
+    noSameLevelNS=true;
+
+    if(p->qtype.getCode()!=QType::SOA) { // regular direct lookup
+      B.lookup(QType(QType::ANY), target,p);
+      
+      while(B.get(rr)) {
+	if(rr.qtype.getCode()==QType::SOA) // skip any direct SOA responses as they may be different
+	  continue;
+	if(rr.qtype==p->qtype || p->qtype.getCode()==QType::ANY ) {
+	  DLOG(L<<"Found a direct answer: "<<rr.content<<endl);
+	  found=true;
+	  r->addRecord(rr);  // and add
+	}
+	else
+	  if(rr.qtype.getCode()==QType::NS)
+	    noSameLevelNS=false;
+      }
+      
+      if(p->qtype.getCode()==QType::ANY) {
+	if(d_doFancyRecords) { 
+	  int res=findMboxFW(p,r,target);
+	  if(res<0)
+	    L<<Logger::Error<<"Error finding a mailbox record after an ANY query"<<endl;
+	  if(res>0) {
+	    DLOG(L<<Logger::Error<<"Frobbed an MX in!"<<endl);
+	    found=true;
+	  }
+	}
+      }
+      if(found) 
+	goto sendit;
+      
+    }
+    
+    // not found yet, try wildcards (we only try here in case of recursion - we should check before we hand off)
+
+    if(mret != 2 && p->d.rd && d_doRecursion && d_doWildcards) { 
+      int res=doWildcardRecords(p,r,target);
+      if(res) { // had a result
+	// FIXME: wildCard may retarget us in the future
+	if(res==1)  // had a straight result
+	  goto sendit;  
+	if(res==2)
+	  goto retargeted;
+	goto sendit;  
+      }
+    }
+
+    // RECURSION CUT-OUT! 
+
+    bool weAuth;
+    int zoneId;
+    zoneId=-1;
+    
+    if(p->d.rd && d_doRecursion && arg().mustDo("allow-recursion-override"))
+      weAuth=getAuth(p, &sd, target, &zoneId);
+    else
+      weAuth=false;
+
+    if(p->d.rd && d_doRecursion && !weAuth) {
+      if(DP->recurseFor(p)) {
+	*shouldRecurse=true;
+	delete r;
+	return 0;
+      }
+      else noCache=true;
+    }
+    
+    string::size_type pos;
+    
+    DLOG(L<<"Nothing found so far for '"<<target<<"', do we even have authority over this domain?"<<endl);
+
+    if(zoneId==-1)
+      weAuth=getAuth(p, &sd, target, &zoneId); // TLDAuth perhaps
+
+    if(weAuth) {
+      DLOG(L<<Logger::Warning<<"Soa found: '"<<sd.qname<<"'"<<endl);
+      ;
+    }
+    if(!weAuth) {
+      DLOG(L<<Logger::Warning<<"We're not authoritative"<<endl);
+      if(p->d.rd || target==p->qdomain) { // only servfail if we didn't follow a CNAME
+	DLOG(L<<Logger::Warning<<"Adding SERVFAIL as we did not followed a CNAME"<<endl);
+	if(d_logDNSDetails)
+	  L<<Logger::Warning<<"Not authoritative for '"<< target<<"', sending servfail to "<<
+	    p->getRemote()<< (p->d.rd ? " (recursion was desired)" : "") <<endl;
+
+	r->setA(false);
+	if(arg().mustDo("send-root-referral")) {
+	  DLOG(L<<Logger::Warning<<"Adding root-referral"<<endl);
+	  addRootReferral(r);
+	}
+	else {
+	  DLOG(L<<Logger::Warning<<"Adding SERVFAIL"<<endl);
+	  r->setRcode(RCode::ServFail);  // 'sorry' 
+	}
+      }
+      else if(!p->d.rd) {
+	addRootReferral(r);
+	goto sendit;
+      }
+				       
+      S.ringAccount("unauth-queries",p->qdomain+"/"+p->qtype.getName());
+      S.ringAccount("remotes-unauth",p->getRemote());
+    }
+    else {
+      DLOG(L<<Logger::Warning<<"We ARE authoritative for a subdomain of '"<<target<<"' ("<<sd.qname<<"), perhaps we have a suitable NS record then"<<endl);
+      subdomain=target;
+      found=0;
+      pos=0; 
+      
+      do {
+	if(pos) // skip dot
+	  pos++;
+	
+	subdomain=subdomain.substr(pos);
+	if(noSameLevelNS) { // skip first lookup if it is known not to exist
+	  noSameLevelNS=false;
+	  continue;
+	}
+	  
+	if(!Utility::strcasecmp(subdomain.c_str(),sd.qname.c_str())) // about to break out of our zone
+	  break; 
+
+	B.lookup("NS", subdomain,p,zoneId);  // start our search at the backend
+	
+	while(B.get(rr)) {
+	  if(target==p->qdomain && !found) { // don't strip data if we've been retargeted!
+	    DLOG(L<<Logger::Warning<<"Clearing records - we've discovered we are auth (see cs 947)"<<endl);
+	    r->clearRecords(); // we need to start out with an empty slate
+	  }
+	  found=true;
+	  rr.d_place=DNSResourceRecord::AUTHORITY; // this for the authority section
+	  r->addRecord(rr);
+	}
+	if(found || (!subdomain.empty() && subdomain[0]=='.')) {  // this catches '..'
+	  r->setA(false);  // send out an NS referral, which should be unauth
+	  break;
+	}
+      }while((pos=subdomain.find("."))!=string::npos);
+      
+      if(!found) {
+	// try wildcards then 
+	if(d_doWildcards) { 
+	  int res=doWildcardRecords(p,r,target);
+
+	  if(res==1)  // had a straight result
+	    goto sendit; 
+	  if(res==2)
+	    goto retargeted;
+	}
+
+	// we have authority but no answer, so we add the SOA for negative caching
+	rr.qname=sd.qname;
+	rr.qtype=QType::SOA;
+	rr.content=serializeSOAData(sd);
+	rr.ttl=sd.ttl;
+	rr.domain_id=sd.domain_id;
+	rr.d_place=DNSResourceRecord::AUTHORITY;
+	r->addRecord(rr);
+
+
+	// need to send NXDOMAIN if there are 0 records for whatever type for target
+	
+	B.lookup("ANY",target,p);
+	while(B.get(rr))
+	  found=true;
+	
+	if(!found) {
+	  SOAData sd2;
+	  if(B.getSOA(target,sd2,p)) // is there a SOA perhaps? (which may not appear in an ANY query)
+	    found=true;
+	}
+
+	if(!found) { 
+	  if(d_logDNSDetails)
+	    L<<Logger::Notice<<"Authoritative NXDOMAIN to "<< p->getRemote() <<" for '"<<target<<"' ("<<p->qtype.getName()<<")"<<endl;
+
+	  r->setRcode(RCode::NXDomain); 
+	  S.ringAccount("nxdomain-queries",p->qdomain+"/"+p->qtype.getName());
+	}
+	else {
+	  if(d_logDNSDetails)
+	    L<<Logger::Notice<<"Authoritative empty NO ERROR to "<< p->getRemote() <<" for '"<<target<<"' ("<<p->qtype.getName()<<"), other types do exist"<<endl;
+	  S.ringAccount("noerror-queries",p->qdomain+"/"+p->qtype.getName());
+	}
+      }
+    }
+    
+    // whatever we've built so far, do additional processing
+    
+  sendit:;
+    if(doAdditionalProcessingAndDropAA(p,r)<0)
+      return 0;
+    r->wrapup(); // needed for inserting in cache
+    if(!noCache)
+      PC.insert(p,r); // in the packet cache
+  }
+  catch(DBException &e) {
+    L<<Logger::Error<<"Database module reported condition which prevented lookup ("+e.reason+") sending out servfail"<<endl;
+    r->setRcode(RCode::ServFail);
+    S.inc("servfail-packets");
+    S.ringAccount("servfail-queries",p->qdomain);
+  }
+  return r; 
+
+}
+

Reply via email to