]> git.ipfire.org Git - thirdparty/pdns.git/blobdiff - pdns/pdnssec.cc
snap
[thirdparty/pdns.git] / pdns / pdnssec.cc
index 780c7830c4c0337631e715c590643d23b3720a90..6c60354d1e7cdffb72858d1a385c4855d8df57bb 100644 (file)
@@ -1,3 +1,6 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
 #include "dnsseckeeper.hh"
 #include "dnssecinfra.hh"
 #include "statbag.hh"
@@ -132,17 +135,20 @@ void loadMainConfig(const std::string& configdir)
   ::arg().set("default-ksk-algorithms","Default KSK algorithms")="rsasha256";
   ::arg().set("default-ksk-size","Default KSK size (0 means default)")="0";
   ::arg().set("default-zsk-algorithms","Default ZSK algorithms")="rsasha256";
-  ::arg().set("default-zsk-size","Default KSK size (0 means default)")="0";
+  ::arg().set("default-zsk-size","Default ZSK size (0 means default)")="0";
   ::arg().set("max-ent-entries", "Maximum number of empty non-terminals in a zone")="100000";
   ::arg().set("module-dir","Default directory for modules")=PKGLIBDIR;
   ::arg().set("entropy-source", "If set, read entropy from this file")="/dev/urandom";
-
+  ::arg().setSwitch("query-logging","Hint backends that queries should be logged")="no";
+  ::arg().set("loglevel","Amount of logging. Higher is more.")="0";
   ::arg().setSwitch("direct-dnskey","Fetch DNSKEY RRs from backend during DNSKEY synthesis")="no";
   ::arg().set("max-nsec3-iterations","Limit the number of NSEC3 hash iterations")="500"; // RFC5155 10.3
   ::arg().set("max-signature-cache-entries", "Maximum number of signatures cache entries")="";
   ::arg().laxFile(configname.c_str());
 
+  L.toConsole(Logger::Error);   // so we print any errors
   BackendMakers().launch(::arg()["launch"]); // vrooooom!
+  L.toConsole((Logger::Urgency)(::arg().asNum("loglevel")));  
   ::arg().laxFile(configname.c_str());    
   //cerr<<"Backend: "<<::arg()["launch"]<<", '" << ::arg()["gmysql-dbname"] <<"'" <<endl;
 
@@ -164,7 +170,6 @@ void loadMainConfig(const std::string& configdir)
   ::arg().set("soa-refresh-default","Default SOA refresh")="10800";
   ::arg().set("soa-retry-default","Default SOA retry")="3600";
   ::arg().set("soa-expire-default","Default SOA expire")="604800";
-  ::arg().setSwitch("query-logging","Hint backends that queries should be logged")="no";
   ::arg().set("soa-minimum-ttl","Default SOA minimum ttl")="3600";    
 
   UeberBackend::go();
@@ -172,34 +177,33 @@ void loadMainConfig(const std::string& configdir)
 
 // irritatingly enough, rectifyZone needs its own ueberbackend and can't therefore benefit from transactions outside its scope
 // I think this has to do with interlocking transactions between B and DK, but unsure.
