]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
auth: implement consumer support
authorKees Monshouwer <mind04@monshouwer.org>
Wed, 6 Jul 2022 16:48:37 +0000 (18:48 +0200)
committermind04 <mind04@monshouwer.org>
Tue, 12 Jul 2022 22:11:30 +0000 (00:11 +0200)
modules/gmysqlbackend/gmysqlbackend.cc
modules/godbcbackend/godbcbackend.cc
modules/gpgsqlbackend/gpgsqlbackend.cc
modules/gsqlite3backend/gsqlite3backend.cc
modules/lmdbbackend/lmdbbackend.cc
pdns/auth-catalogzone.hh
pdns/backends/gsql/gsqlbackend.cc
pdns/slavecommunicator.cc
regression-tests/backends/gmysql-slave
regression-tests/backends/godbc_sqlite3-master
regression-tests/backends/lmdb-slave

index 05bf4a349ade3e0703e473883b096e3d2c139f25..b908df5c6832fba2e5e0c27c1c28f5aade7de913 100644 (file)
@@ -133,8 +133,8 @@ public:
     declare(suffix, "update-serial-query", "", "update domains set notified_serial=? where id=?");
     declare(suffix, "update-lastcheck-query", "", "update domains set last_check=? where id=?");
     declare(suffix, "info-all-master-query", "", "select d.id, d.name, d.type, d.notified_serial,d.options, d.catalog,r.content from records r join domains d on r.domain_id=d.id and r.name=d.name where r.type='SOA' and r.disabled=0 and d.type in ('MASTER', 'PRODUCER')");
-    declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.catalog=? and records.type='SOA' and records.disabled=0");
-    declare(suffix, "info-consumer-members-query", "", "select domains.id, domains.name, domains.options from domains left join records ON records.domain_id=domains.id and records.type='SOA' and records.name=domains.name where domains.catalog=?'");
+    declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.type='MASTER' and domains.catalog=? and records.type='SOA' and records.disabled=0");
+    declare(suffix, "info-consumer-members-query", "", "select id, name, options, master from domains where type='SLAVE' and catalog=?");
     declare(suffix, "delete-domain-query", "", "delete from domains where name=?");
     declare(suffix, "delete-zone-query", "", "delete from records where domain_id=?");
     declare(suffix, "delete-rrset-query", "", "delete from records where domain_id=? and name=? and type=?");
index f37f47dfd32d07772c939029498880180fa71037..6d0cc6b5856f9b564a876aff5855084f93095d16 100644 (file)
@@ -113,8 +113,8 @@ public:
     declare(suffix, "update-serial-query", "", "update domains set notified_serial=? where id=?");
     declare(suffix, "update-lastcheck-query", "", "update domains set last_check=? where id=?");
     declare(suffix, "info-all-master-query", "", "select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type in ('MASTER', 'PRODUCER')");
-    declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.catalog=? and records.type='SOA' and records.disabled=0");
-    declare(suffix, "info-consumer-members-query", "", "select domains.id, domains.name, domains.options from domains left join records ON records.domain_id=domains.id and records.type='SOA' and records.name=domains.name where domains.catalog=?'");
+    declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.type='MASTER' and domains.catalog=? and records.type='SOA' and records.disabled=0");
+    declare(suffix, "info-consumer-members-query", "", "select id, name, options, master from domains where type='SLAVE' and catalog=?");
     declare(suffix, "delete-domain-query", "", "delete from domains where name=?");
     declare(suffix, "delete-zone-query", "", "delete from records where domain_id=?");
     declare(suffix, "delete-rrset-query", "", "delete from records where domain_id=? and name=? and type=?");
index 6edd00fdf457762b42c1b9985093b79c55d78d1b..9160b64e9052e8a442e0bfa363bed140a32b7b03 100644 (file)
@@ -140,8 +140,8 @@ public:
     declare(suffix, "update-serial-query", "", "update domains set notified_serial=$1 where id=$2");
     declare(suffix, "update-lastcheck-query", "", "update domains set last_check=$1 where id=$2");
     declare(suffix, "info-all-master-query", "", "select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=false and domains.type in ('MASTER', 'PRODUCER')");
