From: Peter van Dijk Date: Thu, 24 Apr 2025 13:16:52 +0000 (+0200) Subject: Support for views on AXFR. X-Git-Tag: auth-5.0.0-alpha1~1^2~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ef64c88869880b33c46123e753571b7051201cf2;p=thirdparty%2Fpdns.git Support for views on AXFR. Note that this causes somewhat important plumbing changes, getSOA will now take an optional zone ID, which callers should provide if they know it, in order to save a possible expensive getDomainInfo call. --- diff --git a/docs/appendices/backend-writers-guide.rst b/docs/appendices/backend-writers-guide.rst index 13c2d4284e..143d5f3122 100644 --- a/docs/appendices/backend-writers-guide.rst +++ b/docs/appendices/backend-writers-guide.rst @@ -70,7 +70,7 @@ following methods are relevant: virtual void lookup(const QType &qtype, const string &qdomain, domainid_t zoneId=UnknownDomainID, DNSPacket *pkt_p=nullptr)=0; virtual bool list(const string &target, domainid_t domain_id)=0; virtual bool get(DNSResourceRecord &r)=0; - virtual bool getSOA(const string &name, SOAData &soadata); + virtual bool getSOA(const string &name, domainid_t zoneId, SOAData &soadata); }; Note that the first four methods must be implemented. ``getSOA()`` has @@ -143,8 +143,8 @@ The following lists the contents of a zone called "powerdns.com". .. code-block:: cpp SOAData sd; - if(!yb.getSOA("powerdns.com",sd)) // are we authoritative over powerdns.com? - return RCode::NotAuth; // no + if(!yb.getSOA("powerdns.com", UnknownDomainID, sd)) // are we authoritative over powerdns.com? + return RCode::NotAuth; // no yb.list(sd.domain_id); while(yb.get(rr)) @@ -422,10 +422,11 @@ Methods Should throw an PDNSException in case a database error occurred. -.. cpp:function:: bool DNSBackend::getSOA(const string &name, SOAData &soadata) +.. cpp:function:: bool DNSBackend::getSOA(const string &name, domainid_t zoneId, SOAData &soadata) - If the backend considers itself authoritative over domain ``name``, this - method should fill out the passed **SOAData** structure and return true. + If the backend considers itself authoritative over domain ``name``, of + id ``zoneId`` if known (otherwise, ``UnknownDomainID``), this method should + fill out the passed **SOAData** structure and return true. If the backend is functioning correctly, but does not consider itself authoritative, it should return false. In case of errors, an PDNSException should be thrown. diff --git a/modules/bindbackend/bindbackend2.cc b/modules/bindbackend/bindbackend2.cc index 3cd05868da..675a372fa9 100644 --- a/modules/bindbackend/bindbackend2.cc +++ b/modules/bindbackend/bindbackend2.cc @@ -348,7 +348,7 @@ void Bind2Backend::getUpdatedPrimaries(vector& changedDomains, std:: for (DomainInfo& di : consider) { soadata.serial = 0; try { - this->getSOA(di.zone, soadata); // we might not *have* a SOA yet, but this might trigger a load of it + this->getSOA(di.zone, di.id, soadata); // we might not *have* a SOA yet, but this might trigger a load of it } catch (...) { continue; @@ -394,7 +394,7 @@ void Bind2Backend::getAllDomains(vector* domains, bool getSerial, bo if (di.backend != this) continue; try { - this->getSOA(di.zone, soadata); + this->getSOA(di.zone, di.id, soadata); } catch (...) { continue; @@ -430,7 +430,7 @@ void Bind2Backend::getUnfreshSecondaryInfos(vector* unfreshDomains) soadata.refresh = 0; soadata.serial = 0; try { - getSOA(sd.zone, soadata); // we might not *have* a SOA yet + getSOA(sd.zone, sd.id, soadata); // we might not *have* a SOA yet } catch (...) { } @@ -459,7 +459,7 @@ bool Bind2Backend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool SOAData sd; sd.serial = 0; - getSOA(bbd.d_name, sd); // we might not *have* a SOA yet + getSOA(bbd.d_name, bbd.d_id, sd); // we might not *have* a SOA yet info.serial = sd.serial; } catch (...) { diff --git a/modules/geoipbackend/geoipbackend.cc b/modules/geoipbackend/geoipbackend.cc index 6381d04f2d..137d932aae 100644 --- a/modules/geoipbackend/geoipbackend.cc +++ b/modules/geoipbackend/geoipbackend.cc @@ -911,7 +911,7 @@ bool GeoIPBackend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool for (const GeoIPDomain& dom : s_domains) { if (dom.domain == domain) { SOAData sd; - this->getSOA(domain, sd); + this->getSOA(dom.domain, dom.id, sd); info.id = dom.id; info.zone = dom.domain; info.serial = sd.serial; @@ -930,7 +930,7 @@ void GeoIPBackend::getAllDomains(vector* domains, bool /* getSerial DomainInfo di; for (const auto& dom : s_domains) { SOAData sd; - this->getSOA(dom.domain, sd); + this->getSOA(dom.domain, dom.id, sd); di.id = dom.id; di.zone = dom.domain; di.serial = sd.serial; diff --git a/modules/lua2backend/lua2api2.hh b/modules/lua2backend/lua2api2.hh index 18742000b7..d53384d7c7 100644 --- a/modules/lua2backend/lua2api2.hh +++ b/modules/lua2backend/lua2api2.hh @@ -282,7 +282,13 @@ public: bool getDomainInfo(const ZoneName& domain, DomainInfo& di, bool /* getSerial */ = true) override { if (f_get_domaininfo == nullptr) { - // use getAuth instead + // use getAuth instead... but getAuth wraps getSOA which will call + // getDomainInfo if this is a domain variant, so protect against this + // would-be infinite recursion. + if (domain.hasVariant()) { + g_log << Logger::Info << "Unable to return domain information for '" << domain.toLogString() << "' due to unimplemented dns_get_domaininfo" << endl; + return false; + } SOAData sd; if (!getAuth(domain, &sd)) return false; diff --git a/pdns/auth-zonecache.hh b/pdns/auth-zonecache.hh index d2632c20ee..29c530cb34 100644 --- a/pdns/auth-zonecache.hh +++ b/pdns/auth-zonecache.hh @@ -27,6 +27,7 @@ #include "lock.hh" #include "misc.hh" #include "iputils.hh" +#include "dnspacket.hh" class AuthZoneCache : public boost::noncopyable { diff --git a/pdns/backends/gsql/gsqlbackend.cc b/pdns/backends/gsql/gsqlbackend.cc index 6d12caf937..8f531b5577 100644 --- a/pdns/backends/gsql/gsqlbackend.cc +++ b/pdns/backends/gsql/gsqlbackend.cc @@ -405,8 +405,9 @@ bool GSQLBackend::getDomainInfo(const ZoneName &domain, DomainInfo &info, bool g if(getSerial) { try { SOAData sd; - if(!getSOA(domain, sd)) + if(!getSOA(domain, info.id, sd)) { g_log< g_getGeo; bool DNSBackend::getAuth(const ZoneName& target, SOAData* soaData) { - return this->getSOA(target, *soaData); + return this->getSOA(target, UnknownDomainID, *soaData); } void DNSBackend::setArgPrefix(const string& prefix) @@ -245,19 +245,27 @@ vector> BackendMakerClass::all(bool metadataOnly) answer, in which case you need to perform a getDomainInfo call! \param domain Domain we want to get the SOA details of - \param sd SOAData which is filled with the SOA details + \param zoneId Domain id, if known + \param soaData SOAData which is filled with the SOA details \param unmodifiedSerial bool if set, serial will be returned as stored in the backend (maybe 0) */ -bool DNSBackend::getSOA(const ZoneName& domain, SOAData& soaData) +bool DNSBackend::getSOA(const ZoneName& domain, domainid_t zoneId, SOAData& soaData) { - this->lookup(QType(QType::SOA), domain.operator const DNSName&(), -1); + soaData.db = nullptr; + + if (domain.hasVariant() && zoneId == UnknownDomainID) { + DomainInfo domaininfo; + if (!this->getDomainInfo(domain, domaininfo, false)) { + return false; + } + zoneId = domaininfo.id; + } + this->lookup(QType(QType::SOA), domain.operator const DNSName&(), zoneId); S.inc("backend-queries"); DNSResourceRecord resourceRecord; int hits = 0; - soaData.db = nullptr; - try { while (this->get(resourceRecord)) { if (resourceRecord.qtype != QType::SOA) { diff --git a/pdns/dnsbackend.hh b/pdns/dnsbackend.hh index a57e76a03b..e445c96ef9 100644 --- a/pdns/dnsbackend.hh +++ b/pdns/dnsbackend.hh @@ -184,7 +184,7 @@ public: virtual ~DNSBackend() = default; //! fills the soadata struct with the SOA details. Returns false if there is no SOA. - virtual bool getSOA(const ZoneName& domain, SOAData& soaData); + virtual bool getSOA(const ZoneName& domain, domainid_t zoneId, SOAData& soaData); virtual bool replaceRRSet(domainid_t /* domain_id */, const DNSName& /* qname */, const QType& /* qt */, const vector& /* rrset */) { diff --git a/pdns/dnspacket.hh b/pdns/dnspacket.hh index 7003fb295d..13146fde4b 100644 --- a/pdns/dnspacket.hh +++ b/pdns/dnspacket.hh @@ -139,7 +139,7 @@ public: DNSName qdomain; //!< qname of the question 4 - unsure how this is used DNSName qdomainwild; //!< wildcard matched by qname, used by LuaPolicyEngine - ZoneName qdomainzone; //!< zone name for the answer (as reflected in SOA for negative responses), used by LuaPolicyEngine + ZoneName qdomainzone; //!< zone name for the answer (as reflected in SOA for negative responses), used by LuaPolicyEngine and AXFR string d_peer_principal; const DNSName& getTSIGKeyname() const; diff --git a/pdns/rfc2136handler.cc b/pdns/rfc2136handler.cc index 30ca1b917c..d4844427d4 100644 --- a/pdns/rfc2136handler.cc +++ b/pdns/rfc2136handler.cc @@ -1047,7 +1047,7 @@ int PacketHandler::processUpdate(DNSPacket& packet) { // NOLINT(readability-func void PacketHandler::increaseSerial(const string &msgPrefix, const DomainInfo *di, const string& soaEditSetting, bool haveNSEC3, bool narrow, const NSEC3PARAMRecordContent *ns3pr) { SOAData sd; - if (!di->backend->getSOA(di->zone, sd)) { + if (!di->backend->getSOA(di->zone, di->id, sd)) { throw PDNSException("SOA-Serial update failed because there was no SOA. Wowie."); } diff --git a/pdns/tcpreceiver.cc b/pdns/tcpreceiver.cc index d5615e2c10..454ef41e6a 100644 --- a/pdns/tcpreceiver.cc +++ b/pdns/tcpreceiver.cc @@ -19,6 +19,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "pdns/auth-zonecache.hh" #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -368,12 +369,14 @@ void TCPNameserver::doConnection(int fd) if(packet->qtype.getCode()==QType::AXFR) { packet->d_xfr=true; - doAXFR(packet->qdomain, packet, fd); + g_zoneCache.setZoneVariant(packet); + doAXFR(packet->qdomainzone, packet, fd); continue; } if(packet->qtype.getCode()==QType::IXFR) { packet->d_xfr=true; + g_zoneCache.setZoneVariant(packet); doIXFR(packet, fd); continue; } @@ -460,8 +463,7 @@ bool TCPNameserver::canDoAXFR(std::unique_ptr& q, bool isAXFR, std::u if(::arg().mustDo("disable-axfr")) return false; - ZoneName zonename(q->qdomain); - string logPrefix=string(isAXFR ? "A" : "I")+"XFR-out zone '"+zonename.toLogString()+"', client '"+q->getInnerRemote().toStringWithPort()+"', "; + string logPrefix=string(isAXFR ? "A" : "I")+"XFR-out zone '"+q->qdomainzone.toLogString()+"', client '"+q->getInnerRemote().toStringWithPort()+"', "; if(q->d_havetsig) { // if you have one, it must be good TSIGRecordContent tsigContent; @@ -485,18 +487,18 @@ bool TCPNameserver::canDoAXFR(std::unique_ptr& q, bool isAXFR, std::u #ifdef ENABLE_GSS_TSIG if (g_doGssTSIG && q->d_tsig_algo == TSIG_GSS) { vector princs; - packetHandler->getBackend()->getDomainMetadata(zonename, "GSS-ALLOW-AXFR-PRINCIPAL", princs); + packetHandler->getBackend()->getDomainMetadata(q->qdomainzone, "GSS-ALLOW-AXFR-PRINCIPAL", princs); for(const std::string& princ : princs) { if (q->d_peer_principal == princ) { - g_log<d_peer_principal<<"' and algorithm 'gss-tsig'"<qdomainzone<<"' allowed: TSIG signed request with authorized principal '"<d_peer_principal<<"' and algorithm 'gss-tsig'"<d_peer_principal<<"' and algorithm 'gss-tsig' is not permitted"<qdomainzone<<"' denied: TSIG signed request with principal '"<d_peer_principal<<"' and algorithm 'gss-tsig' is not permitted"<qdomainzone, tsigkeyname)) { g_log<d_tsig_algo)<<"' does not grant access"<& q, bool isAXFR, std::u // cerr<<"doing per-zone-axfr-acls"<getBackend()->getSOAUncached(zonename,sd)) { + if(packetHandler->getBackend()->getSOAUncached(q->qdomainzone,sd)) { // cerr<<"got backend and SOA"< acl; - packetHandler->getBackend()->getDomainMetadata(zonename, "ALLOW-AXFR-FROM", acl); + packetHandler->getBackend()->getDomainMetadata(q->qdomainzone, "ALLOW-AXFR-FROM", acl); for (const auto & i : acl) { // cerr<<"matching against "<<*i<& q, bool isAXFR, std::u extern CommunicatorClass Communicator; - if(Communicator.justNotified(zonename, q->getInnerRemote().toString())) { // we just notified this ip + if(Communicator.justNotified(q->qdomainzone, q->getInnerRemote().toString())) { // we just notified this ip g_log<& q, int outsock) // NOLINT(readability-function-cognitive-complexity) +int TCPNameserver::doAXFR(const ZoneName &targetZone, std::unique_ptr& q, int outsock) // NOLINT(readability-function-cognitive-complexity) { - ZoneName targetZone(target); + DNSName target = targetZone.operator const DNSName&(); string logPrefix="AXFR-out zone '"+targetZone.toLogString()+"', client '"+q->getRemoteStringWithPort()+"', "; std::unique_ptr outpacket= getFreshAXFRPacket(q); @@ -713,9 +715,8 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr& q, if(securedZone && !presignedZone) { // this is where the DNSKEYs, CDNSKEYs and CDSs go in bool doCDNSKEY = true, doCDS = true; string publishCDNSKEY, publishCDS; - ZoneName zonename(q->qdomain); - dk.getPublishCDNSKEY(zonename, publishCDNSKEY); - dk.getPublishCDS(zonename, publishCDS); + dk.getPublishCDNSKEY(q->qdomainzone, publishCDNSKEY); + dk.getPublishCDS(q->qdomainzone, publishCDS); set entryPointIds; DNSSECKeeper::keyset_t entryPoints = dk.getEntryPoints(targetZone); @@ -1189,8 +1190,7 @@ send: int TCPNameserver::doIXFR(std::unique_ptr& q, int outsock) { - ZoneName zonename(q->qdomain); - string logPrefix="IXFR-out zone '"+zonename.toLogString()+"', client '"+q->getRemoteStringWithPort()+"', "; + string logPrefix="IXFR-out zone '"+q->qdomainzone.toLogString()+"', client '"+q->getRemoteStringWithPort()+"', "; std::unique_ptr outpacket=getFreshAXFRPacket(q); if(q->d_dnssecOk) @@ -1242,7 +1242,7 @@ int TCPNameserver::doIXFR(std::unique_ptr& q, int outsock) } // canDoAXFR does all the ACL checks, and has the if(disable-axfr) shortcut, call it first. - if(!canDoAXFR(q, false, *packetHandler) || !(*packetHandler)->getBackend()->getSOAUncached(zonename, sd)) { + if(!canDoAXFR(q, false, *packetHandler) || !(*packetHandler)->getBackend()->getSOAUncached(q->qdomainzone, sd)) { g_log<setRcode(RCode::NotAuth); sendPacket(outpacket,outsock); @@ -1250,10 +1250,10 @@ int TCPNameserver::doIXFR(std::unique_ptr& q, int outsock) } DNSSECKeeper dk((*packetHandler)->getBackend()); - DNSSECKeeper::clearCaches(zonename); + DNSSECKeeper::clearCaches(q->qdomainzone); bool narrow = false; - securedZone = dk.isSecuredZone(zonename); - if(dk.getNSEC3PARAM(zonename, nullptr, &narrow)) { + securedZone = dk.isSecuredZone(q->qdomainzone); + if(dk.getNSEC3PARAM(q->qdomainzone, nullptr, &narrow)) { if(narrow) { g_log<setRcode(RCode::Refused); @@ -1266,7 +1266,7 @@ int TCPNameserver::doIXFR(std::unique_ptr& q, int outsock) } if (serialPermitsIXFR) { - const ZoneName& target = zonename; + const ZoneName& target = q->qdomainzone; TSIGRecordContent trc; DNSName tsigkeyname; string tsigsecret; @@ -1312,7 +1312,7 @@ int TCPNameserver::doIXFR(std::unique_ptr& q, int outsock) } g_log<qdomain, q, outsock); + return doAXFR(q->qdomainzone, q, outsock); } TCPNameserver::~TCPNameserver() = default; diff --git a/pdns/tcpreceiver.hh b/pdns/tcpreceiver.hh index fbb6bd8ea0..81036de5ce 100644 --- a/pdns/tcpreceiver.hh +++ b/pdns/tcpreceiver.hh @@ -50,7 +50,7 @@ private: static void sendPacket(std::unique_ptr& p, int outsock, bool last=true); static void getQuestion(int fd, char *mesg, int pktlen, const ComboAddress& remote, unsigned int totalTime); - static int doAXFR(const DNSName &target, std::unique_ptr& q, int outsock); + static int doAXFR(const ZoneName &target, std::unique_ptr& q, int outsock); static int doIXFR(std::unique_ptr& q, int outsock); static bool canDoAXFR(std::unique_ptr& q, bool isAXFR, std::unique_ptr& packetHandler); static void doConnection(int fd); diff --git a/pdns/test-ueberbackend_cc.cc b/pdns/test-ueberbackend_cc.cc index 4181c22b40..0225b76dbc 100644 --- a/pdns/test-ueberbackend_cc.cc +++ b/pdns/test-ueberbackend_cc.cc @@ -275,7 +275,7 @@ public: return true; } - return getSOA(target, *soadata); + return getSOA(target, UnknownDomainID, *soadata); } size_t d_authLookupCount{0}; diff --git a/pdns/ueberbackend.cc b/pdns/ueberbackend.cc index 8916ccbdf4..b62dc57179 100644 --- a/pdns/ueberbackend.cc +++ b/pdns/ueberbackend.cc @@ -649,7 +649,7 @@ bool UeberBackend::getSOAUncached(const ZoneName& domain, SOAData& soaData) if (domain.hasVariant() && (backend->getCapabilities() & DNSBackend::CAP_VIEWS) == 0) { continue; } - if (backend->getSOA(domain, soaData)) { + if (backend->getSOA(domain, UnknownDomainID, soaData)) { if (domain.operator const DNSName&() != soaData.qname) { throw PDNSException("getSOA() returned an SOA for the wrong zone. Question: '" + domain.toLogString() + "', answer: '" + soaData.qname.toLogString() + "'"); }