-bool rectifyZone(DNSSECKeeper& dk, const std::string& zone)
+bool rectifyZone(DNSSECKeeper& dk, const DNSName& zone)
 {
   if(dk.isPresigned(zone)){
-    cerr<<"Rectify presigned zone '"<<zone<<"' is not allowed/necessary."<<endl;
+    cerr<<"Rectify presigned zone '"<<zone.toString()<<"' is not allowed/necessary."<<endl;
     return false;
   }
 
   UeberBackend B("default");
   bool doTransaction=true; // but see above
   SOAData sd;
-  sd.db = (DNSBackend*)-1;
 
-  if(!B.getSOA(zone, sd)) {
-    cerr<<"No SOA known for '"<<zone<<"', is such a zone in the database?"<<endl;
+  if(!B.getSOAUncached(zone, sd)) {
+    cerr<<"No SOA known for '"<<zone.toString()<<"', is such a zone in the database?"<<endl;
     return false;
   }
   sd.db->list(zone, sd.domain_id);
 
   DNSResourceRecord rr;
-  set<string> qnames, nsset, dsnames, insnonterm, delnonterm;
-  map<string,bool> nonterm;
+  set<DNSName> qnames, nsset, dsnames, insnonterm, delnonterm;
+  map<DNSName,bool> nonterm;
   bool doent=true;
 
   while(sd.db->get(rr)) {
     if (rr.qtype.getCode())
     {
       qnames.insert(rr.qname);
-      if(rr.qtype.getCode() == QType::NS && !pdns_iequals(rr.qname, zone))
+      if(rr.qtype.getCode() == QType::NS && rr.qname!=zone)
         nsset.insert(rr.qname);
       if(rr.qtype.getCode() == QType::DS)
         dsnames.insert(rr.qname);
@@ -219,9 +223,9 @@ bool rectifyZone(DNSSECKeeper& dk, const std::string& zone)
       cerr<<"Adding NSEC ordering information "<<endl;
     else if(!narrow) {
       if(!isOptOut)
-        cerr<<"Adding NSEC3 hashed ordering information for '"<<zone<<"'"<<endl;
+        cerr<<"Adding NSEC3 hashed ordering information for '"<<zone.toString()<<"'"<<endl;
       else
-        cerr<<"Adding NSEC3 opt-out hashed ordering information for '"<<zone<<"'"<<endl;
+        cerr<<"Adding NSEC3 opt-out hashed ordering information for '"<<zone.toString()<<"'"<<endl;
     } else
       cerr<<"Erasing NSEC3 ordering since we are narrow, only setting 'auth' fields"<<endl;
   }
@@ -237,10 +241,10 @@ bool rectifyZone(DNSSECKeeper& dk, const std::string& zone)
   uint32_t maxent = ::arg().asNum("max-ent-entries");
 
   dononterm:;
-  BOOST_FOREACH(const string& qname, qnames)
+  BOOST_FOREACH(const DNSName& qname, qnames)
   {
     bool auth=true;
-    string shorter(qname);
+    DNSName shorter(qname);
 
     if(realrr) {
       do {
@@ -248,7 +252,7 @@ bool rectifyZone(DNSSECKeeper& dk, const std::string& zone)
           auth=false;
           break;
         }
-      } while(chopOff(shorter));
+      } while(shorter.chopOff());
     }
 
     if(haveNSEC3)
@@ -256,7 +260,7 @@ bool rectifyZone(DNSSECKeeper& dk, const std::string& zone)
       if(!narrow && (realrr || !isOptOut || nonterm.find(qname)->second)) {
         hashed=toBase32Hex(hashQNameWithSalt(ns3pr.d_iterations, ns3pr.d_salt, qname));
         if(g_verbose)
-          cerr<<"'"<<qname<<"' -> '"<< hashed <<"'"<<endl;
+          cerr<<"'"<<qname.toString()<<"' -> '"<< hashed <<"'"<<endl;
         sd.db->updateDNSSECOrderAndAuthAbsolute(sd.domain_id, qname, hashed, auth);
       }
       else {
@@ -286,13 +290,13 @@ bool rectifyZone(DNSSECKeeper& dk, const std::string& zone)
       if(doent)
       {
         shorter=qname;
-        while(!pdns_iequals(shorter, zone) && chopOff(shorter))
+        while(shorter!=zone && shorter.chopOff())
         {
           if(!qnames.count(shorter))
           {
             if(!(maxent))
             {
-              cerr<<"Zone '"<<zone<<"' has too many empty non terminals."<<endl;
+              cerr<<"Zone '"<<zone.toString()<<"' has too many empty non terminals."<<endl;
               insnonterm.clear();
               delnonterm.clear();
               doent=false;
@@ -305,7 +309,7 @@ bool rectifyZone(DNSSECKeeper& dk, const std::string& zone)
               delnonterm.erase(shorter);
 
             if (!nonterm.count(shorter)) {
-              nonterm.insert(pair<string, bool>(shorter, auth));
+              nonterm.insert(pair<DNSName, bool>(shorter, auth));
               --maxent;
             } else if (auth)
               nonterm[shorter]=true;
@@ -326,7 +330,7 @@ bool rectifyZone(DNSSECKeeper& dk, const std::string& zone)
     {
       realrr=false;
       qnames.clear();
-      pair<string,bool> nt;
+      pair<DNSName,bool> nt;
       BOOST_FOREACH(nt, nonterm){
         qnames.insert(nt.first);
       }
@@ -390,33 +394,62 @@ void rectifyAllZones(DNSSECKeeper &dk)
 
   B.getAllDomains(&domainInfo);
   BOOST_FOREACH(DomainInfo di, domainInfo) {
-    cerr<<"Rectifying "<<di.zone<<": ";
+    cerr<<"Rectifying "<<di.zone.toString()<<": ";
     rectifyZone(dk, di.zone);
   }
   cout<<"Rectified "<<domainInfo.size()<<" zones."<<endl;
 }
 
-int checkZone(DNSSECKeeper &dk, UeberBackend &B, const std::string& zone)
+int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone)
 {
   SOAData sd;
-  sd.db=(DNSBackend*)-1;
-  if(!B.getSOA(zone, sd)) {
-    cout<<"[error] No SOA record present, or active, in zone '"<<zone<<"'"<<endl;
-    cout<<"Checked 0 records of '"<<zone<<"', 1 errors, 0 warnings."<<endl;
+  if(!B.getSOAUncached(zone, sd)) {
+    cout<<"[error] No SOA record present, or active, in zone '"<<zone.toString()<<"'"<<endl;
+    cout<<"Checked 0 records of '"<<zone.toString()<<"', 1 errors, 0 warnings."<<endl;
     return 1;
   }
+
+  NSEC3PARAMRecordContent ns3pr;
+  bool narrow = false;
+  bool haveNSEC3 = dk.getNSEC3PARAM(zone, &ns3pr, &narrow);
+  bool isOptOut=(haveNSEC3 && ns3pr.d_flags);
+
+  bool isSecure=dk.isSecuredZone(zone);
   bool presigned=dk.isPresigned(zone);
-  sd.db->list(zone, sd.domain_id, true);
+
   DNSResourceRecord rr;
   uint64_t numrecords=0, numerrors=0, numwarnings=0;
 
+
+  // Check for delegation in parent zone
+  string parent(zone);
+  while(chopOff(parent)) {
+    SOAData sd_p;
+    if(B.getSOAUncached(parent, sd_p)) {
+      bool ns=false;
+      DNSResourceRecord rr;
+      B.lookup(QType(QType::ANY), zone, NULL, sd_p.domain_id);
+      while(B.get(rr))
+        ns |= (rr.qtype == QType::NS);
+      if (!ns) {
+        cerr<<"[Error] No delegation for zone '"<<zone<<"' in parent '"<<parent<<"'"<<endl;
+        numerrors++;
+      }
+      break;
+    }
+  }
+
+
   bool hasNsAtApex = false;
-  set<string> records, cnames, noncnames;
+  set<DNSName> cnames, noncnames, glue, checkglue;
+  set<string> records;
   map<string, unsigned int> ttl;
 
   ostringstream content;
   pair<map<string, unsigned int>::iterator,bool> ret;
 
+  sd.db->list(zone, sd.domain_id, true);
+
   while(sd.db->get(rr)) {
     if(!rr.qtype.getCode())
       continue;
@@ -444,89 +477,103 @@ int checkZone(DNSSECKeeper &dk, UeberBackend &B, const std::string& zone)
       tmp = drc->getZoneRepresentation();
       if (rr.qtype.getCode() != QType::AAAA) {
         if (!pdns_iequals(tmp, rr.content)) {
-          cout<<"[Warning] Parsed and original record content are not equal: "<<rr.qname<<" IN " <<rr.qtype.getName()<< " '" << rr.content<<"' (Content parsed as '"<<tmp<<"')"<<endl;
+          cout<<"[Warning] Parsed and original record content are not equal: "<<rr.qname.toString()<<" IN " <<rr.qtype.getName()<< " '" << rr.content<<"' (Content parsed as '"<<tmp<<"')"<<endl;
           numwarnings++;
         }
       } else {
         struct in6_addr tmpbuf;
         if (inet_pton(AF_INET6, rr.content.c_str(), &tmpbuf) != 1 || rr.content.find('.') != string::npos) {
-          cout<<"[Warning] Following record is not a valid IPv6 address: "<<rr.qname<<" IN " <<rr.qtype.getName()<< " '" << rr.content<<"'"<<endl;
+          cout<<"[Warning] Following record is not a valid IPv6 address: "<<rr.qname.toString()<<" IN " <<rr.qtype.getName()<< " '" << rr.content<<"'"<<endl;
           numwarnings++;
         }
       }
     }
     catch(std::exception& e)
     {
-      cout<<"[Error] Following record had a problem: "<<rr.qname<<" IN " <<rr.qtype.getName()<< " " << rr.content<<endl;
+      cout<<"[Error] Following record had a problem: "<<rr.qname.toString()<<" IN " <<rr.qtype.getName()<< " " << rr.content<<endl;
       cout<<"[Error] Error was: "<<e.what()<<endl;
       numerrors++;
       continue;
     }
 
-    if(!endsOn(rr.qname, zone)) {
-      cout<<"[Warning] Record '"<<rr.qname<<" IN "<<rr.qtype.getName()<<" "<<rr.content<<"' in zone '"<<zone<<"' is out-of-zone."<<endl;
+    if(!rr.qname.isPartOf(zone)) {
+      cout<<"[Warning] Record '"<<rr.qname.toString()<<" IN "<<rr.qtype.getName()<<" "<<rr.content<<"' in zone '"<<zone.toString()<<"' is out-of-zone."<<endl;
       numwarnings++;
       continue;
     }
 
     content.str("");
-    content<<rr.qname<<" "<<rr.qtype.getName()<<" "<<rr.content;
+    content<<rr.qname.toString()<<" "<<rr.qtype.getName()<<" "<<rr.content;
     if (records.count(toLower(content.str()))) {
-      cout<<"[Error] Duplicate record found in rrset: '"<<rr.qname<<" IN "<<rr.qtype.getName()<<" "<<rr.content<<"'"<<endl;
+      cout<<"[Error] Duplicate record found in rrset: '"<<rr.qname.toString()<<" IN "<<rr.qtype.getName()<<" "<<rr.content<<"'"<<endl;
       numerrors++;
       continue;
     } else
       records.insert(toLower(content.str()));
 
     content.str("");
-    content<<rr.qname<<" "<<rr.qtype.getName();
+    content<<rr.qname.toString()<<" "<<rr.qtype.getName();
+    if (rr.qtype.getCode() == QType::RRSIG) {
+      RRSIGRecordContent rrc(rr.content);
+      content<<" ("<<DNSRecordContent::NumberToType(rrc.d_type)<<")";
+    }
     ret = ttl.insert(pair<string, unsigned int>(toLower(content.str()), rr.ttl));
     if (ret.second == false && ret.first->second != rr.ttl) {
-      cout<<"[Error] TTL mismatch in rrset: '"<<rr.qname<<" IN " <<rr.qtype.getName()<<" "<<rr.content<<"' ("<<ret.first->second<<" != "<<rr.ttl<<")"<<endl;
+      cout<<"[Error] TTL mismatch in rrset: '"<<rr.qname.toString()<<" IN " <<rr.qtype.getName()<<" "<<rr.content<<"' ("<<ret.first->second<<" != "<<rr.ttl<<")"<<endl;
       numerrors++;
       continue;
     }
 
-    if(pdns_iequals(rr.qname, zone)) {
+    if (isSecure && isOptOut && (rr.qname.countLabels() && rr.qname.getRawLabels()[0] == "*")) {
+      cout<<"[Warning] wildcard record '"<<rr.qname.toString()<<" IN " <<rr.qtype.getName()<<" "<<rr.content<<"' is insecure"<<endl;
+      cout<<"[Info] Wildcard records in opt-out zones are insecure. Disable the opt-out flag for this zone to avoid this warning. Command: pdnssec set-nsec3 "<<zone.toString()<<endl;
+      numwarnings++;
+    }
+
+    if(rr.qname==zone) {
       if (rr.qtype.getCode() == QType::NS) {
         hasNsAtApex=true;
       } else if (rr.qtype.getCode() == QType::DS) {
-        cout<<"[Warning] DS at apex in zone '"<<zone<<"', should no be here."<<endl;
+        cout<<"[Warning] DS at apex in zone '"<<zone.toString()<<"', should not be here."<<endl;
         numwarnings++;
       }
     } else {
       if (rr.qtype.getCode() == QType::SOA) {
-        cout<<"[Error] SOA record not at apex '"<<rr.qname<<" IN "<<rr.qtype.getName()<<" "<<rr.content<<"' in zone '"<<zone<<"'"<<endl;
+        cout<<"[Error] SOA record not at apex '"<<rr.qname.toString()<<" IN "<<rr.qtype.getName()<<" "<<rr.content<<"' in zone '"<<zone.toString()<<"'"<<endl;
         numerrors++;
         continue;
       } else if (rr.qtype.getCode() == QType::DNSKEY) {
-        cout<<"[Warning] DNSKEY record not at apex '"<<rr.qname<<" IN "<<rr.qtype.getName()<<" "<<rr.content<<"' in zone '"<<zone<<"', should not be here."<<endl;
+        cout<<"[Warning] DNSKEY record not at apex '"<<rr.qname.toString()<<" IN "<<rr.qtype.getName()<<" "<<rr.content<<"' in zone '"<<zone.toString()<<"', should not be here."<<endl;
         numwarnings++;
+      } else if (rr.qtype.getCode() == QType::NS && endsOn(rr.content, rr.qname)) {
+        checkglue.insert(toLower(rr.content));
+      } else if (rr.qtype.getCode() == QType::A || rr.qtype.getCode() == QType::AAAA) {
+        glue.insert(toLower(rr.qname));
       }
     }
 
     if (rr.qtype.getCode() == QType::CNAME) {
-      if (!cnames.count(toLower(rr.qname)))
-        cnames.insert(toLower(rr.qname));
+      if (!cnames.count(rr.qname))
+        cnames.insert(rr.qname);
       else {
-        cout<<"[Error] Duplicate CNAME found at '"<<rr.qname<<"'"<<endl;
+        cout<<"[Error] Duplicate CNAME found at '"<<rr.qname.toString()<<"'"<<endl;
         numerrors++;
         continue;
       }
     } else {
       if (rr.qtype.getCode() == QType::RRSIG) {
         if(!presigned) {
-          cout<<"[Error] RRSIG found at '"<<rr.qname<<"' in non-presigned zone. These do not belong in the database."<<endl;
+          cout<<"[Error] RRSIG found at '"<<rr.qname.toString()<<"' in non-presigned zone. These do not belong in the database."<<endl;
           numerrors++;
           continue;
         }
       } else
-        noncnames.insert(toLower(rr.qname));
+        noncnames.insert(rr.qname);
     }
 
     if(rr.qtype.getCode() == QType::NSEC || rr.qtype.getCode() == QType::NSEC3)
     {
-      cout<<"[Error] NSEC or NSEC3 found at '"<<rr.qname<<"'. These do not belong in the database."<<endl;
+      cout<<"[Error] NSEC or NSEC3 found at '"<<rr.qname.toString()<<"'. These do not belong in the database."<<endl;
       numerrors++;
       continue;
     }
@@ -537,54 +584,55 @@ int checkZone(DNSSECKeeper &dk, UeberBackend &B, const std::string& zone)
       {
         if(rr.ttl != sd.default_ttl)
         {
-          cout<<"[Warning] DNSKEY TTL of "<<rr.ttl<<" at '"<<rr.qname<<"' differs from SOA minimum of "<<sd.default_ttl<<endl;
+          cout<<"[Warning] DNSKEY TTL of "<<rr.ttl<<" at '"<<rr.qname.toString()<<"' differs from SOA minimum of "<<sd.default_ttl<<endl;
           numwarnings++;
         }
       }
       else
       {
-        cout<<"[Warning] DNSKEY at '"<<rr.qname<<"' in non-presigned zone will mostly be ignored and can cause problems."<<endl;
+        cout<<"[Warning] DNSKEY at '"<<rr.qname.toString()<<"' in non-presigned zone will mostly be ignored and can cause problems."<<endl;
         numwarnings++;
       }
     }
 
-    if(rr.qtype.getCode() == QType::URL || rr.qtype.getCode() == QType::MBOXFW) {
-      cout<<"[Error] The recordtype "<<rr.qtype.getName()<<" for record '"<<rr.qname<<"' is no longer supported."<<endl;
-      numerrors++;
-      continue;
-    }
-
-    if (rr.qname[rr.qname.size()-1] == '.') {
-      cout<<"[Error] Record '"<<rr.qname<<"' has a trailing dot. PowerDNS will ignore this record!"<<endl;
-      numerrors++;
-    }
+    // if (rr.qname[rr.qname.size()-1] == '.') {
+    //   cout<<"[Error] Record '"<<rr.qname.toString()<<"' has a trailing dot. PowerDNS will ignore this record!"<<endl;
+    //   numerrors++;
+    // }
 
     if ( (rr.qtype.getCode() == QType::NS || rr.qtype.getCode() == QType::SRV || rr.qtype.getCode() == QType::MX || rr.qtype.getCode() == QType::CNAME) &&
          rr.content[rr.content.size()-1] == '.') {
-      cout<<"[Warning] The record "<<rr.qname<<" with type "<<rr.qtype.getName()<<" has a trailing dot in the content ("<<rr.content<<"). Your backend might not work well with this."<<endl;
+      cout<<"[Warning] The record "<<rr.qname.toString()<<" with type "<<rr.qtype.getName()<<" has a trailing dot in the content ("<<rr.content<<"). Your backend might not work well with this."<<endl;
       numwarnings++;
     }
 
     if(rr.auth == 0 && rr.qtype.getCode()!=QType::NS && rr.qtype.getCode()!=QType::A && rr.qtype.getCode()!=QType::AAAA)
     {
-      cout<<"[Error] Following record is auth=0, run pdnssec rectify-zone?: "<<rr.qname<<" IN " <<rr.qtype.getName()<< " " << rr.content<<endl;
+      cout<<"[Error] Following record is auth=0, run pdnssec rectify-zone?: "<<rr.qname.toString()<<" IN " <<rr.qtype.getName()<< " " << rr.content<<endl;
       numerrors++;
     }
   }
 
-  for(set<string>::const_iterator i = cnames.begin(); i != cnames.end(); i++) {
-    if (noncnames.find(*i) != noncnames.end()) {
-      cout<<"[Error] CNAME "<<*i<<" found, but other records with same label exist."<<endl;
+  for(auto &i: cnames) {
+    if (noncnames.find(i) != noncnames.end()) {
+      cout<<"[Error] CNAME "<<i.toString()<<" found, but other records with same label exist."<<endl;
       numerrors++;
     }
   }
 
   if(!hasNsAtApex) {
-    cout<<"[Error] No NS record at zone apex in zone '"<<zone<<"'"<<endl;
+    cout<<"[Error] No NS record at zone apex in zone '"<<zone.toString()<<"'"<<endl;
     numerrors++;
   }
 
-  cout<<"Checked "<<numrecords<<" records of '"<<zone<<"', "<<numerrors<<" errors, "<<numwarnings<<" warnings."<<endl;
+  for(const auto &qname : checkglue) {
+    if (!glue.count(qname)) {
+      cerr<<"[Warning] Missing glue for '"<<qname.toString()<<"' in zone '"<<zone.toString()<<"'"<<endl;
+      numwarnings++;
+    }
+  }
+
+  cout<<"Checked "<<numrecords<<" records of '"<<zone.toString()<<"', "<<numerrors<<" errors, "<<numwarnings<<" warnings."<<endl;
   return numerrors;
 }
 
@@ -603,13 +651,12 @@ int checkAllZones(DNSSECKeeper &dk)
   return 0;
 }
 
-int increaseSerial(const string& zone, DNSSECKeeper &dk)
+int increaseSerial(const DNSName& zone, DNSSECKeeper &dk)
 {
   UeberBackend B("default");
   SOAData sd;
-  sd.db=(DNSBackend*)-1;
-  if(!B.getSOA(zone, sd)) {
-    cout<<"No SOA for zone '"<<zone<<"'"<<endl;
+  if(!B.getSOAUncached(zone, sd)) {
+    cout<<"No SOA for zone '"<<zone.toString()<<"'"<<endl;
     return -1;
   }
   
@@ -625,11 +672,11 @@ int increaseSerial(const string& zone, DNSSECKeeper &dk)
   } 
 
   if (rrs.size() > 1) {
-    cerr<<rrs.size()<<" SOA records found for "<<zone<<"!"<<endl;
+    cerr<<rrs.size()<<" SOA records found for "<<zone.toString()<<"!"<<endl;
     return -1;
   }
   if (rrs.size() < 1) {
-     cerr<<zone<<" not found!"<<endl;
+     cerr<<zone.toString()<<" not found!"<<endl;
   }
   
   if (soaEditKind.empty()) {
@@ -671,7 +718,7 @@ int increaseSerial(const string& zone, DNSSECKeeper &dk)
       if(!narrow) {
         string hashed=toBase32Hex(hashQNameWithSalt(ns3pr.d_iterations, ns3pr.d_salt, rrs[0].qname));
         if(g_verbose)
-          cerr<<"'"<<rrs[0].qname<<"' -> '"<< hashed <<"'"<<endl;
+          cerr<<"'"<<rrs[0].qname.toString()<<"' -> '"<< hashed <<"'"<<endl;
         sd.db->updateDNSSECOrderAndAuthAbsolute(sd.domain_id, rrs[0].qname, hashed, 1);
       }
       else {
@@ -684,25 +731,100 @@ int increaseSerial(const string& zone, DNSSECKeeper &dk)
 
   sd.db->commitTransaction();
 
-  cout<<"SOA serial for zone "<<zone<<" set to "<<sd.serial<<endl;
+  cout<<"SOA serial for zone "<<zone.toString()<<" set to "<<sd.serial<<endl;
   return 0;
 }
 
-int deleteZone(const string &zone) {
+int deleteZone(const DNSName &zone) {
   UeberBackend B;
   DomainInfo di;
   if (! B.getDomainInfo(zone, di)) {
-    cerr<<"Domain '"<<zone<<"' not found!"<<endl;
+    cerr<<"Domain '"<<zone.toString()<<"' not found!"<<endl;
     return 1;
   }
 
   if(di.backend->deleteDomain(zone))
     return 0;
 
-  cerr<<"Failed to delete domain '"+zone+"'"<<endl;;
+  cerr<<"Failed to delete domain '"<<zone.toString()<<"'"<<endl;;
+  return 1;
+}
+
+int listZone(const DNSName &zone) {
+  UeberBackend B;
+  DomainInfo di;
+  
+  if (! B.getDomainInfo(zone, di)) {
+    cerr<<"Domain '"<<zone.toString()<<"' not found!"<<endl;
+    return 1;
+  }
+  di.backend->list(zone, di.id);
+  DNSResourceRecord rr;
+  while(di.backend->get(rr)) {
+    if(rr.qtype.getCode()) {
+      if ( (rr.qtype.getCode() == QType::NS || rr.qtype.getCode() == QType::SRV || rr.qtype.getCode() == QType::MX || rr.qtype.getCode() == QType::CNAME) && !rr.content.empty() && rr.content[rr.content.size()-1] != '.') 
+       rr.content.append(1, '.');
+       
+      cout<<rr.qname.toString()<<".\t"<<rr.ttl<<"\tIN\t"<<rr.qtype.getName()<<"\t"<<rr.content<<endl;
+    }
+  }
+  return 0;
+}
+
+int loadZone(DNSName zone, const string& fname) {
+  UeberBackend B;
+  DomainInfo di;
+
+  if (B.getDomainInfo(zone, di)) {
+    cerr<<"Domain '"<<zone.toString()<<"' exists already, replacing contents"<<endl;
+  }
+  else {
+    cerr<<"Creating '"<<zone.toString()<<"'"<<endl;
+    B.createDomain(zone);
+    
+    if(!B.getDomainInfo(zone, di)) {
+      cerr<<"Domain '"<<zone.toString()<<"' was not created!"<<endl;
+      return 1;
+    }
+  }
+  DNSBackend* db = di.backend;
+  ZoneParserTNG zpt(fname, zone);
+  
+  DNSResourceRecord rr;
+  if(!db->startTransaction(zone, di.id)) {
+    cerr<<"Unable to start transaction for load of zone '"<<zone.toString()<<"'"<<endl;
+    return 1;
+  }
+  rr.domain_id=di.id;  
+  while(zpt.get(rr)) {
+    if(!rr.qname.isPartOf(zone) && rr.qname!=zone) {
+      cerr<<"File contains record named '"<<rr.qname.toString()<<"' which is not part of zone '"<<zone.toString()<<"'"<<endl;
+      return 1;
+    }
+    db->feedRecord(rr);
+  }
+  db->commitTransaction();
+  return 0;
+}
+
+int createZone(const DNSName &zone) {
+  UeberBackend B;
+  DomainInfo di;
+  if (B.getDomainInfo(zone, di)) {
+    cerr<<"Domain '"<<zone.toString()<<"' exists already"<<endl;
+    return 1;
+  }
+  cerr<<"Creating '"<<zone.toString()<<"'"<<endl;
+  B.createDomain(zone);
+
+  if(!B.getDomainInfo(zone, di)) {
+    cerr<<"Domain '"<<zone.toString()<<"' was not created!"<<endl;
+    return 1;
+  }
   return 1;
 }
 
+
 int listAllZones(const string &type="") {
 
   int kindFilter = -1;
@@ -727,7 +849,7 @@ int listAllZones(const string &type="") {
   int count = 0;
   for (vector<DomainInfo>::const_iterator di=domains.begin(); di != domains.end(); di++) {
     if (di->kind == kindFilter || kindFilter == -1) {
-      cout<<di->zone<<endl;
+      cout<<di->zone.toString()<<endl;
       count++;
     }
   }
@@ -803,7 +925,7 @@ void verifyCrypto(const string& zone)
   RRSIGRecordContent rrc;
   DSRecordContent dsrc;
   vector<shared_ptr<DNSRecordContent> > toSign;
-  string qname, apex;
+  DNSName qname, apex;
   dsrc.d_digesttype=0;
   while(zpt.get(rr)) {
     if(rr.qtype.getCode() == QType::DNSKEY) {
@@ -828,8 +950,8 @@ void verifyCrypto(const string& zone)
   string msg = getMessageForRRSET(qname, rrc, toSign);        
   cerr<<"Verify: "<<DNSCryptoKeyEngine::makeFromPublicKeyString(drc.d_algorithm, drc.d_key)->verify(msg, rrc.d_signature)<<endl;
   if(dsrc.d_digesttype) {
-    cerr<<"Calculated DS: "<<apex<<" IN DS "<<makeDSFromDNSKey(apex, drc, dsrc.d_digesttype).getZoneRepresentation()<<endl;
-    cerr<<"Original DS:   "<<apex<<" IN DS "<<dsrc.getZoneRepresentation()<<endl;
+    cerr<<"Calculated DS: "<<apex.toString()<<" IN DS "<<makeDSFromDNSKey(apex, drc, dsrc.d_digesttype).getZoneRepresentation()<<endl;
+    cerr<<"Original DS:   "<<apex.toString()<<" IN DS "<<dsrc.getZoneRepresentation()<<endl;
   }
 #if 0
   DNSCryptoKeyEngine*key=DNSCryptoKeyEngine::makeFromISCString(drc, "Private-key-format: v1.2\n"
@@ -841,7 +963,7 @@ void verifyCrypto(const string& zone)
 #endif
 
 }
-bool disableDNSSECOnZone(DNSSECKeeper& dk, const string& zone)
+bool disableDNSSECOnZone(DNSSECKeeper& dk, const DNSName& zone)
 {
   UeberBackend B("default");
   DomainInfo di;
@@ -858,7 +980,7 @@ bool disableDNSSECOnZone(DNSSECKeeper& dk, const string& zone)
   DNSSECKeeper::keyset_t keyset=dk.getKeys(zone);
 
   if(keyset.empty())  {
-    cerr << "No keys for zone '"<<zone<<"'."<<endl;
+    cerr << "No keys for zone '"<<zone.toString()<<"'."<<endl;
   }
   else {  
     BOOST_FOREACH(DNSSECKeeper::keyset_t::value_type value, keyset) {
@@ -870,7 +992,7 @@ bool disableDNSSECOnZone(DNSSECKeeper& dk, const string& zone)
   dk.unsetPresigned(zone);
   return true;
 }
-bool showZone(DNSSECKeeper& dk, const std::string& zone)
+bool showZone(DNSSECKeeper& dk, const DNSName& zone)
 {
   UeberBackend B("default");
   DomainInfo di;
@@ -901,7 +1023,7 @@ bool showZone(DNSSECKeeper& dk, const std::string& zone)
   cout <<"Zone is " << (dk.isPresigned(zone) ? "" : "not ") << "presigned"<<endl;
 
   if(keyset.empty())  {
-    cerr << "No keys for zone '"<<zone<<"'."<<endl;
+    cerr << "No keys for zone '"<<zone.toString()<<"'."<<endl;
   }
   else {  
     if(!haveNSEC3) 
@@ -913,23 +1035,27 @@ bool showZone(DNSSECKeeper& dk, const std::string& zone)
     BOOST_FOREACH(DNSSECKeeper::keyset_t::value_type value, keyset) {
       string algname;
       algorithm2name(value.first.d_algorithm, algname);
+      if (value.first.getKey()->getBits() < 1) {
+        cout<<"ID = "<<value.second.id<<" ("<<(value.second.keyOrZone ? "KSK" : "ZSK")<<") <key missing or defunct>" <<endl;
+        continue;
+      }
       cout<<"ID = "<<value.second.id<<" ("<<(value.second.keyOrZone ? "KSK" : "ZSK")<<"), tag = "<<value.first.getDNSKEY().getTag();
       cout<<", algo = "<<(int)value.first.d_algorithm<<", bits = "<<value.first.getKey()->getBits()<<"\tActive: "<<value.second.active<< " ( " + algname + " ) "<<endl;
       if(value.second.keyOrZone || ::arg().mustDo("direct-dnskey") || g_verbose)
-        cout<<(value.second.keyOrZone ? "KSK" : "ZSK")<<" DNSKEY = "<<zone<<" IN DNSKEY "<< value.first.getDNSKEY().getZoneRepresentation() << " ; ( "  + algname + " )" << endl;
+        cout<<(value.second.keyOrZone ? "KSK" : "ZSK")<<" DNSKEY = "<<zone.toString()<<" IN DNSKEY "<< value.first.getDNSKEY().getZoneRepresentation() << " ; ( "  + algname + " )" << endl;
       if(value.second.keyOrZone || g_verbose) {
-        cout<<"DS = "<<zone<<" IN DS "<<makeDSFromDNSKey(zone, value.first.getDNSKEY(), 1).getZoneRepresentation() << " ; ( SHA1 digest )" << endl;
-        cout<<"DS = "<<zone<<" IN DS "<<makeDSFromDNSKey(zone, value.first.getDNSKEY(), 2).getZoneRepresentation() << " ; ( SHA256 digest )" << endl;
+        cout<<"DS = "<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, value.first.getDNSKEY(), 1).getZoneRepresentation() << " ; ( SHA1 digest )" << endl;
+        cout<<"DS = "<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, value.first.getDNSKEY(), 2).getZoneRepresentation() << " ; ( SHA256 digest )" << endl;
         try {
           string output=makeDSFromDNSKey(zone, value.first.getDNSKEY(), 3).getZoneRepresentation();
-          cout<<"DS = "<<zone<<" IN DS "<< output << " ; ( GOST R 34.11-94 digest )" << endl;
+          cout<<"DS = "<<zone.toString()<<" IN DS "<< output << " ; ( GOST R 34.11-94 digest )" << endl;
         }
         catch(...)
         {
         }
         try {
           string output=makeDSFromDNSKey(zone, value.first.getDNSKEY(), 4).getZoneRepresentation();
-          cout<<"DS = "<<zone<<" IN DS "<< output << " ; ( SHA-384 digest )" << endl;
+          cout<<"DS = "<<zone.toString()<<" IN DS "<< output << " ; ( SHA-384 digest )" << endl;
         }
         catch(...)
         {
@@ -941,7 +1067,7 @@ bool showZone(DNSSECKeeper& dk, const std::string& zone)
   return true;
 }
 
-bool secureZone(DNSSECKeeper& dk, const std::string& zone)
+bool secureZone(DNSSECKeeper& dk, const DNSName& zone)
 {
   // parse attribute
   vector<string> k_algos;
@@ -971,21 +1097,21 @@ bool secureZone(DNSSECKeeper& dk, const std::string& zone)
   }
 
   if(dk.isSecuredZone(zone)) {
-    cerr << "Zone '"<<zone<<"' already secure, remove keys with pdnssec remove-zone-key if needed"<<endl;
+    cerr << "Zone '"<<zone.toString()<<"' already secure, remove keys with pdnssec remove-zone-key if needed"<<endl;
     return false;
   }
 
   DomainInfo di;
   UeberBackend B("default");
   if(!B.getDomainInfo(zone, di) || !di.backend) { // di.backend and B are mostly identical
-    cout<<"Can't find a zone called '"<<zone<<"'"<<endl;
+    cout<<"Can't find a zone called '"<<zone.toString()<<"'"<<endl;
     return false;
   }
 
   if(di.kind == DomainInfo::Slave)
   {
     cout<<"Warning! This is a slave domain! If this was a mistake, please run"<<endl;
-    cout<<"pdnssec disable-dnssec "<<zone<<" right now!"<<endl;
+    cout<<"pdnssec disable-dnssec "<<zone.toString()<<" right now!"<<endl;
   }
 
   if (k_size)
@@ -995,7 +1121,7 @@ bool secureZone(DNSSECKeeper& dk, const std::string& zone)
 
   // run secure-zone with first default algorith, then add keys
   if(!dk.secureZone(zone, shorthand2algorithm(k_algos[0]), k_size)) {
-    cerr<<"No backend was able to secure '"<<zone<<"', most likely because no DNSSEC"<<endl;
+    cerr<<"No backend was able to secure '"<<zone.toString()<<"', most likely because no DNSSEC"<<endl;
     cerr<<"capable backends are loaded, or because the backends have DNSSEC disabled."<<endl;
     cerr<<"For the Generic SQL backends, set the 'gsqlite3-dnssec', 'gmysql-dnssec' or"<<endl;
     cerr<<"'gpgsql-dnssec' flag. Also make sure the schema has been updated for DNSSEC!"<<endl;
@@ -1014,7 +1140,7 @@ bool secureZone(DNSSECKeeper& dk, const std::string& zone)
   DNSSECKeeper::keyset_t zskset=dk.getKeys(zone, false);
 
   if(!zskset.empty())  {
-    cerr<<"There were ZSKs already for zone '"<<zone<<"', no need to add more"<<endl;
+    cerr<<"There were ZSKs already for zone '"<<zone.toString()<<"', no need to add more"<<endl;
     return false;
   }
   
@@ -1029,11 +1155,11 @@ bool secureZone(DNSSECKeeper& dk, const std::string& zone)
 
   // rectifyZone(dk, zone);
   // showZone(dk, zone);
-  cout<<"Zone "<<zone<<" secured"<<endl;
+  cout<<"Zone "<<zone.toString()<<" secured"<<endl;
   return true;
 }
 
-void testSchema(DNSSECKeeper& dk, const std::string& zone)
+void testSchema(DNSSECKeeper& dk, const DNSName& zone)
 {
   cout<<"Note: test-schema will try to create the zone, but it will not remove it."<<endl;
   cout<<"Please clean up after this."<<endl;
@@ -1042,7 +1168,7 @@ void testSchema(DNSSECKeeper& dk, const std::string& zone)
   UeberBackend B("default");
   cout<<"Picking first backend - if this is not what you want, edit launch line!"<<endl;
   DNSBackend *db = B.backends[0];
-  cout<<"Creating slave domain "<<zone<<endl;
+  cout<<"Creating slave domain "<<zone.toString()<<endl;
   db->createSlaveDomain("127.0.0.1", zone, "", "_testschema");
   cout<<"Slave domain created"<<endl;
 
@@ -1116,22 +1242,22 @@ void testSchema(DNSSECKeeper& dk, const std::string& zone)
   cout<<"Rectifying zone"<<endl;
   rectifyZone(dk, zone);
   cout<<"Checking underscore ordering"<<endl;
-  string before, after;
+  DNSName before, after;
   db->getBeforeAndAfterNames(di.id, zone, "z."+zone, before, after);
-  cout<<"got '"<<before<<"' < 'z."<<zone<<"' < '"<<after<<"'"<<endl;
+  cout<<"got '"<<before.toString()<<"' < 'z."<<zone.toString()<<"' < '"<<after.toString()<<"'"<<endl;
   if(before != "_underscore."+zone)
   {
-    cout<<"before is wrong, got '"<<before<<"', expected '_underscore."<<zone<<"', aborting"<<endl;
+    cout<<"before is wrong, got '"<<before.toString()<<"', expected '_underscore."<<zone.toString()<<"', aborting"<<endl;
     return;
   }
   if(after != zone)
   {
-    cout<<"after is wrong, got '"<<after<<"', expected '"<<zone<<"', aborting"<<endl;
+    cout<<"after is wrong, got '"<<after.toString()<<"', expected '"<<zone.toString()<<"', aborting"<<endl;
     return;
   }
   cout<<"[+] ordername sorting is correct for names starting with _"<<endl;
   cout<<endl;
-  cout<<"End of tests, please remove "<<zone<<" from domains+records"<<endl;
+  cout<<"End of tests, please remove "<<zone.toString()<<" from domains+records"<<endl;
 }
 
 int main(int argc, char** argv)
@@ -1167,10 +1293,12 @@ try
     cerr<<"add-zone-key ZONE zsk|ksk [bits] [active|passive]"<<endl;
     cerr<<"             [rsasha1|rsasha256|rsasha512|gost|ecdsa256|ecdsa384]"<<endl;
     cerr<<"                                   Add a ZSK or KSK to zone and specify algo&bits"<<endl;
+    cerr<<"b2b-migrate old new                Move all data from one backend to another"<<endl;
     cerr<<"bench-db [filename]                Bench database backend with queries, one domain per line"<<endl;
     cerr<<"check-zone ZONE                    Check a zone for correctness"<<endl;
     cerr<<"check-all-zones                    Check all zones for correctness"<<endl;
     cerr<<"create-bind-db FNAME               Create DNSSEC db for BIND backend (bind-dnssec-db)"<<endl;
+    cerr<<"create-zone ZONE                   Create empty zone ZONE"<<endl;
     cerr<<"deactivate-tsig-key ZONE NAME [master|slave]"<<endl;
     cerr<<"                                   Disable TSIG key for a zone"<<endl;
     cerr<<"deactivate-zone-key ZONE KEY-ID    Deactivate the key with key id KEY-ID in ZONE"<<endl;
@@ -1185,17 +1313,20 @@ try
     cerr<<"get-meta ZONE [kind kind ..]       Get zone metadata. If no KIND given, lists all known"<<endl;
     cerr<<"hash-zone-record ZONE RNAME        Calculate the NSEC3 hash for RNAME in ZONE"<<endl;
 #ifdef HAVE_P11KIT1
-    cerr<<"hsm assign zone zsk|ksk module slot pin label"<<endl<<
+    cerr<<"hsm assign zone algorithm ksk|zsk module slot pin label"<<endl<<
           "                                   Assign a hardware signing module to a ZONE"<<endl;
-    cerr<<"hsm create-key zone [bits]         Create a key using hardware signing module for ZONE (use assign first)"<<endl; 
+    cerr<<"hsm create-key zone key-id [bits]  Create a key using hardware signing module for ZONE (use assign first)"<<endl; 
     cerr<<"                                   bits defaults to 2048"<<endl;
 #endif
     cerr<<"increase-serial ZONE               Increases the SOA-serial by 1. Uses SOA-EDIT"<<endl;
     cerr<<"import-tsig-key NAME ALGORITHM KEY Import TSIG key"<<endl;
     cerr<<"import-zone-key ZONE FILE          Import from a file a private key, ZSK or KSK"<<endl;
     cerr<<"       [active|passive][ksk|zsk]   Defaults to KSK and active"<<endl;
+    cerr<<"load-zone ZONE FILE                Load ZONE from FILE, possibly creating zone or atomically"<<endl;
+    cerr<<"                                   replacing contents"<<endl;
+    cerr<<"list-zone ZONE                     List zone contents"<<endl;
     cerr<<"list-all-zones [master|slave|native]"<<endl;
-    cerr<<"                                   List all zones"<<endl;;
+    cerr<<"                                   List all zone names"<<endl;;
     cerr<<"list-tsig-keys                     List all TSIG keys"<<endl;
     cerr<<"rectify-zone ZONE [ZONE ..]        Fix up DNSSEC fields (order, auth)"<<endl;
     cerr<<"rectify-all-zones                  Rectify all zones."<<endl;
@@ -1241,8 +1372,9 @@ try
       SSQLite3 db(cmds[1], true); // create=ok
       vector<string> statements;
       stringtok(statements, sqlCreate, ";");
-      BOOST_FOREACH(const string& statement, statements)
-        db.doCommand(statement);
+      BOOST_FOREACH(const string& statement, statements) {
+        db.execute(statement);
+      }
     }
     catch(SSqlException& se) {
       throw PDNSException("Error creating database in BIND backend: "+se.txtReason());
@@ -1457,6 +1589,33 @@ try
     }
     exit(deleteZone(cmds[1]));
   }
+  else if(cmds[0] == "create-zone") {
+    if(cmds.size() != 2) {
+      cerr<<"Syntax: pdnssec create-zone ZONE"<<endl;
+      return 0;
+    }
+    exit(createZone(cmds[1]));
+  }
+  else if(cmds[0] == "list-zone") {
+    if(cmds.size() != 2) {
+      cerr<<"Syntax: pdnssec list-zone ZONE"<<endl;
+      return 0;
+    }
+    if(cmds[1]==".")
+      cmds[1].clear();
+
+    exit(listZone(cmds[1]));
+  }
+  else if(cmds[0] == "load-zone") {
+    if(cmds.size() != 3) {
+      cerr<<"Syntax: pdnssec load-zone ZONE FILENAME"<<endl;
+      return 0;
+    }
+    if(cmds[1]==".")
+      cmds[1].clear();
+
+    exit(loadZone(cmds[1], cmds[2]));
+  }
   else if(cmds[0] == "secure-zone") {
     if(cmds.size() < 2) {
       cerr << "Syntax: pdnssec secure-zone ZONE"<<endl;
@@ -1497,7 +1656,7 @@ try
     unsigned int zonesSecured=0, zoneErrors=0;
     BOOST_FOREACH(DomainInfo di, domainInfo) {
       if(!dk.isSecuredZone(di.zone)) {
-        cout<<"Securing "<<di.zone<<": ";
+        cout<<"Securing "<<di.zone.toString()<<": ";
         if (secureZone(dk, di.zone)) {
           zonesSecured++;
           if (cmds.size() == 2) {
@@ -1526,9 +1685,9 @@ try
     bool narrow = cmds.size() > 3 && cmds[3]=="narrow";
     NSEC3PARAMRecordContent ns3pr(nsec3params);
     
-    string zone=cmds[1];
+    DNSName zone(cmds[1]);
     if(!dk.isSecuredZone(zone)) {
-      cerr<<"Zone '"<<zone<<"' is not secured, can't set NSEC3 parameters"<<endl;
+      cerr<<"Zone '"<<zone.toString()<<"' is not secured, can't set NSEC3 parameters"<<endl;
       exit(EXIT_FAILURE);
     }
     dk.setNSEC3PARAM(zone, ns3pr, narrow);
@@ -1565,16 +1724,16 @@ try
       cerr<<"Syntax: pdnssec hash-zone-record ZONE RNAME"<<endl;
       return 0;
     }
-    string& zone=cmds[1];
+    DNSName zone(cmds[1]);
     string& record=cmds[2];
     NSEC3PARAMRecordContent ns3pr;
     bool narrow;
     if(!dk.getNSEC3PARAM(zone, &ns3pr, &narrow)) {
-      cerr<<"The '"<<zone<<"' zone does not use NSEC3"<<endl;
+      cerr<<"The '"<<zone.toString()<<"' zone does not use NSEC3"<<endl;
       return 0;
     }
     if(narrow) {
-      cerr<<"The '"<<zone<<"' zone uses narrow NSEC3, but calculating hash anyhow"<<endl;
+      cerr<<"The '"<<zone.toString()<<"' zone uses narrow NSEC3, but calculating hash anyhow"<<endl;
     }
       
     cout<<toBase32Hex(hashQNameWithSalt(ns3pr.d_iterations, ns3pr.d_salt, record))<<endl;
@@ -1840,7 +1999,7 @@ try
      UeberBackend B("default");
      if (B.getTSIGKeys(keys)) {
         BOOST_FOREACH(const struct TSIGKey &key, keys) {
-           cout << key.name << " " << key.algorithm << " " << key.key << endl;
+           cout << key.name.toString() << " " << key.algorithm.toString() << " " << key.key << endl;
         }
      }
      return 0;
@@ -1968,6 +2127,7 @@ try
     if (cmds[1] == "assign") {
       DNSCryptoKeyEngine::storvector_t storvect;
       DomainInfo di;
+      std::vector<DNSBackend::KeyData> keys;
 
       if (cmds.size() < 9) {
         std::cout << "Usage: pdnssec hsm assign zone algorithm ksk|zsk module slot pin label" << std::endl;
@@ -1983,6 +2143,11 @@ try
       }
 
       int algorithm = shorthand2algorithm(cmds[3]);
+      if (algorithm<0) {
+        cerr << "Unable to use unknown algorithm '" << cmds[3] << "'" << std::endl;
+        return 1;
+      }
+
       int id;
       bool keyOrZone = (cmds[4] == "ksk" ? true : false);
       string module = cmds[5];
@@ -2003,12 +2168,43 @@ try
      dpk.d_flags = (keyOrZone ? 257 : 256);
      dpk.setKey(shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromISCString(drc, iscString.str())));
  
+     // make sure this key isn't being reused.
+     B.getDomainKeys(zone, 0, keys);
+     id = -1;
+
+     BOOST_FOREACH(DNSBackend::KeyData& kd, keys) {
+       if (kd.content == iscString.str()) {
+         // it's this one, I guess...
+         id = kd.id;
+         break;
+       }
+     }
+
+     if (id > -1) {
+       cerr << "You have already assigned this key with ID=" << id << std::endl;
+       return 1;
+     }
+
      if (!(id = dk.addKey(zone, dpk))) {
        cerr << "Unable to assign module slot to zone" << std::endl;
        return 1;
      }
 
+     // figure out key id.
+
+     B.getDomainKeys(zone, 0, keys);
+
+     // validate which one got the key...
+     BOOST_FOREACH(DNSBackend::KeyData& kd, keys) {
+       if (kd.content == iscString.str()) {
+         // it's this one, I guess...
+         id = kd.id;
+         break;
+       }
+     }
+
      cerr << "Module " << module << " slot " << slot << " assigned to " << zone << " with key id " << id << endl;
+
      return 0;
     } else if (cmds[1] == "create-key") {
 
@@ -2068,6 +2264,101 @@ try
     cerr<<"PKCS#11 support not enabled"<<endl;
     return 1; 
 #endif
+  } else if (cmds[0] == "b2b-migrate") {
+    if (cmds.size() < 3) {
+      cerr<<"Usage: b2b-migrate old new"<<endl;
+      return 1;
+    }
+
+    DNSBackend *src,*tgt;
+    src = tgt = NULL;
+
+    for(DNSBackend *b : BackendMakers().all()) {
+      if (b->getPrefix() == cmds[1]) src = b;
+      if (b->getPrefix() == cmds[2]) tgt = b;
+    }
+    if (!src) {
+      cerr<<"Unknown source backend '"<<cmds[1]<<"'"<<endl;
+      return 1;
+    }
+    if (!tgt) {
+      cerr<<"Unknown target backend '"<<cmds[2]<<"'"<<endl;
+      return 1;
+    }
+
+    cout<<"Moving zone(s) from "<<src->getPrefix()<<" to "<<tgt->getPrefix()<<endl;
+
+    vector<DomainInfo> domains;
+
+    tgt->getAllDomains(&domains, true);
+    if (domains.size()>0)
+      throw PDNSException("Target backend has domain(s), please clean it first");
+
+    src->getAllDomains(&domains, true);
+    // iterate zones
+    for(const DomainInfo& di: domains) {
+      size_t nr,nc,nm,nk;
+      DNSResourceRecord rr;
+      cout<<"Processing '"<<di.zone<<"'"<<endl;
+      // create zone
+      if (!tgt->createDomain(di.zone)) throw PDNSException("Failed to create zone");
+      tgt->setKind(di.zone, di.kind);
+      tgt->setAccount(di.zone,di.account);
+      for(const string& master: di.masters) {
+        tgt->setMaster(di.zone, master);
+      }
+      // move records
+      if (!src->list(di.zone, di.id, true)) throw PDNSException("Failed to list records");
+      nr=0;
+      while(src->get(rr)) {
+        if (!tgt->feedRecord(rr)) throw PDNSException("Failed to feed record");
+        nr++;
+      }
+      // move comments
+      nc=0;
+      if (src->listComments(di.id)) {
+        Comment c;
+        while(src->getComment(c)) {
+          tgt->feedComment(c);
+          nc++;
+        }
+      }
+      // move metadata
+      nm=0;
+      std::map<std::string, std::vector<std::string> > meta;
+      if (src->getAllDomainMetadata(di.zone, meta)) {
+        std::map<std::string, std::vector<std::string> >::iterator i;
+        for(i=meta.begin(); i != meta.end(); i++) {
+          if (!tgt->setDomainMetadata(di.zone, i->first, i->second)) throw PDNSException("Failed to feed domain metadata");
+          nm++;
+        }
+      }
+      // move keys
+      nk=0;
+      std::vector<DNSBackend::KeyData> keys;
+      if (src->getDomainKeys(di.zone, 0, keys)) {
+        for(const DNSBackend::KeyData& k: keys) {
+          tgt->addDomainKey(di.zone, k);
+          nk++;
+        }
+      }
+      cout<<"Moved "<<nr<<" record(s), "<<nc<<" comment(s), "<<nm<<" metadata(s) and "<<nk<<" cryptokey(s)"<<endl;
+    }
+
+    int ntk=0;
+    // move tsig keys
+    std::vector<struct TSIGKey> tkeys;
+    if (src->getTSIGKeys(tkeys)) {
+      for(const struct TSIGKey& tk: tkeys) {
+        if (!tgt->setTSIGKey(tk.name, tk.algorithm, tk.key)) throw PDNSException("Failed to feed TSIG key");
+        ntk++;
+      }
+    }
+    cout<<"Moved "<<ntk<<" TSIG key(s)"<<endl;
+
+    cout<<"Remember to drop the old backend and run rectify-all-zones"<<endl;
+
+    return 0;
   } else {
     cerr<<"Unknown command '"<<cmds[0] <<"'"<< endl;
     return 1;
@@ -2082,4 +2373,8 @@ catch(std::exception& e) {
   cerr<<"Error: "<<e.what()<<endl;
   return 1;
 }
-
+catch(...)
+{
+  cerr<<"Caught an unknown exception"<<endl;
+  return 1;
+}