-    declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.catalog=$1 and records.type='SOA' and records.disabled=0");
-    declare(suffix, "info-consumer-members-query", "", "select domains.id, domains.name, domains.options from domains left join records ON records.domain_id=domains.id and records.type='SOA' and records.name=domains.name where domains.catalog=$1'");
+    declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.type='MASTER' and domains.catalog=$1 and records.type='SOA' and records.disabled=0");
+    declare(suffix, "info-consumer-members-query", "", "select id, name, options, master from domains where type='SLAVE' and catalog=$1");
     declare(suffix, "delete-domain-query", "", "delete from domains where name=$1");
     declare(suffix, "delete-zone-query", "", "delete from records where domain_id=$1");
     declare(suffix, "delete-rrset-query", "", "delete from records where domain_id=$1 and name=$2 and type=$3");
index abdf3286c9ffa888b84878bcfabce83924c508e2..df3aff2714f5d451ea595a58109df5d0829e9cb6 100644 (file)
@@ -126,8 +126,8 @@ public:
     declare(suffix, "update-serial-query", "", "update domains set notified_serial=:serial where id=:domain_id");
     declare(suffix, "update-lastcheck-query", "", "update domains set last_check=:last_check where id=:domain_id");
     declare(suffix, "info-all-master-query", "", "select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type in ('MASTER', 'PRODUCER')");
-    declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.catalog:catalog and records.type='SOA' and records.disabled=0");
-    declare(suffix, "info-consumer-members-query", "", "select domains.id, domains.name, domains.options from domains left join records ON records.domain_id=domains.id and records.type='SOA' and records.name=domains.name where domains.catalog:catalog'");
+    declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.type='MASTER' and domains.catalog:catalog and records.type='SOA' and records.disabled=0");
+    declare(suffix, "info-consumer-members-query", "", "select id, name, options, master from domains where type='SLAVE' and catalog:catalog");
     declare(suffix, "delete-domain-query", "", "delete from domains where name=:domain");
     declare(suffix, "delete-zone-query", "", "delete from records where domain_id=:domain_id");
     declare(suffix, "delete-rrset-query", "", "delete from records where domain_id=:domain_id and name=:qname and type=:qtype");
index 5c1a3a7289c0b4581cc6dd5eac4cb2e3fdae6d7c..514cbccacb3e7d672dfe2993f5784599d1e06627 100644 (file)
@@ -679,8 +679,12 @@ bool LMDBBackend::deleteDomain(const DNSName& domain)
     id = txn.get<0>(domain, di);
   }
 
