From: Kees Monshouwer Date: Sat, 2 Jul 2022 00:23:08 +0000 (+0200) Subject: auth: implement producer axfr X-Git-Tag: auth-4.8.0-alpha0~7^2~24 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8a66a9272451eb8b0daed88d8f1db5193c6c154e;p=thirdparty%2Fpdns.git auth: implement producer axfr --- diff --git a/modules/gmysqlbackend/gmysqlbackend.cc b/modules/gmysqlbackend/gmysqlbackend.cc index d0ff7295bc..05bf4a349a 100644 --- a/modules/gmysqlbackend/gmysqlbackend.cc +++ b/modules/gmysqlbackend/gmysqlbackend.cc @@ -133,6 +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, "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=?"); diff --git a/modules/godbcbackend/godbcbackend.cc b/modules/godbcbackend/godbcbackend.cc index 0a75093068..f37f47dfd3 100644 --- a/modules/godbcbackend/godbcbackend.cc +++ b/modules/godbcbackend/godbcbackend.cc @@ -113,6 +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, "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=?"); diff --git a/modules/gpgsqlbackend/gpgsqlbackend.cc b/modules/gpgsqlbackend/gpgsqlbackend.cc index 409e83fe2e..6edd00fdf4 100644 --- a/modules/gpgsqlbackend/gpgsqlbackend.cc +++ b/modules/gpgsqlbackend/gpgsqlbackend.cc @@ -140,6 +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, "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"); diff --git a/modules/gsqlite3backend/gsqlite3backend.cc b/modules/gsqlite3backend/gsqlite3backend.cc index b3103f8b54..abdf3286c9 100644 --- a/modules/gsqlite3backend/gsqlite3backend.cc +++ b/modules/gsqlite3backend/gsqlite3backend.cc @@ -126,6 +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, "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"); diff --git a/modules/lmdbbackend/lmdbbackend.cc b/modules/lmdbbackend/lmdbbackend.cc index b617aee34a..5c1a3a7289 100644 --- a/modules/lmdbbackend/lmdbbackend.cc +++ b/modules/lmdbbackend/lmdbbackend.cc @@ -23,7 +23,6 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif -#include "pdns/auth-catalogzone.hh" #include "pdns/utility.hh" #include "pdns/dnsbackend.hh" #include "pdns/dns.hh" @@ -978,20 +977,6 @@ bool LMDBBackend::setKind(const DNSName& domain, const DomainInfo::DomainKind ki }); } -bool LMDBBackend::setOptions(const DNSName& domain, const std::string& options) -{ - return genChangeDomain(domain, [options](DomainInfo& di) { - di.options = options; - }); -} - -bool LMDBBackend::setCatalog(const DNSName& domain, const DNSName& catalog) -{ - return genChangeDomain(domain, [catalog](DomainInfo& di) { - di.catalog = catalog; - }); -} - bool LMDBBackend::setAccount(const DNSName& domain, const std::string& account) { return genChangeDomain(domain, [account](DomainInfo& di) { @@ -1054,7 +1039,7 @@ void LMDBBackend::getUnfreshSlaveInfos(vector* domains) auto txn = d_tdomains->getROTransaction(); for (auto iter = txn.begin(); iter != txn.end(); ++iter) { - if (iter->kind != DomainInfo::Slave && iter->kind != DomainInfo::Consumer) { + if (!iter->isSecondaryType()) { continue; } @@ -1103,7 +1088,7 @@ void LMDBBackend::getUpdatedMasters(vector& updatedDomains, std::uno auto txn = d_tdomains->getROTransaction(); for (auto iter = txn.begin(); iter != txn.end(); ++iter) { - if (iter->kind != DomainInfo::Master) { + if (!iter->isPrimaryType()) { continue; } @@ -1132,6 +1117,44 @@ void LMDBBackend::setNotified(uint32_t domain_id, uint32_t serial) }); } +bool LMDBBackend::getCatalogMembers(const DNSName& catalog, vector& members, CatalogInfo::CatalogType type) +{ + auto txn = d_tdomains->getROTransaction(); + for (auto iter = txn.begin(); iter != txn.end(); ++iter) { + if ((type == CatalogInfo::CatalogType::Producer && iter->kind != DomainInfo::Master) || (type == CatalogInfo::CatalogType::Consumer && iter->kind != DomainInfo::Slave) || iter->catalog != catalog) { + continue; + } + + CatalogInfo ci; + ci.d_id = iter->id; + ci.d_zone = iter->zone; + try { + ci.fromJson(iter->options, type); + } + catch (const std::runtime_error& e) { + g_log << Logger::Warning << __PRETTY_FUNCTION__ << " options '" << iter->options << "' for zone '" << iter->zone << "' is no valid JSON: " << e.what() << endl; + members.clear(); + return false; + } + members.emplace_back(ci); + } + return true; +} + +bool LMDBBackend::setOptions(const DNSName& domain, const std::string& options) +{ + return genChangeDomain(domain, [options](DomainInfo& di) { + di.options = options; + }); +} + +bool LMDBBackend::setCatalog(const DNSName& domain, const DNSName& catalog) +{ + return genChangeDomain(domain, [catalog](DomainInfo& di) { + di.catalog = catalog; + }); +} + bool LMDBBackend::getAllDomainMetadata(const DNSName& name, std::map>& meta) { meta.clear(); diff --git a/modules/lmdbbackend/lmdbbackend.hh b/modules/lmdbbackend/lmdbbackend.hh index f395a85480..1bc63e5c4d 100644 --- a/modules/lmdbbackend/lmdbbackend.hh +++ b/modules/lmdbbackend/lmdbbackend.hh @@ -89,6 +89,11 @@ public: void getUpdatedMasters(vector& updatedDomains, std::unordered_set& catalogs, CatalogHashMap& catalogHashes) override; void setNotified(uint32_t id, uint32_t serial) override; + // catalog zones + bool getCatalogMembers(const DNSName& catalog, vector& members, CatalogInfo::CatalogType type) override; + bool setOptions(const DNSName& domain, const std::string& options) override; + bool setCatalog(const DNSName& domain, const DNSName& options) override; + bool setMasters(const DNSName& domain, const vector& masters) override; bool setKind(const DNSName& domain, const DomainInfo::DomainKind kind) override; bool getAllDomainMetadata(const DNSName& name, std::map>& meta) override; @@ -110,8 +115,6 @@ public: } bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector& meta) override; - bool setOptions(const DNSName& domain, const std::string& options) override; - bool setCatalog(const DNSName& domain, const DNSName& options) override; bool setAccount(const DNSName& domain, const std::string& account) override; bool deleteDomain(const DNSName& domain) override; diff --git a/pdns/auth-catalogzone.cc b/pdns/auth-catalogzone.cc index a34851aaeb..03c25b75d2 100644 --- a/pdns/auth-catalogzone.cc +++ b/pdns/auth-catalogzone.cc @@ -24,7 +24,7 @@ #include "config.h" #endif -#include "auth-catalogzone.hh" +#include "dnsbackend.hh" void CatalogInfo::fromJson(const std::string& json, CatalogType type) { @@ -40,21 +40,25 @@ void CatalogInfo::fromJson(const std::string& json, CatalogType type) if (!d_doc.is_null()) { if (!d_doc[getTypeString(d_type)].is_null()) { auto items = d_doc[getTypeString(type)].object_items(); - if (items["coo"].is_string()) { - if (!items["coo"].string_value().empty()) { - this->coo = DNSName(items["coo"].string_value()); + if (!items["coo"].is_null()) { + if (items["coo"].is_string()) { + if (!items["coo"].string_value().empty()) { + d_coo = DNSName(items["coo"].string_value()); + } } - } - else { - throw std::out_of_range("Key 'coo' is not a string"); - } - if (items["unique"].is_string()) { - if (!items["uniq"].string_value().empty()) { - this->unique = DNSName(items["unique"].string_value()); + else { + throw std::out_of_range("Key 'coo' is not a string"); } } - else { - throw std::out_of_range("Key 'unique' is not a string"); + if (!items["unique"].is_null()) { + if (items["unique"].is_string()) { + if (!items["unique"].string_value().empty()) { + d_unique = DNSName(items["unique"].string_value()); + } + } + else { + throw std::out_of_range("Key 'unique' is not a string"); + } } } } @@ -69,11 +73,11 @@ std::string CatalogInfo::toJson() const throw std::runtime_error("CatalogType is set to None"); } json11::Json::object object; - if (!coo.empty()) { - object["coo"] = coo.toString(); + if (!d_coo.empty()) { + object["coo"] = d_coo.toString(); } - if (!unique.empty()) { - object["unique"] = unique.toString(); + if (!d_unique.empty()) { + object["unique"] = d_unique.toString(); } auto tmp = d_doc.object_items(); tmp[getTypeString(d_type)] = object; @@ -83,5 +87,42 @@ std::string CatalogInfo::toJson() const void CatalogInfo::updateHash(CatalogHashMap& hashes, const DomainInfo& di) const { - hashes[di.catalog].process(static_cast(di.id) + di.zone.toLogString() + "\0" + this->coo.toLogString() + "\0" + this->unique.toLogString()); + hashes[di.catalog].process(static_cast(di.id) + di.zone.toLogString() + "\0" + d_coo.toLogString() + "\0" + d_unique.toLogString()); +} + +DNSZoneRecord CatalogInfo::getCatalogVersionRecord(const DNSName& zone) +{ + DNSZoneRecord dzr; + dzr.dr.d_name = DNSName("version") + zone; + dzr.dr.d_ttl = 0; + dzr.dr.d_type = QType::TXT; + dzr.dr.d_content = std::make_shared("2"); + return dzr; +} + +void CatalogInfo::toDNSZoneRecords(const DNSName& zone, vector& dzrs) const +{ + DNSName prefix; + if (d_unique.empty()) { + prefix = getUnique(); + } + else { + prefix = d_unique; + } + prefix += DNSName("zones") + zone; + + DNSZoneRecord dzr; + dzr.dr.d_name = prefix; + dzr.dr.d_ttl = 0; + dzr.dr.d_type = QType::PTR; + dzr.dr.d_content = std::make_shared(d_zone.toString()); + dzrs.emplace_back(dzr); + + if (!d_coo.empty()) { + dzr.dr.d_name = DNSName("coo") + prefix; + dzr.dr.d_ttl = 0; + dzr.dr.d_type = QType::PTR; + dzr.dr.d_content = std::make_shared(d_coo); + dzrs.emplace_back(dzr); + } } diff --git a/pdns/auth-catalogzone.hh b/pdns/auth-catalogzone.hh index f483fab68d..e4862f282a 100644 --- a/pdns/auth-catalogzone.hh +++ b/pdns/auth-catalogzone.hh @@ -23,7 +23,13 @@ #pragma once #include "ext/json11/json11.hpp" +#include "base32.hh" #include "dnsbackend.hh" +#include "dnssecinfra.hh" + +struct DomainInfo; + +typedef map CatalogHashMap; class CatalogInfo { @@ -42,26 +48,29 @@ public: } CatalogInfo() : - d_type(CatalogType::None) {} - CatalogInfo(const DNSName& zone, CatalogType type) + d_id(0), d_type(CatalogType::None) {} + CatalogInfo(uint32_t id, const DNSName& zone, const std::string& options, CatalogType type) { - this->zone = zone; - d_type = type; + d_id = id; + d_zone = zone; + fromJson(options, type); } - void setType(CatalogType type) { d_type = type; } - void fromJson(const std::string& json, CatalogType type); std::string toJson() const; 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 + static DNSZoneRecord getCatalogVersionRecord(const DNSName& zone); + void toDNSZoneRecords(const DNSName& zone, vector& dzrs) const; bool operator<(const CatalogInfo& rhs) const { - return zone < rhs.zone; + return d_zone < rhs.d_zone; } - DNSName coo, unique, zone; + uint32_t d_id; + DNSName d_zone, d_coo, d_unique; private: CatalogType d_type; diff --git a/pdns/backends/gsql/gsqlbackend.cc b/pdns/backends/gsql/gsqlbackend.cc index c18900c4d7..f589aa3e5e 100644 --- a/pdns/backends/gsql/gsqlbackend.cc +++ b/pdns/backends/gsql/gsqlbackend.cc @@ -22,7 +22,6 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif -#include "pdns/auth-catalogzone.hh" #include "pdns/dns.hh" #include "pdns/dnsbackend.hh" #include "gsqlbackend.hh" @@ -84,6 +83,8 @@ GSQLBackend::GSQLBackend(const string &mode, const string &suffix) d_UpdateCatalogOfZoneQuery = getArg("update-catalog-query"); d_UpdateAccountOfZoneQuery=getArg("update-account-query"); d_InfoOfAllMasterDomainsQuery=getArg("info-all-master-query"); + d_InfoProducerMembersQuery = getArg("info-producer-members-query"); + d_InfoConsumerMembersQuery = getArg("info-consumer-members-query"); d_DeleteDomainQuery=getArg("delete-domain-query"); d_DeleteZoneQuery=getArg("delete-zone-query"); d_DeleteRRSetQuery=getArg("delete-rrset-query"); @@ -159,6 +160,8 @@ GSQLBackend::GSQLBackend(const string &mode, const string &suffix) d_UpdateCatalogOfZoneQuery_stmt = nullptr; d_UpdateAccountOfZoneQuery_stmt = nullptr; d_InfoOfAllMasterDomainsQuery_stmt = nullptr; + d_InfoProducerMembersQuery_stmt = nullptr; + d_InfoConsumerMembersQuery_stmt = nullptr; d_DeleteDomainQuery_stmt = nullptr; d_DeleteZoneQuery_stmt = nullptr; d_DeleteRRSetQuery_stmt = nullptr; @@ -418,7 +421,7 @@ void GSQLBackend::getUnfreshSlaveInfos(vector *unfreshDomains) SOAData sd; DomainInfo di; - for (const auto& row : d_result) { // id, name, type, master, last_check, content + for (const auto& row : d_result) { // id, name, type, master, last_check, catalog, content ASSERT_ROW_COLUMNS("info-all-slaves-query", row, 6); try { @@ -570,7 +573,7 @@ void GSQLBackend::getUpdatedMasters(vector& updatedDomains, std::uno try { if (!row[5].empty()) { - ci.fromJson(row[3], CatalogInfo::CatalogType::Producer); + ci.fromJson(row[4], CatalogInfo::CatalogType::Producer); ci.updateHash(catalogHashes, di); } } @@ -580,7 +583,7 @@ void GSQLBackend::getUpdatedMasters(vector& updatedDomains, std::uno } try { - pdns::checked_stoi_into(di.id, row[4]); + pdns::checked_stoi_into(di.id, row[3]); } catch (const std::exception& e) { g_log << Logger::Warning << "Could not convert notified_serial '" << row[4] << "' for zone '" << di.zone << "' into an integer: " << e.what() << endl; @@ -591,11 +594,11 @@ void GSQLBackend::getUpdatedMasters(vector& updatedDomains, std::uno fillSOAData(row[6], sd); } catch (const std::exception& exp) { - g_log << Logger::Warning << "Error while parsing SOA data for zone '" << di.zone << "': " << exp.what() << endl; + g_log << Logger::Warning << "Error while parsing SOA content '" << row[6] << "' for zone '" << di.zone << "': " << exp.what() << endl; continue; } catch (...) { - g_log << Logger::Warning << "Error while parsing SOA data for zone '" << di.zone << endl; + g_log << Logger::Warning << "Error while parsing SOA content '" << row[6] << "' for zone '" << di.zone << endl; continue; } @@ -609,6 +612,79 @@ void GSQLBackend::getUpdatedMasters(vector& updatedDomains, std::uno } } +bool GSQLBackend::getCatalogMembers(const DNSName& catalog, vector& members, CatalogInfo::CatalogType type) +{ + try { + reconnectIfNeeded(); + + if (type == CatalogInfo::CatalogType::Producer) { + // clang-format off + d_InfoProducerMembersQuery_stmt-> + bind("catalog", catalog)-> + execute()-> + getResult(d_result)-> + reset(); + // clang-format on + } + else if (type == CatalogInfo::CatalogType::Consumer) { + // clang-format off + d_InfoProducerMembersQuery_stmt-> + bind("catalog", catalog)-> + execute()-> + getResult(d_result)-> + reset(); + // clang-format on + } + else { + PDNSException(std::string(__PRETTY_FUNCTION__) + " unknown type '" + CatalogInfo::getTypeString(type) + "'"); + } + } + catch (SSqlException& e) { + 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); + + CatalogInfo ci; + + try { + ci.d_zone = DNSName(row[1]); + } + catch (const std::runtime_error& e) { + g_log << Logger::Warning << __PRETTY_FUNCTION__ << " zone name '" << row[1] << "' is not a valid DNS name: " << e.what() << endl; + members.clear(); + return false; + } + catch (PDNSException& ae) { + g_log << Logger::Warning << __PRETTY_FUNCTION__ << " zone name '" << row[1] << "' is not a valid DNS name: " << ae.reason << endl; + members.clear(); + return false; + } + + try { + pdns::checked_stoi_into(ci.d_id, row[0]); + } + catch (const std::exception& e) { + g_log << Logger::Warning << __PRETTY_FUNCTION__ << " could not convert id '" << row[0] << "' for zone '" << ci.d_zone << "' into an integer: " << e.what() << endl; + members.clear(); + return false; + } + + try { + ci.fromJson(row[2], type); + } + catch (const std::runtime_error& e) { + g_log << Logger::Warning << __PRETTY_FUNCTION__ << " options '" << row[2] << "' for zone '" << ci.d_zone << "' is no valid JSON: " << e.what() << endl; + members.clear(); + return false; + } + + members.emplace_back(ci); + } + return true; +} + bool GSQLBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName& qname, const DNSName& ordername, bool auth, const uint16_t qtype) { if(!d_dnssecQueries) diff --git a/pdns/backends/gsql/gsqlbackend.hh b/pdns/backends/gsql/gsqlbackend.hh index 395fafdcf0..724f050d91 100644 --- a/pdns/backends/gsql/gsqlbackend.hh +++ b/pdns/backends/gsql/gsqlbackend.hh @@ -80,6 +80,8 @@ protected: d_UpdateSerialOfZoneQuery_stmt = d_db->prepare(d_UpdateSerialOfZoneQuery, 2); d_UpdateLastCheckOfZoneQuery_stmt = d_db->prepare(d_UpdateLastCheckOfZoneQuery, 2); d_InfoOfAllMasterDomainsQuery_stmt = d_db->prepare(d_InfoOfAllMasterDomainsQuery, 0); + d_InfoProducerMembersQuery_stmt = d_db->prepare(d_InfoProducerMembersQuery, 1); + d_InfoConsumerMembersQuery_stmt = d_db->prepare(d_InfoConsumerMembersQuery, 1); d_DeleteDomainQuery_stmt = d_db->prepare(d_DeleteDomainQuery, 1); d_DeleteZoneQuery_stmt = d_db->prepare(d_DeleteZoneQuery, 1); d_DeleteRRSetQuery_stmt = d_db->prepare(d_DeleteRRSetQuery, 3); @@ -148,6 +150,8 @@ protected: d_UpdateSerialOfZoneQuery_stmt.reset(); d_UpdateLastCheckOfZoneQuery_stmt.reset(); d_InfoOfAllMasterDomainsQuery_stmt.reset(); + d_InfoProducerMembersQuery_stmt.reset(); + d_InfoConsumerMembersQuery_stmt.reset(); d_DeleteDomainQuery_stmt.reset(); d_DeleteZoneQuery_stmt.reset(); d_DeleteRRSetQuery_stmt.reset(); @@ -212,6 +216,7 @@ public: void setFresh(uint32_t domain_id) override; void getUnfreshSlaveInfos(vector *domains) override; void getUpdatedMasters(vector& updatedDomains, std::unordered_set& catalogs, CatalogHashMap& catalogHashes) override; + bool getCatalogMembers(const DNSName& catalog, vector& members, CatalogInfo::CatalogType type) override; bool getDomainInfo(const DNSName &domain, DomainInfo &di, bool getSerial=true) override; void setNotified(uint32_t domain_id, uint32_t serial) override; bool setMasters(const DNSName &domain, const vector &masters) override; @@ -315,6 +320,8 @@ private: string d_UpdateSerialOfZoneQuery; string d_UpdateLastCheckOfZoneQuery; string d_InfoOfAllMasterDomainsQuery; + string d_InfoProducerMembersQuery; + string d_InfoConsumerMembersQuery; string d_DeleteDomainQuery; string d_DeleteZoneQuery; string d_DeleteRRSetQuery; @@ -390,6 +397,8 @@ private: unique_ptr d_UpdateSerialOfZoneQuery_stmt; unique_ptr d_UpdateLastCheckOfZoneQuery_stmt; unique_ptr d_InfoOfAllMasterDomainsQuery_stmt; + unique_ptr d_InfoProducerMembersQuery_stmt; + unique_ptr d_InfoConsumerMembersQuery_stmt; unique_ptr d_DeleteDomainQuery_stmt; unique_ptr d_DeleteZoneQuery_stmt; unique_ptr d_DeleteRRSetQuery_stmt; diff --git a/pdns/dnsbackend.hh b/pdns/dnsbackend.hh index 9c0f678f38..ace60d7e58 100644 --- a/pdns/dnsbackend.hh +++ b/pdns/dnsbackend.hh @@ -43,6 +43,7 @@ class DNSPacket; #include "dnsrecords.hh" #include "iputils.hh" #include "sha.hh" +#include "auth-catalogzone.hh" class DNSBackend; struct DomainInfo @@ -120,8 +121,6 @@ struct DomainInfo }; -typedef map CatalogHashMap; - struct TSIGKey { DNSName name; DNSName algorithm; @@ -345,6 +344,12 @@ public: { } + //! get list of all members in a catalog + virtual bool getCatalogMembers(const DNSName& catalog, vector& members, CatalogInfo::CatalogType type) + { + return false; + } + //! Called by PowerDNS to inform a backend that a domain need to be checked for freshness virtual void setStale(uint32_t domain_id) { diff --git a/pdns/mastercommunicator.cc b/pdns/mastercommunicator.cc index 29c46f8487..332e40ce7f 100644 --- a/pdns/mastercommunicator.cc +++ b/pdns/mastercommunicator.cc @@ -141,7 +141,6 @@ void CommunicatorClass::getUpdatedProducers(UeberBackend* B, vector& std::string metaHash; std::string mapHash; for (auto& ch : catalogHashes) { - g_log << Logger::Error << catalogs.size() << " " << ch.first << endl; if (!catalogs.count(ch.first)) { g_log << Logger::Warning << "orphaned member zones found with catalog '" << ch.first << "'" << endl; continue; diff --git a/pdns/recursordist/Makefile.am b/pdns/recursordist/Makefile.am index 91d49d822b..00ad4cba67 100644 --- a/pdns/recursordist/Makefile.am +++ b/pdns/recursordist/Makefile.am @@ -101,6 +101,7 @@ endif pdns_recursor_SOURCES = \ aggressive_nsec.cc aggressive_nsec.hh \ arguments.cc \ + auth-catalogzone.hh \ axfr-retriever.hh axfr-retriever.cc \ base32.cc base32.hh \ base64.cc base64.hh \ diff --git a/pdns/recursordist/auth-catalogzone.hh b/pdns/recursordist/auth-catalogzone.hh new file mode 120000 index 0000000000..6883b8985e --- /dev/null +++ b/pdns/recursordist/auth-catalogzone.hh @@ -0,0 +1 @@ +../auth-catalogzone.hh \ No newline at end of file diff --git a/pdns/tcpreceiver.cc b/pdns/tcpreceiver.cc index 30cc461096..9eeff00369 100644 --- a/pdns/tcpreceiver.cc +++ b/pdns/tcpreceiver.cc @@ -562,7 +562,7 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr& q, g_log<& q, return 0; } - if(!(*packetHandler)->getBackend()->getSOAUncached(target, sd)) { + if (!(*packetHandler)->getBackend()->getDomainInfo(target, di, false)) { g_log<setRcode(RCode::NotAuth); sendPacket(outpacket,outsock); @@ -588,6 +588,7 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr& q, } UeberBackend db; + SOAData sd; if(!db.getSOAUncached(target, sd)) { g_log<setRcode(RCode::NotAuth); @@ -595,14 +596,20 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr& q, return 0; } + bool securedZone = false; + bool presignedZone = false; + bool NSEC3Zone = false; + bool narrow = false; + + NSEC3PARAMRecordContent ns3pr; + DNSSECKeeper dk(&db); DNSSECKeeper::clearCaches(target); - bool securedZone = dk.isSecuredZone(target); - bool presignedZone = dk.isPresigned(target); + if (!di.isCatalogType()) { + securedZone = dk.isSecuredZone(target); + presignedZone = dk.isPresigned(target); + } - NSEC3PARAMRecordContent ns3pr; - bool narrow; - bool NSEC3Zone=false; if(securedZone && dk.getNSEC3PARAM(target, &ns3pr, &narrow)) { NSEC3Zone=true; if(narrow) { @@ -727,18 +734,46 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr& q, zrrs.push_back(zrr); } + const bool rectify = !(presignedZone || ::arg().mustDo("disable-axfr-rectify")); + set qnames, nsset, terms; + + // Catalog zone start + if (di.kind == DomainInfo::Producer) { + // Ignore all records except NS at apex + sd.db->lookup(QType::NS, target, di.id); + while (sd.db->get(zrr)) { + zrrs.emplace_back(zrr); + } + if (zrrs.empty()) { + zrr.dr.d_name = target; + zrr.dr.d_ttl = 0; + zrr.dr.d_type = QType::NS; + zrr.dr.d_content = std::make_shared("invalid."); + zrrs.emplace_back(zrr); + } + + zrrs.emplace_back(CatalogInfo::getCatalogVersionRecord(target)); + + vector members; + sd.db->getCatalogMembers(target, members, CatalogInfo::CatalogType::Producer); + for (const auto& ci : members) { + ci.toDNSZoneRecords(target, zrrs); + } + if (members.empty()) { + g_log << Logger::Warning << logPrefix << "catalog zone '" << target << "' has no members" << endl; + } + goto send; + } + // Catalog zone end + // now start list zone - if(!(sd.db->list(target, sd.domain_id))) { + if (!(sd.db->list(target, sd.domain_id, di.isCatalogType()))) { g_log<setRcode(RCode::ServFail); sendPacket(outpacket,outsock); return 0; } - - const bool rectify = !(presignedZone || ::arg().mustDo("disable-axfr-rectify")); - set qnames, nsset, terms; - while(sd.db->get(zrr)) { if (!presignedZone) { if (zrr.dr.d_type == QType::RRSIG) { @@ -905,6 +940,7 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr& q, } } +send: /* now write all other records */