]> granicus.if.org Git - pdns/commitdiff
Add rfc2136handler to handle rfc2136 operation
authorRuben d'Arco <cyclops@prof-x.net>
Mon, 3 Dec 2012 05:55:02 +0000 (06:55 +0100)
committermind04 <mind04@monshouwer.org>
Fri, 12 Jul 2013 15:17:25 +0000 (17:17 +0200)
pdns/rfc2136handler.cc [new file with mode: 0644]

diff --git a/pdns/rfc2136handler.cc b/pdns/rfc2136handler.cc
new file mode 100644 (file)
index 0000000..b7e39f3
--- /dev/null
@@ -0,0 +1,691 @@
+#include "packethandler.hh"
+#include "qtype.hh"
+#include "dnspacket.hh"
+#include "packetcache.hh"
+#include "dnsseckeeper.hh"
+#include "base64.hh"
+#include "base32.hh"
+#include "misc.hh"
+#include "arguments.hh"
+
+extern PacketCache PC;
+
+// Implement section 3.2.1 and 3.2.2 of RFC2136
+int PacketHandler::checkUpdatePrerequisites(const DNSRecord *rr, DomainInfo *di) {
+  if (rr->d_ttl != 0)
+    return RCode::FormErr;
+
+  // 3.2.1 and 3.2.2 check content length.
+  if ( (rr->d_class == QClass::NONE || rr->d_class == QClass::ANY) && rr->d_clen != 0)
+    return RCode::FormErr;
+
+  string rLabel = stripDot(rr->d_label);
+
+  bool foundRecord=false;
+  DNSResourceRecord rec;
+  di->backend->lookup(QType(QType::ANY), rLabel);
+  while(di->backend->get(rec)) {
+    if (!rec.qtype.getCode())
+      continue;
+    if ((rr->d_type != QType::ANY && rec.qtype == rr->d_type) || rr->d_type == QType::ANY)
+      foundRecord=true;
+  }
+
+  // Section 3.2.1        
+  if (rr->d_class == QClass::ANY && !foundRecord) { 
+    if (rr->d_type == QType::ANY) 
+      return RCode::NXDomain;
+    if (rr->d_type != QType::ANY)
+      return RCode::NXRRSet;
+  } 
+
+  // Section 3.2.2
+  if (rr->d_class == QClass::NONE && foundRecord) {
+    if (rr->d_type == QType::ANY)
+      return RCode::YXDomain;
+    if (rr->d_type != QType::ANY)
+      return RCode::YXRRSet;
+  }
+
+  return RCode::NoError;
+}
+
+
+// Method implements section 3.4.1 of RFC2136
+int PacketHandler::checkUpdatePrescan(const DNSRecord *rr) {
+  // The RFC stats that d_class != ZCLASS, but we only support the IN class.
+  if (rr->d_class != QClass::IN && rr->d_class != QClass::NONE && rr->d_class != QClass::ANY) 
+    return RCode::FormErr;
+
+  QType qtype = QType(rr->d_type);
+
+  if (! qtype.isSupportedType())
+    return RCode::FormErr;
+
+  if ((rr->d_class == QClass::NONE || rr->d_class == QClass::ANY) && rr->d_ttl != 0)
+    return RCode::FormErr;
+
+  if (rr->d_class == QClass::ANY && rr->d_clen != 0)
+    return RCode::FormErr;
+  
+  if (qtype.isMetadataType())
+      return RCode::FormErr;
+
+  if (rr->d_class != QClass::ANY && qtype.getCode() == QType::ANY)
+    return RCode::FormErr;
+
+  return RCode::NoError;
+}
+
+// Implements section 3.4.2 of RFC2136
+uint16_t PacketHandler::performUpdate(const string &msgPrefix, const DNSRecord *rr, DomainInfo *di, bool narrow, bool haveNSEC3, const NSEC3PARAMRecordContent *ns3pr, bool *updatedSerial) {
+  /*DNSResourceRecord rec;
+  uint16_t updatedRecords = 0, deletedRecords = 0, insertedRecords = 0;
+
+  string rLabel = stripDot(rr->d_label);
+
+  if (rr->d_class == QClass::IN) { // 3.4.2.2, Add/update records.
+    DLOG(L<<msgPrefix<<"Add/Update record (QClass == IN)"<<endl);
+    bool foundRecord=false;
+    set<string> delnonterm;
+    vector<pair<DNSResourceRecord, DNSResourceRecord> > recordsToUpdate;
+    di->backend->lookup(QType(QType::ANY), rLabel);
+    while (di->backend->get(rec)) {
+      if (!rec.qtype.getCode())
+        delnonterm.insert(rec.qname); // we're inserting a record which is a ENT, so we must delete that ENT
+      if (rr->d_type == QType::SOA && rec.qtype == QType::SOA) {
+        foundRecord = true;
+        DNSResourceRecord newRec = rec;
+        newRec.setContent(rr->d_content->getZoneRepresentation());
+        newRec.ttl = rr->d_ttl;
+        SOAData sdOld, sdUpdate;
+        fillSOAData(rec.content, sdOld);
+        fillSOAData(newRec.content, sdUpdate);
+        if (rfc1982LessThan(sdOld.serial, sdUpdate.serial)) {
+          recordsToUpdate.push_back(make_pair(rec, newRec));
+          *updatedSerial = true;
+        }
+        else
+          L<<Logger::Notice<<msgPrefix<<"Provided serial ("<<sdUpdate.serial<<") is older than the current serial ("<<sdOld.serial<<"), ignoring SOA update."<<endl;
+      } else if (rr->d_type == QType::CNAME && rec.qtype == QType::CNAME) { // If the update record is a cname, we update that cname. 
+        foundRecord = true;
+        DNSResourceRecord newRec = rec;
+        newRec.ttl = rr->d_ttl;
+        newRec.setContent(rr->d_content->getZoneRepresentation());
+        recordsToUpdate.push_back(make_pair(rec, newRec));
+      } else if (rec.qtype == rr->d_type) {
+        string content = rr->d_content->getZoneRepresentation();
+        if (rec.getZoneRepresentation() == content) {
+          foundRecord=true;
+          DNSResourceRecord newRec = rec;
+          newRec.ttl = rr->d_ttl; // If content matches, we can only update the TTL.
+          recordsToUpdate.push_back(make_pair(rec, newRec));
+        }
+      }
+    }
+   // Update the records
+   for(vector<pair<DNSResourceRecord, DNSResourceRecord> >::const_iterator i=recordsToUpdate.begin(); i!=recordsToUpdate.end(); ++i){
+      di->backend->updateRecord(i->first, i->second);
+      L<<Logger::Notice<<msgPrefix<<"Updating record "<<i->first.qname<<"|"<<i->first.qtype.getName()<<endl;
+      updatedRecords++;
+    }
+  
+
+    // If the record was not replaced, we insert it.
+    if (! foundRecord) {
+      DNSResourceRecord newRec(*rr);
+      newRec.domain_id = di->id;
+      L<<Logger::Notice<<msgPrefix<<"Adding record "<<newRec.qname<<"|"<<newRec.qtype.getName()<<endl;
+      di->backend->feedRecord(newRec);
+      insertedRecords++;
+    }
+    
+    // The next section will fix order and Auth fields and insert ENT's 
+    if (insertedRecords > 0) {
+      string shorter(rLabel);
+      bool auth=true;
+
+      set<string> insnonterm;
+      if (shorter != di->zone && rr->d_type != QType::DS) {
+        do {
+          if (shorter == di->zone)
+            break;
+
+          bool foundShorter = false;
+          di->backend->lookup(QType(QType::ANY), shorter);
+          while (di->backend->get(rec)) {
+            if (rec.qname != rLabel)
+              foundShorter = true;
+            if (rec.qtype == QType::NS)
+              auth=false;
+          }
+          if (!foundShorter && shorter != rLabel && shorter != di->zone)
+            insnonterm.insert(shorter);
+
+        } while(chopOff(shorter));
+      }
+
+
+      if(haveNSEC3)
+      {
+        string hashed;
+        if(!narrow) 
+          hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr->d_iterations, ns3pr->d_salt, rLabel)));
+        
+        di->backend->updateDNSSECOrderAndAuthAbsolute(di->id, rLabel, hashed, auth);
+        if(!auth || rr->d_type == QType::DS)
+        {
+          di->backend->nullifyDNSSECOrderNameAndAuth(di->id, rLabel, "NS");
+          di->backend->nullifyDNSSECOrderNameAndAuth(di->id, rLabel, "A");
+          di->backend->nullifyDNSSECOrderNameAndAuth(di->id, rLabel, "AAAA");
+        }
+      }
+      else // NSEC
+      {
+        di->backend->updateDNSSECOrderAndAuth(di->id, di->zone, rLabel, auth);
+        if(!auth || rr->d_type == QType::DS)
+        {
+          di->backend->nullifyDNSSECOrderNameAndAuth(di->id, rLabel, "A");
+          di->backend->nullifyDNSSECOrderNameAndAuth(di->id, rLabel, "AAAA");
+        }
+      }
+      // If we insert an NS, all the records below it become non auth - so, we're inserting a delegate.
+      // Auth can only be false when the rLabel is not the zone 
+      if (auth == false && rr->d_type == QType::NS) {
+        DLOG(L<<msgPrefix<<"Going to fix auth flags below "<<rLabel<<endl);
+        vector<string> qnames;
+        di->backend->listSubZone(rLabel, di->id);
+        while(di->backend->get(rec)) {
+          if (rec.qtype.getCode() && rec.qtype.getCode() != QType::DS) // Skip ENT and DS records.
+            qnames.push_back(rec.qname);
+        }
+        for(vector<string>::const_iterator qname=qnames.begin(); qname != qnames.end(); ++qname) {
+          if(haveNSEC3)  {
+            string hashed;
+            if(!narrow) 
+              hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr->d_iterations, ns3pr->d_salt, *qname)));
+        
+            di->backend->updateDNSSECOrderAndAuthAbsolute(di->id, *qname, hashed, auth);
+            di->backend->nullifyDNSSECOrderNameAndAuth(di->id, *qname, "NS");
+          }
+          else // NSEC
+            di->backend->updateDNSSECOrderAndAuth(di->id, di->zone, *qname, auth);
+
+          di->backend->nullifyDNSSECOrderNameAndAuth(di->id, *qname, "AAAA");
+          di->backend->nullifyDNSSECOrderNameAndAuth(di->id, *qname, "A");
+        }
+      }
+
+      //Insert and delete ENT's
+      if (insnonterm.size() > 0 || delnonterm.size() > 0) {
+        DLOG(L<<msgPrefix<<"Updating ENT records"<<endl);
+        di->backend->updateEmptyNonTerminals(di->id, di->zone, insnonterm, delnonterm, false);
+        for (set<string>::const_iterator i=insnonterm.begin(); i!=insnonterm.end(); i++) {
+          string hashed;
+          if(haveNSEC3)
+          {
+            string hashed;
+            if(!narrow) 
+              hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr->d_iterations, ns3pr->d_salt, *i)));
+            di->backend->updateDNSSECOrderAndAuthAbsolute(di->id, *i, hashed, false);
+          }
+        }
+      }
+    }
+  } // rr->d_class == QClass::IN
+
+
+
+  // The following section deals with the removal of records. When the class is ANY, all records of 
+  // that name (and/or type) are deleted. When the type is NONE, the RDATA must match as well.
+  // There are special cases for SOA and NS records to ensure the zone will remain operational.
+  //Section 3.4.2.3: Delete RRs based on name and (if provided) type, but never delete NS or SOA at the zone apex.
+  vector<DNSResourceRecord> recordsToDelete;
+  if (rr->d_class == QClass::ANY) {
+    DLOG(L<<msgPrefix<<"Deleting records (QClass == ANY)"<<endl);
+    if (! (rLabel == di->zone && (rr->d_type == QType::SOA || rr->d_type == QType::NS) ) ) {
+      di->backend->lookup(QType(QType::ANY), rLabel);
+      while (di->backend->get(rec)) {
+        if (rec.qtype.getCode() && (rr->d_type == QType::ANY || rr->d_type == rec.qtype.getCode()))
+          recordsToDelete.push_back(rec);
+      }
+    }
+  }
+
+  // Section 3.4.2.4, Delete a specific record that matches name, type and rdata
+  // There are special conditions for SOA (never delete them). There is also a special condition for NS records,
+  // but that's filtered out by not calling this method in those cases - There's a check to make sure we don't delete the 
+  // last NS.
+  if (rr->d_class == QClass::NONE && rr->d_type != QType::SOA) { // never remove SOA.
+    DLOG(L<<msgPrefix<<"Deleting records (QClass == NONE && type != SOA)"<<endl);
+    di->backend->lookup(QType(QType::ANY), rLabel);
+    while(di->backend->get(rec)) {
+      if (rec.qtype.getCode() && rec.qtype == rr->d_type && rec.getZoneRepresentation() == rr->d_content->getZoneRepresentation())
+        recordsToDelete.push_back(rec);
+    }
+  }
+
+  if (recordsToDelete.size()) {
+    // Perform removes on the backend and fix auth/ordername
+    for(vector<DNSResourceRecord>::const_iterator recToDelete=recordsToDelete.begin(); recToDelete!=recordsToDelete.end(); ++recToDelete){
+      L<<Logger::Notice<<msgPrefix<<"Deleting record "<<recToDelete->qname<<"|"<<recToDelete->qtype.getName()<<endl;
+      di->backend->removeRecord(*recToDelete);
+      deletedRecords++;
+
+      if (recToDelete->qtype.getCode() == QType::NS && recToDelete->qname != di->zone) {
+        vector<string> changeAuth;
+        di->backend->listSubZone(recToDelete->qname, di->id);
+        while (di->backend->get(rec)) {
+          if (rec.qtype.getCode()) // skip ENT records
+            changeAuth.push_back(rec.qname);
+        }
+        for (vector<string>::const_iterator changeRec=changeAuth.begin(); changeRec!=changeAuth.end(); ++changeRec) {
+          if(haveNSEC3)  {
+            string hashed;
+            if(!narrow) 
+              hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr->d_iterations, ns3pr->d_salt, *changeRec)));
+        
+            di->backend->updateDNSSECOrderAndAuthAbsolute(di->id, *changeRec, hashed, true);
+          }
+          else // NSEC
+            di->backend->updateDNSSECOrderAndAuth(di->id, di->zone, *changeRec, true);
+        }
+      }
+    }
+
+    // Fix ENT records.
+    // We must check if we have a record below the current level and if we removed the 'last' record
+    // on that level. If so, we must insert an ENT record.
+    // We take extra care here to not 'include' the record that we just deleted. Some backends will still return it.
+    set<string> insnonterm, delnonterm;
+    bool foundDeeper = false, foundOther = false;
+    di->backend->listSubZone(rLabel, di->id);
+    while (di->backend->get(rec)) {
+      if (rec.qname == rLabel && !count(recordsToDelete.begin(), recordsToDelete.end(), rec))
+        foundOther = true;
+      if (rec.qname != rLabel)
+        foundDeeper = true;
+    }
+
+    if (foundDeeper && !foundOther) {
+      insnonterm.insert(rLabel);
+    } else if (!foundOther) {
+      // If we didn't have to insert an ENT, we might have deleted a record at very deep level
+      // and we must then clean up the ENT's above the deleted record.
+      string shorter(rLabel);
+      do {
+        bool foundRealRR=false;
+        if (shorter == di->zone)
+          break;
+        // The reason for a listSubZone here is because might go up the tree and find the root ENT of another branch
+        // consider these non ENT-records:
+        // a.b.c.d.e.test.com
+        // a.b.d.e.test.com
+        // if we delete a.b.c.d.e.test.com, we go up to d.e.test.com and then find a.b.d.e.test.com
+        // At that point we can stop deleting ENT's because the tree is in tact again.
+        di->backend->listSubZone(shorter, di->id);
+        while (di->backend->get(rec)) {
+          if (rec.qtype.getCode())
+            foundRealRR=true;
+        }
+        if (!foundRealRR)
+          delnonterm.insert(shorter);
+        else
+          break; // we found a real record - tree is ok again.
+      }while(chopOff(shorter));
+    }
+
+    if (insnonterm.size() > 0 || delnonterm.size() > 0) {
+      DLOG(L<<msgPrefix<<"Updating ENT records"<<endl);
+      di->backend->updateEmptyNonTerminals(di->id, di->zone, insnonterm, delnonterm, false);
+      for (set<string>::const_iterator i=insnonterm.begin(); i!=insnonterm.end(); i++) {
+        string hashed;
+        if(haveNSEC3)
+        {
+          string hashed;
+          if(!narrow) 
+            hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr->d_iterations, ns3pr->d_salt, *i)));
+          di->backend->updateDNSSECOrderAndAuthAbsolute(di->id, *i, hashed, true);
+        }
+      }
+    }
+  }
+
+  L<<Logger::Notice<<msgPrefix<<"Added "<<insertedRecords<<"; Updated: "<<updatedRecords<<"; Deleted:"<<deletedRecords<<endl;
+
+  return updatedRecords + deletedRecords + insertedRecords;*/
+  return 0;
+}
+
+int PacketHandler::processUpdate(DNSPacket *p) {
+  if (::arg().mustDo("disable-rfc2136"))
+    return RCode::Refused;
+  
+  string msgPrefix="UPDATE from " + p->getRemote() + " for " + p->qdomain + ": ";
+  L<<Logger::Info<<msgPrefix<<"Processing started."<<endl;
+
+  // Check permissions - IP based
+  vector<string> allowedRanges;
+  B.getDomainMetadata(p->qdomain, "ALLOW-2136-FROM", allowedRanges);
+  if (! ::arg()["allow-2136-from"].empty()) 
+    stringtok(allowedRanges, ::arg()["allow-2136-from"], ", \t" );
+
+  NetmaskGroup ng;
+  for(vector<string>::const_iterator i=allowedRanges.begin(); i != allowedRanges.end(); i++)
+    ng.addMask(*i);
+    
+  if ( ! ng.match(&p->d_remote)) {
+    L<<Logger::Error<<msgPrefix<<"Remote not listed in allow-2136-from or domainmetadata. Sending REFUSED"<<endl;
+    return RCode::Refused;
+  }
+
+
+  // Check permissions - TSIG based.
+  vector<string> tsigKeys;
+  B.getDomainMetadata(p->qdomain, "TSIG-ALLOW-2136", tsigKeys);
+  if (tsigKeys.size() > 0) {
+    bool validKey = false;
+    
+    TSIGRecordContent trc;
+    string inputkey, message;
+    if (! p->getTSIGDetails(&trc,  &inputkey, &message)) {
+      L<<Logger::Error<<msgPrefix<<"TSIG key required, but packet does not contain key. Sending REFUSED"<<endl;
+      return RCode::Refused;
+    }
+
+    for(vector<string>::const_iterator key=tsigKeys.begin(); key != tsigKeys.end(); key++) {
+      if (inputkey == *key) // because checkForCorrectTSIG has already been performed earlier on, if the names of the ky match with the domain given. THis is valid.
+        validKey=true;
+    }
+
+    if (!validKey) {
+      L<<Logger::Error<<msgPrefix<<"TSIG key ("<<inputkey<<") required, but no matching key found in domainmetadata, tried "<<tsigKeys.size()<<". Sending REFUSED"<<endl;
+      return RCode::Refused;
+    }
+  }
+
+  if (tsigKeys.size() == 0 && p->d_havetsig)
+    L<<Logger::Warning<<msgPrefix<<"TSIG is provided, but domain is not secured with TSIG. Processing continues"<<endl;
+
+  // RFC2136 uses the same DNS Header and Message as defined in RFC1035.
+  // This means we can use the MOADNSParser to parse the incoming packet. The result is that we have some different 
+  // variable names during the use of our MOADNSParser.
+  MOADNSParser mdp(p->getString());
+  if (mdp.d_header.qdcount != 1) {
+    L<<Logger::Warning<<msgPrefix<<"Zone Count is not 1, sending FormErr"<<endl;
+    return RCode::FormErr;
+  }     
+
+  if (p->qtype.getCode() != QType::SOA) { // RFC2136 2.3 - ZTYPE must be SOA
+    L<<Logger::Warning<<msgPrefix<<"Query ZTYPE is not SOA, sending FormErr"<<endl;
+    return RCode::FormErr;
+  }
+
+  if (p->qclass != QClass::IN) {
+    L<<Logger::Warning<<msgPrefix<<"Class is not IN, sending NotAuth"<<endl;
+    return RCode::NotAuth;
+  }
+
+  DomainInfo di;
+  di.backend=0;
+  if(!B.getDomainInfo(p->qdomain, di) || !di.backend) {
+    L<<Logger::Error<<msgPrefix<<"Can't determine backend for domain '"<<p->qdomain<<"' (or backend does not support RFC2136 operation)"<<endl;
+    return RCode::NotAuth;
+  }
+
+  if (di.kind == DomainInfo::Slave) { //TODO: We do not support the forwarding to master stuff.. which we should ;-)
+    L<<Logger::Error<<msgPrefix<<"We are slave for the domain and do not support forwarding to master, sending NotImp"<<endl;
+    return RCode::NotImp;
+  }
+
+  // Check if all the records provided are within the zone 
+  for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i != mdp.d_answers.end(); ++i) {
+    const DNSRecord *rr = &i->first;
+    // Skip this check for other field types (like the TSIG -  which is in the additional section)
+    // For a TSIG, the label is the dnskey.
+    if (! (rr->d_place == DNSRecord::Answer || rr->d_place == DNSRecord::Nameserver)) 
+      continue;
+
+    string label = stripDot(rr->d_label);
+
+    if (!endsOn(label, di.zone)) {
+      L<<Logger::Error<<msgPrefix<<"Received update/record out of zone, sending NotZone."<<endl;
+      return RCode::NotZone;
+    }
+  }
+
+  //TODO: Start a lock here, to make section 3.7 correct???
+  L<<Logger::Info<<msgPrefix<<"starting transaction."<<endl;
+  if (!di.backend->startTransaction(p->qdomain, -1)) { // Not giving the domain_id means that we do not delete the records.
+    L<<Logger::Error<<msgPrefix<<"Backend for domain "<<p->qdomain<<" does not support transaction. Can't do Update packet."<<endl;
+    return RCode::NotImp;
+  }
+
+  // 3.2.1 and 3.2.2 - Prerequisite check 
+  for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i != mdp.d_answers.end(); ++i) {
+    const DNSRecord *rr = &i->first;
+    if (rr->d_place == DNSRecord::Answer) {
+      int res = checkUpdatePrerequisites(rr, &di);
+      if (res>0) {
+        L<<Logger::Error<<msgPrefix<<"Failed PreRequisites check, returning "<<res<<endl;
+        di.backend->abortTransaction();
+        return res;
+      }
+    } 
+  }
+
+  // 3.2.3 - Prerequisite check - this is outside of updatePrequisitesCheck because we check an RRSet and not the RR.
+  typedef pair<string, QType> rrSetKey_t;
+  typedef vector<DNSResourceRecord> rrVector_t;
+  typedef std::map<rrSetKey_t, rrVector_t> RRsetMap_t;
+  RRsetMap_t preReqRRsets;
+  for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i != mdp.d_answers.end(); ++i) {
+    const DNSRecord *rr = &i->first;
+    if (rr->d_place == DNSRecord::Answer) {
+      // Last line of 3.2.3
+      if (rr->d_class != QClass::IN && rr->d_class != QClass::NONE && rr->d_class != QClass::ANY) 
+        return RCode::FormErr;
+
+      if (rr->d_class == QClass::IN) {
+        rrSetKey_t key = make_pair(stripDot(rr->d_label), rr->d_type);
+        rrVector_t *vec = &preReqRRsets[key];
+        vec->push_back(DNSResourceRecord(*rr));
+      }
+    }
+  }
+
+  if (preReqRRsets.size() > 0) {
+    RRsetMap_t zoneRRsets;
+    for (RRsetMap_t::iterator preRRSet = preReqRRsets.begin(); preRRSet != preReqRRsets.end(); ++preRRSet) {
+      rrSetKey_t rrSet=preRRSet->first;
+      rrVector_t *vec = &preRRSet->second;
+
+      DNSResourceRecord rec;
+      di.backend->lookup(QType(QType::ANY), rrSet.first);
+      uint16_t foundRR=0, matchRR=0;
+      while (di.backend->get(rec)) {
+        if (rec.qtype == rrSet.second) {
+          foundRR++;
+          for(rrVector_t::iterator rrItem=vec->begin(); rrItem != vec->end(); ++rrItem) {
+            rrItem->ttl = rec.ttl; // The compare one line below also compares TTL, so we make them equal because TTL is not user within prerequisite checks.
+            if (*rrItem == rec) 
+              matchRR++;
+          }
+        }
+      }
+      if (matchRR != foundRR || foundRR != vec->size()) {
+        L<<Logger::Error<<msgPrefix<<"Failed PreRequisites check, returning NXRRSet"<<endl;
+        di.backend->abortTransaction();
+        return RCode::NXRRSet;
+      }
+    }
+  }
+
+
+
+  // 3.4 - Prescan & Add/Update/Delete records
+  uint16_t changedRecords = 0;
+  try {
+
+    // 3.4.1 - Prescan section
+    for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i != mdp.d_answers.end(); ++i) {
+      const DNSRecord *rr = &i->first;
+      if (rr->d_place == DNSRecord::Nameserver) {
+        int res = checkUpdatePrescan(rr);
+        if (res>0) {
+          L<<Logger::Error<<msgPrefix<<"Failed prescan check, returning "<<res<<endl;
+          di.backend->abortTransaction();
+          return res;
+        }
+      }
+    }
+
+    bool updatedSerial=false;
+    NSEC3PARAMRecordContent ns3pr;
+    bool narrow; 
+    bool haveNSEC3 = d_dk.getNSEC3PARAM(di.zone, &ns3pr, &narrow);
+
+    // We get all the before/after fields before doing anything to the db.
+    // We can't do this inside performUpdate() because when we remove a delegate, the before/after result is different to what it should be
+    // to purge the cache correctly - One update/delete might cause a before/after to be created which is before/after the original before/after.
+    vector< pair<string, string> > beforeAfterSet;
+    if (!haveNSEC3) {
+      for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i != mdp.d_answers.end(); ++i) {
+        const DNSRecord *rr = &i->first;
+        if (rr->d_place == DNSRecord::Nameserver) {
+          string before, after;
+//          di.backend->getBeforeAndAfterNames(di.id, di.zone, stripDot(rr->d_label), before, after, (rr->d_class != QClass::IN));
+          beforeAfterSet.push_back(make_pair(before, after));
+        }
+      }
+    }
+
+    // 3.4.2 - Perform the updates.
+    // There's a special condition where deleting the last NS record at zone apex is never deleted (3.4.2.4)
+    // This means we must do it outside the normal performUpdate() because that focusses only on a seperate RR.
+    vector<const DNSRecord *> nsRRtoDelete;
+    for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i != mdp.d_answers.end(); ++i) {
+      const DNSRecord *rr = &i->first;
+      if (rr->d_place == DNSRecord::Nameserver) {
+        if (rr->d_class == QClass::NONE  && rr->d_type == QType::NS && stripDot(rr->d_label) == di.zone)
+          nsRRtoDelete.push_back(rr);
+        else
+          changedRecords += performUpdate(msgPrefix, rr, &di, narrow, haveNSEC3, &ns3pr, &updatedSerial);
+      }
+    }
+    if (nsRRtoDelete.size()) {
+      vector<DNSResourceRecord> nsRRInZone;
+      DNSResourceRecord rec;
+      di.backend->lookup(QType(QType::NS), di.zone);
+      while (di.backend->get(rec)) {
+        nsRRInZone.push_back(rec);
+      }
+      if (nsRRInZone.size() > nsRRtoDelete.size()) { // only delete if the NS's we delete are less then what we have in the zone (3.4.2.4)
+        for (vector<DNSResourceRecord>::iterator inZone=nsRRInZone.begin(); inZone != nsRRInZone.end(); inZone++) {
+          for (vector<const DNSRecord *>::iterator rr=nsRRtoDelete.begin(); rr != nsRRtoDelete.end(); rr++) {
+            if (inZone->getZoneRepresentation() == (*rr)->d_content->getZoneRepresentation())
+              changedRecords += performUpdate(msgPrefix, *rr, &di, narrow, haveNSEC3, &ns3pr, &updatedSerial);
+          }
+        }
+      }
+    }
+
+
+    // Purge the records!
+    if (changedRecords > 0) {
+      if (haveNSEC3) {
+        string zone(di.zone);
+        zone.append("$");
+        PC.purge(zone);  // For NSEC3, nuke the complete zone.
+      } else {
+        //for(vector< pair<string, string> >::const_iterator i=beforeAfterSet.begin(); i != beforeAfterSet.end(); i++)
+          //PC.purgeRange(i->first, i->second, di.zone);
+      }
+    }
+
+    // Section 3.6 - Update the SOA serial - outside of performUpdate because we do a SOA update for the complete update message
+    if (changedRecords > 0 && !updatedSerial)
+      increaseSerial(msgPrefix, di);
+  }
+  catch (AhuException &e) {
+    L<<Logger::Error<<msgPrefix<<"Caught AhuException: "<<e.reason<<"; Sending ServFail!"<<endl;
+    di.backend->abortTransaction();
+    return RCode::ServFail;
+  }
+  catch (...) {
+    L<<Logger::Error<<msgPrefix<<"Caught unknown exception when performing update. Sending ServFail!"<<endl;
+    di.backend->abortTransaction();
+    return RCode::ServFail;
+  }
+  
+  if (!di.backend->commitTransaction()) {
+    L<<Logger::Error<<msgPrefix<<"Failed to commit update for domain "<<di.zone<<"!"<<endl;
+    return RCode::ServFail;
+  }
+  L<<Logger::Info<<msgPrefix<<"Update completed, "<<changedRecords<<" changed records commited."<<endl;
+  return RCode::NoError; //rfc 2136 3.4.2.5
+}
+
+void PacketHandler::increaseSerial(const string &msgPrefix, const DomainInfo& di) {
+  DNSResourceRecord rec, newRec;
+  di.backend->lookup(QType(QType::SOA), di.zone);
+  bool foundSOA=false;
+  while (di.backend->get(rec)) {
+    newRec = rec;
+    foundSOA=true;
+  }
+  if (!foundSOA) {
+    throw AhuException("SOA-Serial update failed because there was no SOA. Wowie.");
+  }
+  SOAData soa2Update;
+  fillSOAData(rec.content, soa2Update);
+
+  vector<string> soaEdit2136Setting;
+  B.getDomainMetadata(di.zone, "SOA-EDIT-2136", soaEdit2136Setting);
+  string soaEdit2136 = "DEFAULT";
+  string soaEdit;
+  if (!soaEdit2136Setting.empty()) {
+    soaEdit2136 = soaEdit2136Setting[0];
+    if (pdns_iequals(soaEdit2136, "SOA-EDIT") || pdns_iequals(soaEdit2136,"SOA-EDIT-INCREASE") ){
+      vector<string> soaEditSetting;
+      B.getDomainMetadata(di.zone, "SOA-EDIT", soaEditSetting);
+      if (soaEditSetting.empty()) {
+        L<<Logger::Error<<msgPrefix<<"Using "<<soaEdit2136<<" for SOA-EDIT-2136 increase on RFC2136, but SOA-EDIT is not set for domain. Using DEFAULT for SOA-EDIT-2136"<<endl;
+        soaEdit2136 = "DEFAULT";
+      } else
+        soaEdit = soaEditSetting[0];
+    }
+  }
+
+
+  if (pdns_iequals(soaEdit2136, "INCREASE"))
+    soa2Update.serial++;
+  else if (pdns_iequals(soaEdit2136, "SOA-EDIT-INCREASE")) {
+    uint32_t newSer = calculateEditSOA(soa2Update, soaEdit);
+    if (newSer <= soa2Update.serial)
+      soa2Update.serial++;
+    else
+      soa2Update.serial = newSer;
+  } else if (pdns_iequals(soaEdit2136, "SOA-EDIT"))
+    soa2Update.serial = calculateEditSOA(soa2Update, soaEdit);
+  else if (pdns_iequals(soaEdit2136, "EPOCH"))
+    soa2Update.serial = time(0);
+  else {
+    time_t now = time(0);
+    struct tm tm;
+    localtime_r(&now, &tm);
+    boost::format fmt("%04d%02d%02d%02d");
+    string newserdate=(fmt % (tm.tm_year+1900) % (tm.tm_mon +1 )% tm.tm_mday % 1).str();
+    uint32_t newser = atol(newserdate.c_str());
+    if (newser <= soa2Update.serial)
+      soa2Update.serial++;
+    else
+      soa2Update.serial = newser;
+  }
+  
+
+  newRec.content = serializeSOAData(soa2Update);
+  //di.backend->updateRecord(rec, newRec);
+  PC.purge(newRec.qname); 
+}
\ No newline at end of file