-  if (!d_rwtxn || d_transactiondomainid != id) {
-    throw DBException(std::string(__PRETTY_FUNCTION__) + " called without a proper transaction");
+  if (!d_rwtxn) {
+    throw DBException(std::string(__PRETTY_FUNCTION__) + " called without a transaction");
+  }
+  if (d_transactiondomainid != id) {
+    commitTransaction();
+    startTransaction(domain);
   }
 
   { // Remove metadata
@@ -1128,6 +1132,7 @@ bool LMDBBackend::getCatalogMembers(const DNSName& catalog, vector<CatalogInfo>&
     CatalogInfo ci;
     ci.d_id = iter->id;
     ci.d_zone = iter->zone;
+    ci.d_primaries = iter->masters;
     try {
       ci.fromJson(iter->options, type);
     }
index adfb9fc3b199ef1f475f63807b157c3a3d6ddb4d..26805e71f2dc38161f8dbfaab88cac05a32034ee 100644 (file)
@@ -58,6 +58,7 @@ public:
 
   void fromJson(const std::string& json, CatalogType type);
   std::string toJson() const;
+  void setType(CatalogType type) { d_type = type; }
 
   void updateHash(CatalogHashMap& hashes, const DomainInfo& di) const;
   DNSName getUnique() const { return DNSName(toBase32Hex(hashQNameWithSalt(std::to_string(d_id), 0, d_zone))); } // salt with domain id to detect recreated zones
@@ -72,6 +73,7 @@ public:
   uint32_t d_id;
   DNSName d_zone, d_coo, d_unique;
   std::set<std::string> d_group;
+  vector<ComboAddress> d_primaries;
 
 private:
   CatalogType d_type;
index f589aa3e5ea3a8d86bef47802b041e73d55c6c02..90b1be27e76d4860c10694b7be7abcae7045f328 100644 (file)
@@ -628,7 +628,7 @@ bool GSQLBackend::getCatalogMembers(const DNSName& catalog, vector<CatalogInfo>&
     }
     else if (type == CatalogInfo::CatalogType::Consumer) {
       // clang-format off
-      d_InfoProducerMembersQuery_stmt->
+      d_InfoConsumerMembersQuery_stmt->
         bind("catalog", catalog)->
         execute()->
         getResult(d_result)->
@@ -643,8 +643,13 @@ bool GSQLBackend::getCatalogMembers(const DNSName& catalog, vector<CatalogInfo>&
     throw PDNSException(std::string(__PRETTY_FUNCTION__) + " unable to retrieve list of member zones: " + e.txtReason());
   }
 
-  for (const auto& row : d_result) { // id, zone, options
-    ASSERT_ROW_COLUMNS("info-producer/consumer-members-query", row, 3);
+  for (const auto& row : d_result) { // id, zone, options, [master]
+    if (type == CatalogInfo::CatalogType::Producer) {
+      ASSERT_ROW_COLUMNS("info-producer/consumer-members-query", row, 3);
+    }
+    else {
+      ASSERT_ROW_COLUMNS("info-producer/consumer-members-query", row, 4);
+    }
 
     CatalogInfo ci;
 
@@ -680,6 +685,21 @@ bool GSQLBackend::getCatalogMembers(const DNSName& catalog, vector<CatalogInfo>&
       return false;
     }
 
+    if (row.size() >= 4) { // Consumer only
+      vector<string> masters;
+      stringtok(masters, row[3], ", \t");
+      for (const auto& m : masters) {
+        try {
+          ci.d_primaries.emplace_back(m, 53);
+        }
+        catch (const PDNSException& e) {
+          g_log << Logger::Warning << "Could not parse master address '" << m << "' for zone '" << ci.d_zone << "': " << e.reason << endl;
+          members.clear();
+          return false;
+        }
+      }
+    }
+
     members.emplace_back(ci);
   }
   return true;
index 79b0d3955097c28d92f15b1bba2bcb40fe2931fb..c76d6dbfe38c4e9c887a7f3270ad92c03ab685b9 100644 (file)
@@ -86,6 +86,311 @@ struct ZoneStatus
   int numDeltas{0};
 };
 
+static bool catalogDiff(const DomainInfo& di, vector<CatalogInfo>& fromXFR, vector<CatalogInfo>& fromDB, const string& logPrefix)
+{
+  extern CommunicatorClass Communicator;
+
+  bool doTransaction{true};
+  bool inTransaction{false};
+  bool doOptions{false};
+  CatalogInfo ciCreate, ciRemove;
+  vector<CatalogInfo> retrieve;
+
+  try {
+    sort(fromXFR.begin(), fromXFR.end());
+    sort(fromDB.begin(), fromDB.end());
+
+    auto xfr = fromXFR.cbegin();
+    auto db = fromDB.cbegin();
+
+    while (xfr != fromXFR.end() || db != fromDB.end()) {
+      bool create{false};
+      bool remove{false};
+
+      if ((xfr != fromXFR.end() && db == fromDB.end()) || *xfr < *db) { // create
+        ciCreate = *xfr;
+        create = true;
+        ++xfr;
+      }
+      else if ((db != fromDB.end() && xfr == fromXFR.end()) || *db < *xfr) { // remove
+        ciRemove = *db;
+        remove = true;
+        ++db;
+      }
+      else {
+        CatalogInfo ciXFR = *xfr;
+        CatalogInfo ciDB = *db;
+        if (ciXFR.d_unique == ciDB.d_unique) { // update
+
+          if (ciXFR.d_coo != ciDB.d_coo) { // update coo
+            g_log << Logger::Warning << logPrefix << "update coo for zone '" << ciXFR.d_zone << "' to '" << ciXFR.d_coo << "'" << endl;
+            ciDB.d_coo = ciXFR.d_coo;
+            doOptions = true;
+          }
+
+          if (ciXFR.d_group != ciDB.d_group) { // update group
+            g_log << Logger::Warning << logPrefix << "update group for zone '" << ciXFR.d_zone << "' to '" << boost::join(ciXFR.d_group, ", ") << "'" << endl;
+            ciDB.d_group = ciXFR.d_group;
+            doOptions = true;
+          }
+
+          if (doOptions) { // update zone options
+            if (doTransaction && (inTransaction = di.backend->startTransaction(di.zone))) {
+              g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
+              doTransaction = false;
+            }
+
+            g_log << Logger::Warning << logPrefix << "update options for zone '" << ciXFR.d_zone << "'" << endl;
+            di.backend->setOptions(ciXFR.d_zone, ciDB.toJson());
+          }
+
+          if (di.masters != ciDB.d_primaries) { // update primaries
+            if (doTransaction && (inTransaction = di.backend->startTransaction(di.zone))) {
+              g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
+              doTransaction = false;
+            }
+
+            vector<string> primaries;
+            for (const auto& primary : di.masters) {
+              primaries.push_back(primary.toStringWithPortExcept(53));
+            }
+            g_log << Logger::Warning << logPrefix << "update primaries for zone '" << ciXFR.d_zone << "' to '" << boost::join(primaries, ", ") << "'" << endl;
+            di.backend->setMasters(ciXFR.d_zone, di.masters);
+
+            retrieve.emplace_back(ciXFR);
+          }
+        }
+        else { // reset
+          ciCreate = *xfr;
+          ciRemove = *db;
+          create = true;
+          remove = true;
+        }
+        ++xfr;
+        ++db;
+      }
+
+      DomainInfo d;
+      if (create && remove) {
+        g_log << Logger::Warning << logPrefix << "zone '" << ciCreate.d_zone << "' state reset" << endl;
+      }
+      else if (create && di.backend->getDomainInfo(ciCreate.d_zone, d)) { // detect clash
+        CatalogInfo ci;
+        ci.fromJson(d.options, CatalogInfo::CatalogType::Consumer);
+
+        if (di.zone != d.catalog && di.zone == ci.d_coo) {
+          if (ciCreate.d_unique == ci.d_unique) {
+            g_log << Logger::Warning << logPrefix << "zone '" << d.zone << "' owner change without state reset, old catalog '" << d.catalog << "', new catalog '" << di.zone << "'" << endl;
+
+            if (doTransaction && (inTransaction = di.backend->startTransaction(di.zone))) {
+              g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
+              doTransaction = false;
+            }
+
+            di.backend->setMasters(ciCreate.d_zone, di.masters);
+            di.backend->setOptions(ciCreate.d_zone, ciCreate.toJson());
+            di.backend->setCatalog(ciCreate.d_zone, di.zone);
+
+            retrieve.emplace_back(ciCreate);
+            continue;
+          }
+          g_log << Logger::Warning << logPrefix << "zone '" << d.zone << "' owner change with state reset, old catalog '" << d.catalog << "', new catalog '" << di.zone << "'" << endl;
+
+          ciRemove.d_zone = d.zone;
+          remove = true;
+        }
+        else {
+          g_log << Logger::Warning << logPrefix << "zone '" << d.zone << "' already exists";
+          if (!d.catalog.empty()) {
+            g_log << " in catalog '" << d.catalog;
+          }
+          g_log << "', create skipped" << endl;
+          continue;
+        }
+      }
+
+      if (remove) { // delete zone
+        if (doTransaction && (inTransaction = di.backend->startTransaction(di.zone))) {
+          g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
+          doTransaction = false;
+        }
+
+        g_log << Logger::Warning << logPrefix << "delete zone '" << ciRemove.d_zone << "'" << endl;
+        di.backend->deleteDomain(ciRemove.d_zone);
+
+        if (g_zoneCache.isEnabled()) {
+          g_zoneCache.remove(ciRemove.d_zone);
+        }
+      }
+
+      if (create) { // create zone
+        if (doTransaction && (inTransaction = di.backend->startTransaction(ciCreate.d_zone))) {
+          g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
+          doTransaction = false;
+        }
+
+        g_log << Logger::Warning << logPrefix << "create zone '" << ciCreate.d_zone << "'" << endl;
+        di.backend->createDomain(ciCreate.d_zone, DomainInfo::Slave, ciCreate.d_primaries, "");
+
+        di.backend->setMasters(ciCreate.d_zone, di.masters);
+        di.backend->setOptions(ciCreate.d_zone, ciCreate.toJson());
+        di.backend->setCatalog(ciCreate.d_zone, di.zone);
+
+        retrieve.emplace_back(ciCreate);
+
+        if (g_zoneCache.isEnabled()) {
+          if (di.backend->getDomainInfo(ciCreate.d_zone, d)) {
+            g_zoneCache.add(ciCreate.d_zone, d.id);
+          }
+          else {
+            g_log << Logger::Error << logPrefix << "new zone '" << ciCreate.d_zone << "' does not exists and was not inserted in the zone-cache" << endl;
+          }
+        }
+      }
+    }
+
+    if (inTransaction && di.backend->commitTransaction()) {
+      g_log << Logger::Warning << logPrefix << "backend transaction committed" << endl;
+    }
+
+    // retrieve new and updated zones with new primaries
+    auto masters = di.masters;
+    if (!masters.empty()) {
+      for (auto& ret : retrieve) {
+        shuffle(masters.begin(), masters.end(), pdns::dns_random_engine());
+        const auto& master = masters.front();
+        Communicator.addSuckRequest(ret.d_zone, master, SuckRequest::Notify);
+      }
+    }
+
+    return true;
+  }
+  catch (DBException& re) {
+    g_log << Logger::Error << logPrefix << "DBException " << re.reason << endl;
+  }
+  catch (PDNSException& pe) {
+    g_log << Logger::Error << logPrefix << "PDNSException " << pe.reason << endl;
+  }
+  catch (std::exception& re) {
+    g_log << Logger::Error << logPrefix << "std::exception " << re.what() << endl;
+  }
+
+  if (di.backend && inTransaction) {
+    g_log << Logger::Info << logPrefix << "aborting possible open transaction" << endl;
+    di.backend->abortTransaction();
+  }
+
+  return false;
+}
+
+static bool catalogProcess(const DomainInfo& di, vector<DNSResourceRecord>& rrs, string logPrefix)
+{
+  logPrefix += "Catalog-Zone ";
+
+  vector<CatalogInfo> fromXFR, fromDB;
+  std::unordered_set<DNSName> dupcheck;
+
+  // From XFR
+  bool hasSOA{false};
+  bool hasVersion{false};
+  bool zoneInvalid{false};
+
+  CatalogInfo ci;
+
+  vector<DNSResourceRecord> ret;
+
+  const auto compare = [](const DNSResourceRecord& a, const DNSResourceRecord& b) { return a.qname == b.qname ? a.qtype < b.qtype : a.qname.canonCompare(b.qname); };
+  sort(rrs.begin(), rrs.end(), compare);
+
+  DNSName rel;
+  DNSName unique;
+  for (auto& rr : rrs) {
+    if (di.zone == rr.qname) {
+      if (rr.qtype == QType::SOA) {
+        hasSOA = true;
+        continue;
+      }
+    }
+
+    else if (rr.qname == DNSName("version") + di.zone && rr.qtype == QType::TXT) {
+      if (rr.content == "\"2\"") {
+        hasVersion = true;
+        continue;
+      }
+      else {
+        g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', unsupported catalog zone schema version " << rr.content << ", aborting" << endl;
+        return false;
+      }
+    }
+
+    else if (rr.qname.isPartOf(DNSName("zones") + di.zone)) {
+      if (rel.empty() && !hasVersion) {
+        g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', catalog zone schema version missing, aborting" << endl;
+        return false;
+      }
+
+      rel = rr.qname.makeRelative(DNSName("zones") + di.zone);
+
+      if (rel.countLabels() == 1 && rr.qtype == QType::PTR) {
+        if (!unique.empty()) {
+          if (rel != unique) {
+            fromXFR.emplace_back(ci);
+          }
+          else {
+            g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', duplicate unique '" << unique << "'" << endl;
+            zoneInvalid = true;
+          }
+        }
+
+        unique = rel;
+
+        ci = {};
+        ci.setType(CatalogInfo::CatalogType::Consumer);
+        ci.d_zone = DNSName(rr.content);
+        ci.d_unique = unique;
+
+        if (!dupcheck.insert(ci.d_zone).second) {
+          g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', duplicate member zone'" << ci.d_zone << "'" << endl;
+          zoneInvalid = true;
+        }
+      }
+
+      else if (rel == (DNSName("coo") + unique) && rr.qtype == QType::PTR) {
+        if (!ci.d_coo.empty()) {
+          g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', duplicate COO for unique '" << unique << "'" << endl;
+          zoneInvalid = true;
+        }
+        else {
+          ci.d_coo = DNSName(rr.content);
+        }
+      }
+      else if (rel == (DNSName("group") + unique) && rr.qtype == QType::TXT) {
+        std::string content = rr.content;
+        if (content.length() >= 2 && content.at(0) == '\"' && content.at(content.length() - 1) == '\"') { // TXT pain
+          content = content.substr(1, content.length() - 2);
+        }
+        ci.d_group.insert(content);
+      }
+    }
+    rr.disabled = true;
+  }
+  if (!ci.d_zone.empty()) {
+    fromXFR.emplace_back(ci);
+  }
+
+  if (!hasSOA || !hasVersion || zoneInvalid) {
+    g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "' is invalid, skip updates" << endl;
+    return false;
+  }
+
+  // Get catalog ifo from db
+  if (!di.backend->getCatalogMembers(di.zone, fromDB, CatalogInfo::CatalogType::Consumer)) {
+    return false;
+  }
+
+  // Process
+  return catalogDiff(di, fromXFR, fromDB, logPrefix);
+}
 
 void CommunicatorClass::ixfrSuck(const DNSName &domain, const TSIGTriplet& tt, const ComboAddress& laddr, const ComboAddress& remote, unique_ptr<AuthLua4>& pdl,
                                  ZoneStatus& zs, vector<DNSRecord>* axfr)
@@ -327,9 +632,9 @@ void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote,
     DNSSECKeeper dk (&B); // reuse our UeberBackend copy for DNSSECKeeper
     bool wrongDomainKind = false;
     // this checks three error conditions & sets wrongDomainKind if we hit the third
-    if(!B.getDomainInfo(domain, di) || !di.backend || (wrongDomainKind = true, !force && di.kind != DomainInfo::Slave)) { // di.backend and B are mostly identical
+    if (!B.getDomainInfo(domain, di) || !di.backend || (wrongDomainKind = true, !force && !di.isSecondaryType())) { // di.backend and B are mostly identical
       if(wrongDomainKind)
-        g_log<<Logger::Warning<<logPrefix<<"can't determine backend, not configured as slave"<<endl;
+        g_log << Logger::Warning << logPrefix << "can't determine backend, not configured as secondary" << endl;
       else
         g_log<<Logger::Warning<<logPrefix<<"can't determine backend"<<endl;
       return;
@@ -454,6 +759,12 @@ void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote,
       g_log<<Logger::Notice<<logPrefix<<"retrieval finished"<<endl;
     }
 
+    if (di.kind == DomainInfo::Consumer) {
+      if (!catalogProcess(di, rrs, logPrefix)) {
+        g_log << Logger::Warning << logPrefix << "Catalog-Zone update failed, only import records" << endl;
+      }
+    }
+
     if(zs.isNSEC3) {
       zs.ns3pr.d_flags = zs.optOutFlag ? 1 : 0;
     }
index e4dd2e64173a485f8e2c1839e8dfea7702381369..a6c21ab619abbef23a7751987e655acba0a5f2cb 100644 (file)
@@ -27,21 +27,49 @@ __EOF__
                echo "gmysql-dnssec" >> pdns-gmysql2.conf
        fi
 
+       zones=0
        for zone in $(grep 'zone ' named.conf  | cut -f2 -d\" | perl -e 'print reverse <STDIN>')
        do
-               mysql --user="$GMYSQL2USER" --password="$GMYSQL2PASSWD" --host="$GMYSQL2HOST" \
-                       "$GMYSQL2DB" -e "INSERT INTO domains (name, type, master) VALUES('$zone','SLAVE','127.0.0.1:$port')"
+               zones=$((zones+1))
+               if [ "$zone" = "example.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 create-slave-zone $zone 127.0.0.1:$port
+               fi
+               if [ "$zone" = "test.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 create-slave-zone $zone 127.0.0.1:$port
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-catalog $zone other-catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-options $zone '{"consumer":{"coo":"catalog.invalid","unique":"42"}}'
+               fi
                if [ "$zone" = "tsig.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 create-slave-zone $zone 127.0.0.2:$port
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-catalog $zone catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-options $zone "{\"consumer\":{\"unique\":\"$($SAXFR 127.0.0.1 $port catalog.invalid | grep $zone | grep PTR | cut -d'.' -f1)\"}}"
                        $PDNSUTIL --config-dir=. --config-name=gmysql2 import-tsig-key test $ALGORITHM $KEY
                        $PDNSUTIL --config-dir=. --config-name=gmysql2 activate-tsig-key tsig.com test slave
                fi
                if [ "$zone" = "stest.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 create-slave-zone $zone 127.0.0.1:$port
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-catalog $zone other-catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-options $zone "{\"consumer\":{\"coo\":\"catalog.invalid\",\"unique\":\"$($SAXFR 127.0.0.1 $port catalog.invalid | grep $zone | grep PTR | cut -d'.' -f1)\"}}"
                        if [[ $skipreasons != *nolua* ]]; then
                                $PDNSUTIL --config-dir=. --config-name=gmysql2 set-meta stest.com AXFR-SOURCE 127.0.0.2
                        fi
                fi
+               if [ "$zone" = "." ]; then
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 create-slave-zone $zone 127.0.0.1:$port
+               fi
        done
 
+       # setup catalog zone
+       if [ $zones -ne 1 ] # detect root tests
+       then
+               zones=$((zones+1))
+               $PDNSUTIL --config-dir=. --config-name=gmysql2 create-slave-zone catalog.invalid 127.0.0.1:$port
+               $PDNSUTIL --config-dir=. --config-name=gmysql2 set-kind catalog.invalid consumer
+
+               $PDNSUTIL --config-dir=. --config-name=gmysql2 create-slave-zone remove.invalid 127.0.0.1:$port
+               $PDNSUTIL --config-dir=. --config-name=gmysql2 set-catalog remove.invalid catalog.invalid
+       fi
+
        port=$((port+100))
 
        $RUNWRAPPER $PDNS2 --daemon=no --local-port=$port --config-dir=. \
@@ -54,15 +82,15 @@ __EOF__
        while [ $loopcount -lt 30 ]
        do
                sleep 5
-               todo=$(mysql --user="$GMYSQL2USER" --password="$GMYSQL2PASSWD" --host="$GMYSQL2HOST" \
-                       "$GMYSQL2DB" -ss -e 'SELECT COUNT(id) FROM domains WHERE last_check IS NULL')
-               if [ $todo = 0 ]
+               present=$(mysql --user="$GMYSQL2USER" --password="$GMYSQL2PASSWD" --host="$GMYSQL2HOST" \
+                       "$GMYSQL2DB" -ss -e "SELECT COUNT(DISTINCT(name)) FROM records WHERE type='SOA'")
+               if [ $present -eq $zones ]
                then
                        break
                fi
                let loopcount=loopcount+1
        done
-       if [ $todo -ne 0 ]
+       if [ $present -ne $zones ]
        then
                echo "AXFR FAILED" >> failed_tests
                exit
index 3efedc66a79e6aa0664cca4082cd00609105e44b..89c11cb2189e85e4102472b94860e252bed65a16 100644 (file)
@@ -41,9 +41,11 @@ godbc-get-tsig-key-query=select algorithm, secret from tsigkeys where name=?
 godbc-get-tsig-keys-query=select name,algorithm, secret from tsigkeys
 godbc-publish-domain-key-query=update cryptokeys set published=1 where domain_id=(select id from domains where name=?) and  cryptokeys.id=?
 godbc-id-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM records WHERE disabled=0 and type=? and name=? and domain_id=?
-godbc-info-all-master-query=select id,name,master,last_check,notified_serial,type from domains where type='MASTER'
-godbc-info-all-slaves-query=select id,name,master,last_check from domains where type='SLAVE'
-godbc-info-zone-query=select id,name,master,last_check,notified_serial,type,account from domains where name=?
+godbc-info-all-master-query=select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type in ('MASTER', 'PRODUCER')
+godbc-info-all-slaves-query=select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')
+godbc-info-zone-query=select id,name,master,last_check,notified_serial,type,options,catalog,account from domains where name=?
+godbc-info-producer-members-query=select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.type='MASTER' and domains.catalog=? and records.type='SOA' and records.disabled=0
+godbc-info-consumer-members-query=select id, name, options, master from domains where type='SLAVE' and catalog=?
 godbc-insert-comment-query=INSERT INTO comments (domain_id, name, type, modified_at, account, comment) VALUES (?, ?, ?, ?, ?, ?)
 godbc-insert-empty-non-terminal-order-query=insert into records (type,domain_id,disabled,name,ordername,auth,ttl,prio,content) values (null,?,0,?,?,?,null,null,null)
 godbc-insert-record-query=insert into records (content,ttl,prio,type,domain_id,disabled,name,ordername,auth) values (?,?,?,?,?,?,?,?,?)
index 992a8bcf4a83deecef0d2c3c79ad202b430ef9da..7981a654c28b66bc0574532a57a7fd7e7c2ec287 100644 (file)
@@ -9,19 +9,46 @@ __EOF__
        zones=0
        for zone in $(grep 'zone ' named.conf  | cut -f2 -d\" | grep -v '^nztest.com$' | perl -e 'print reverse <STDIN>')
        do
-               let zones=zones+1
-               $PDNSUTIL --config-dir=. --config-name=lmdb2 create-slave-zone $zone 127.0.0.1:$port
+               zones=$((zones+1))
+               if [ "$zone" = "example.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 create-slave-zone $zone 127.0.0.1:$port
+               fi
+               if [ "$zone" = "test.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 create-slave-zone $zone 127.0.0.1:$port
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 set-catalog $zone other-catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 set-options $zone '{"consumer":{"coo":"catalog.invalid","unique":"42"}}'
+               fi
                if [ "$zone" = "tsig.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 create-slave-zone $zone 127.0.0.2:$port
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 set-catalog $zone catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 set-options $zone "{\"consumer\":{\"unique\":\"$($SAXFR 127.0.0.1 $port catalog.invalid | grep $zone | grep PTR | cut -d'.' -f1)\"}}"
                        $PDNSUTIL --config-dir=. --config-name=lmdb2 import-tsig-key test $ALGORITHM $KEY
                        $PDNSUTIL --config-dir=. --config-name=lmdb2 activate-tsig-key tsig.com test slave
                fi
                if [ "$zone" = "stest.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 create-slave-zone $zone 127.0.0.1:$port
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 set-catalog $zone other-catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 set-options $zone "{\"consumer\":{\"coo\":\"catalog.invalid\",\"unique\":\"$($SAXFR 127.0.0.1 $port catalog.invalid | grep $zone | grep PTR | cut -d'.' -f1)\"}}"
                        if [[ $skipreasons != *nolua* ]]; then
                                $PDNSUTIL --config-dir=. --config-name=lmdb2 set-meta stest.com AXFR-SOURCE 127.0.0.2
                        fi
                fi
+               if [ "$zone" = "." ]; then
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 create-slave-zone $zone 127.0.0.1:$port
+               fi
        done
 
+       # setup catalog zone
+       if [ $zones -ne 1 ] # detect root tests
+       then
+               zones=$((zones+1))
+               $PDNSUTIL --config-dir=. --config-name=lmdb2 create-slave-zone catalog.invalid 127.0.0.1:$port
+               $PDNSUTIL --config-dir=. --config-name=lmdb2 set-kind catalog.invalid consumer
+
+               $PDNSUTIL --config-dir=. --config-name=lmdb2 create-slave-zone remove.invalid 127.0.0.1:$port
+               $PDNSUTIL --config-dir=. --config-name=lmdb2 set-catalog remove.invalid catalog.invalid
+       fi
+
        port=$((port+100))
 
        $RUNWRAPPER $PDNS2 --daemon=no --local-port=$port --config-dir=. \