]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Add master support and document it.
authorGrégory Oestreicher <greg@kamago.net>
Wed, 14 Sep 2016 21:34:01 +0000 (23:34 +0200)
committerGrégory Oestreicher <greg@kamago.net>
Tue, 28 Feb 2017 21:38:32 +0000 (22:38 +0100)
docs/markdown/authoritative/backend-ldap.md
modules/ldapbackend/ldapbackend.cc
modules/ldapbackend/ldapbackend.hh

index 0e548fd75169a101016b873b8567e389b9199768..84c1179b1860d9a1fe5f2911d97580c75c68f80e 100644 (file)
@@ -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.
index d4e4c7f040966866719cb483fd36960ab2e1812d..70d5671ca9c19a5a6e84dcba9ecd3b977cb9df6c 100644 (file)
@@ -535,32 +535,231 @@ bool LdapBackend::get( DNSResourceRecord &rr )
 
   return false;
 }
+void LdapBackend::getUpdatedMasters( vector<DomainInfo>* 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 &lt )
+  {
+    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 &lt )
+  {
+    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<char*>( 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 &lt )
+  {
+    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;
   }
index 8957f2985b5969f6561351fa3a009d4ab3cc0210..f3e584c8dfb289b7fe319c76f5a75ebac3fac245 100644 (file)
@@ -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<DomainInfo>* domains );
+    void setNotified( uint32_t id, uint32_t serial );
 };
 
 #endif /* LDAPBACKEND_HH */