From cb9e7d25ffce6bb36da7097df7783bca9e7b2636 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9gory=20Oestreicher?= Date: Wed, 14 Sep 2016 23:34:01 +0200 Subject: [PATCH] Add master support and document it. --- docs/markdown/authoritative/backend-ldap.md | 44 ++++ modules/ldapbackend/ldapbackend.cc | 225 ++++++++++++++++++-- modules/ldapbackend/ldapbackend.hh | 9 +- 3 files changed, 263 insertions(+), 15 deletions(-) diff --git a/docs/markdown/authoritative/backend-ldap.md b/docs/markdown/authoritative/backend-ldap.md index 0e548fd751..84c1179b18 100644 --- a/docs/markdown/authoritative/backend-ldap.md +++ b/docs/markdown/authoritative/backend-ldap.md @@ -97,6 +97,50 @@ In case the used LDAP client library doesn't support LDAP URIs as connection par ## `ldap-filter-lookup` (default "(:target:)" ) : LDAP filter for limiting IP or name lookups, e.g. (&(:target:)(active=yes)) for returning only entries whose attribute "active" is set to "yes". +# Master Mode + +Schema update +------------- + +First off adding master support to the LDAP backend needs +a schema update. This is required as some metadata must +be stored by PowerDNS, such as the last successful transfer +to slaves. The new schema is available in +schema/pdns-domaininfo.schema. + +Once the schema is loaded the zones for which you want to +be a master must be modified. The dn of the SOA record +*must* have the object class `PdnsDomain`, and thus the +`PdnsDomainId` attribute. This attribute is an integer +that *must* be unique across all zones served by the +backend. Furthermore the `PdnsDomainType` must be equal +to 'master' (lower case). + +Example +------- + +Here is an example LDIF of a zone that's ready for master +operation (assuming the 'tree' style): + +``` +dn: dc=example,dc=com,ou=dns,dc=mycompany,dc=com +objectClass: top +objectClass: domainRelatedObject +objectClass: dNSDomain2 +objectClass: PdnsDomain +dc: example +associatedDomain: example.com +nSRecord: ns1.example.com +sOARecord: ns1.example.com. hostmaster.example.com. 2013031101 1800 600 1209600 600 +mXRecord: 10 mx1.example.com +PdnsDomainId: 1 +PdnsDomainType: master +PdnsDomainMaster: 192.168.0.2 +``` + +You should have one attribute `PdnsDomainMaster` per +master serving this zone. + # Example ## Tree design The DNS LDAP tree should be designed carefully to prevent mistakes, which are hard to correct afterwards. diff --git a/modules/ldapbackend/ldapbackend.cc b/modules/ldapbackend/ldapbackend.cc index d4e4c7f040..70d5671ca9 100644 --- a/modules/ldapbackend/ldapbackend.cc +++ b/modules/ldapbackend/ldapbackend.cc @@ -535,32 +535,231 @@ bool LdapBackend::get( DNSResourceRecord &rr ) return false; } + + + +void LdapBackend::getUpdatedMasters( vector* domains ) +{ + string filter; + int msgid; + PowerLDAP::sentry_t result; + const char* attronly[] = { + "associatedDomain", + NULL + }; + + try + { + // First get all domains on which we are master. + filter = strbind( ":target:", "&(SOARecord=*)(PdnsDomainId=*)", getArg( "filter-axfr" ) ); + msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly ); + } + catch( LDAPTimeout < ) + { + L << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl; + throw( DBException( "LDAP server timeout" ) ); + } + catch( LDAPNoConnection &lnc ) + { + L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl; + if ( reconnect() ) + this->getUpdatedMasters( domains ); + else + throw PDNSException( "Failed to reconnect to LDAP server" ); + } + catch( LDAPException &le ) + { + L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl; + throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server + } + catch( std::exception &e ) + { + throw( DBException( "STL exception" ) ); + } + + while( m_pldap->getSearchEntry( msgid, result ) ) { + if( !result.count( "associatedDomain" ) || result["associatedDomain"].empty() ) + continue; + + DomainInfo di; + if ( !getDomainInfo( result["associatedDomain"][0], di ) ) + continue; + + di.backend = this; + if( di.notified_serial < di.serial ) + domains->push_back( di ); + } +} - bool LdapBackend::getDomainInfo( const string& domain, DomainInfo& di ) + +void LdapBackend::setNotified( uint32_t id, uint32_t serial ) { string filter; - SOAData sd; - const char* attronly[] = { "sOARecord", NULL }; + int msgid; + PowerLDAP::sresult_t results; + PowerLDAP::sentry_t entry; + const char* attronly[] = { "associatedDomain", NULL }; + + try + { + // Try to find the notified domain + filter = strbind( ":target:", "PdnsDomainId=" + std::to_string( id ), getArg( "filter-axfr" ) ); + msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly ); + m_pldap->getSearchResults( msgid, results, true ); + } + catch( LDAPTimeout < ) + { + L << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl; + throw( DBException( "LDAP server timeout" ) ); + } + catch( LDAPNoConnection &lnc ) + { + L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl; + if ( reconnect() ) + this->setNotified( id, serial ); + else + throw PDNSException( "Failed to reconnect to LDAP server" ); + } + catch( LDAPException &le ) + { + L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl; + throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server + } + catch( std::exception &e ) + { + throw( DBException( "STL exception" ) ); + } + + if ( results.empty() ) + throw PDNSException( "No results found when trying to update domain notified_serial for ID " + std::to_string( id ) ); + + entry = results.front(); + string dn = entry["dn"][0]; + string serialStr = std::to_string( serial ); + LDAPMod *mods[2]; + LDAPMod mod; + char *vals[2]; + mod.mod_op = LDAP_MOD_REPLACE; + mod.mod_type = (char*)"PdnsDomainNotifiedSerial"; + vals[0] = const_cast( serialStr.c_str() ); + vals[1] = NULL; + mod.mod_values = vals; - // search for SOARecord of domain - filter = "(&(associatedDomain=" + toLower( m_pldap->escape( domain ) ) + ")(SOARecord=*))"; - m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly ); - m_pldap->getSearchEntry( m_msgid, m_result ); + mods[0] = &mod; + mods[1] = NULL; - if( m_result.count( "sOARecord" ) && !m_result["sOARecord"].empty() ) + try + { + m_pldap->modify( dn, mods ); + } + catch( LDAPNoConnection &lnc ) + { + L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl; + if ( reconnect() ) + this->setNotified( id, serial ); + else + throw PDNSException( "Failed to reconnect to LDAP server" ); + } + catch( LDAPException &le ) + { + L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl; + throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server + } + catch( std::exception &e ) + { + throw( DBException( "STL exception" ) ); + } +} + + + +bool LdapBackend::getDomainInfo( const string& domain, DomainInfo& di ) +{ + string filter; + SOAData sd; + int msgid; + PowerLDAP::sentry_t result; + const char* attronly[] = { + "sOARecord", + "PdnsDomainId", + "PdnsDomainNotifiedSerial", + "PdnsDomainLastCheck", + "PdnsDomainMaster", + "PdnsDomainType", + NULL + }; + + try + { + // search for SOARecord of domain + filter = "(&(associatedDomain=" + toLower( m_pldap->escape( domain ) ) + ")(SOARecord=*))"; + m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly ); + m_pldap->getSearchEntry( msgid, result ); + } + catch( LDAPTimeout < ) + { + L << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl; + throw( DBException( "LDAP server timeout" ) ); + } + catch( LDAPNoConnection &lnc ) + { + L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl; + if ( reconnect() ) + this->getDomainInfo( domain, di ); + else + throw PDNSException( "Failed to reconnect to LDAP server" ); + } + catch( LDAPException &le ) + { + L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl; + throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server + } + catch( std::exception &e ) + { + throw( DBException( "STL exception" ) ); + } + + if( result.count( "sOARecord" ) && !result["sOARecord"].empty() ) { sd.serial = 0; - fillSOAData( m_result["sOARecord"][0], sd ); + fillSOAData( result["sOARecord"][0], sd ); + + if ( result.count( "PdnsDomainId" ) && !result["PdnsDomainId"].empty() ) + di.id = std::stoi( result["PdnsDomainId"][0] ); + else + di.id = 0; - di.id = 0; di.serial = sd.serial; di.zone = DNSName(domain); - di.last_check = 0; - di.backend = this; - di.kind = DomainInfo::Master; + + if( result.count( "PdnsDomainLastCheck" ) && !result["PdnsDomainLastCheck"].empty() ) + di.last_check = pdns_stou( result["PdnsDomainLastCheck"][0] ); + else + di.last_check = 0; + + if ( result.count( "PdnsDomainNotifiedSerial" ) && !result["PdnsDomainNotifiedSerial"].empty() ) + di.notified_serial = pdns_stou( result["PdnsDomainNotifiedSerial"][0] ); + else + di.notified_serial = 0; + + if ( result.count( "PdnsDomainMaster" ) && !result["PdnsDomainMaster"].empty() ) + di.masters = result["PdnsDomainMaster"]; + + if ( result.count( "PdnsDomainType" ) && !result["PdnsDomainType"].empty() ) { + string kind = result["PdnsDomainType"][0]; + if ( kind == "master" ) + di.kind = DomainInfo::Master; + else if ( kind == "slave" ) + di.kind = DomainInfo::Slave; + else + di.kind = DomainInfo::Native; + } + else { + di.kind = DomainInfo::Native; + } return true; } diff --git a/modules/ldapbackend/ldapbackend.hh b/modules/ldapbackend/ldapbackend.hh index 8957f2985b..f3e584c8df 100644 --- a/modules/ldapbackend/ldapbackend.hh +++ b/modules/ldapbackend/ldapbackend.hh @@ -131,8 +131,6 @@ class LdapBackend : public DNSBackend bool prepare_simple(); bool prepare_strict(); - bool getDomainInfo( const string& domain, DomainInfo& di ); - bool reconnect(); public: @@ -140,9 +138,16 @@ class LdapBackend : public DNSBackend LdapBackend( const string &suffix="" ); ~LdapBackend(); + // Native backend bool list( const DNSName& target, int domain_id, bool include_disabled=false ); void lookup( const QType& qtype, const DNSName& qdomain, DNSPacket* p = 0, int zoneid = -1 ); bool get( DNSResourceRecord& rr ); + + bool getDomainInfo( const string& domain, DomainInfo& di ); + + // Master backend + void getUpdatedMasters( vector* domains ); + void setNotified( uint32_t id, uint32_t serial ); }; #endif /* LDAPBACKEND_HH */ -- 2.47.2