]> granicus.if.org Git - pdns/commitdiff
LDAP search revamp
authorGrégory Oestreicher <greg@kamago.net>
Sat, 24 Jun 2017 21:46:44 +0000 (23:46 +0200)
committerGrégory Oestreicher <greg@kamago.net>
Tue, 10 Apr 2018 20:41:26 +0000 (22:41 +0200)
Use a dedicated search result class, and factorize LDAP
entries parsing.

modules/ldapbackend/Makefile.am
modules/ldapbackend/OBJECTFILES
modules/ldapbackend/exceptions.hh
modules/ldapbackend/ldapbackend.cc
modules/ldapbackend/ldapbackend.hh
modules/ldapbackend/master.cc [new file with mode: 0644]
modules/ldapbackend/native.cc [new file with mode: 0644]
modules/ldapbackend/powerldap.cc
modules/ldapbackend/powerldap.hh

index 8eecd8f8bce8891b662e25909cea503b854cdf4b..53020a64dde67a85f746c7728e4cdeb7d6cb109f 100644 (file)
@@ -12,6 +12,7 @@ dist_doc_DATA = \
 
 libldapbackend_la_SOURCES = \
        ldapbackend.cc ldapbackend.hh \
+  master.cc native.cc \
        powerldap.cc powerldap.hh \
        utils.hh exceptions.hh \
        ldaputils.hh ldaputils.cc \
index 0f758368845a76ba7a048b3968a4bd3bb7ba7c8a..d9864867e79fb7de3e9f759c242144729dd80ed9 100644 (file)
@@ -1 +1 @@
-ldapbackend.lo powerldap.lo ldaputils.lo ldapauthenticator.lo
+ldapbackend.lo master.lo native.lo powerldap.lo ldaputils.lo ldapauthenticator.lo
index 53e0145f5e7f34a5bf66eb6d6eec976e93054101..3436e38d1824a9d717d898c885c99b31e4bad8f3 100644 (file)
@@ -42,4 +42,10 @@ class LDAPNoConnection : public LDAPException
     explicit LDAPNoConnection() : LDAPException( "No connection to LDAP server" ) {}
 };
 
+class LDAPNoSuchObject : public LDAPException
+{
+  public:
+    explicit LDAPNoSuchObject() : LDAPException( "No such object" ) {}
+};
+
 #endif // LDAPEXCEPTIONS_HH
index 5f913aae36ee7ba5e98ec3dcd9d45a12ff1df15a..b6b4687d61fbf47fd6ac9922b10edbda70fcde33 100644 (file)
@@ -39,16 +39,13 @@ LdapBackend::LdapBackend( const string &suffix )
 
   try
   {
-    d_msgid = 0;
     d_qname.clear();
     d_pldap = NULL;
     d_authenticator = NULL;
-    d_ttl = 0;
-    d_axfrqlen = 0;
-    d_last_modified = 0;
     d_qlog = arg().mustDo( "query-logging" );
     d_default_ttl = arg().asNum( "default-ttl" );
     d_myname = "[LdapBackend]";
+    d_in_list = false;
 
     setArgPrefix( "ldap" + suffix );
 
@@ -56,7 +53,6 @@ LdapBackend::LdapBackend( const string &suffix )
     d_reconnect_attempts = getArgAsNum( "reconnect-attempts" );
     d_list_fcnt = &LdapBackend::list_simple;
     d_lookup_fcnt = &LdapBackend::lookup_simple;
-    d_prepare_fcnt = &LdapBackend::prepare_simple;
 
     if( getArg( "method" ) == "tree" )
     {
@@ -67,7 +63,6 @@ LdapBackend::LdapBackend( const string &suffix )
     {
       d_list_fcnt = &LdapBackend::list_strict;
       d_lookup_fcnt = &LdapBackend::lookup_strict;
-      d_prepare_fcnt = &LdapBackend::prepare_strict;
     }
 
     stringtok( hosts, getArg( "host" ), ", " );
@@ -118,6 +113,9 @@ LdapBackend::LdapBackend( const string &suffix )
 
 LdapBackend::~LdapBackend()
 {
+  d_search.reset(); // This is necessary otherwise d_pldap will get deleted first and
+                    // we may hang in SearchResult::~SearchResult() waiting for the
+                    // current operation to be abandoned
   delete( d_pldap );
   delete( d_authenticator );
   g_log << Logger::Notice << d_myname << " Ldap connection closed" << endl;
@@ -144,634 +142,73 @@ bool LdapBackend::reconnect()
 }
 
 
+void LdapBackend::extract_common_attributes( DNSResult &result ) {
+  if ( d_result.count( "dNSTTL" ) && !d_result["dNSTTL"].empty() ) {
+    char *endptr;
+    uint32_t ttl = (uint32_t) strtol( d_result["dNSTTL"][0].c_str(), &endptr, 10 );
 
-bool LdapBackend::list( const DNSName& target, int domain_id, bool include_disabled )
-{
-  try
-  {
-    d_qname = target;
-    d_qtype = QType::ANY;
-    d_axfrqlen = target.toStringRootDot().length();
-    d_adomain = d_adomains.end();   // skip loops in get() first time
-
-    return (this->*d_list_fcnt)( target, domain_id );
-  }
-  catch( LDAPTimeout &lt )
-  {
-    g_log << Logger::Warning << d_myname << " Unable to get zone " << target << " from LDAP directory: " << lt.what() << endl;
-    throw DBException( "LDAP server timeout" );
-  }
-  catch( LDAPNoConnection &lnc )
-  {
-    g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
-    if ( reconnect() )
-      this->list( target, domain_id );
-    else
-      throw PDNSException( "Failed to reconnect to LDAP server" );
-  }
-  catch( LDAPException &le )
-  {
-    g_log << Logger::Error << d_myname << " Unable to get zone " << target << " from LDAP directory: " << le.what() << endl;
-    throw PDNSException( "LDAP server unreachable" );   // try to reconnect to another server
-  }
-  catch( std::exception &e )
-  {
-    g_log << Logger::Error << d_myname << " Caught STL exception for target " << target << ": " << e.what() << endl;
-    throw DBException( "STL exception" );
-  }
-
-  return false;
-}
-
-
-
-inline bool LdapBackend::list_simple( const DNSName& target, int domain_id )
-{
-  string dn;
-  string filter;
-  string qesc;
-
-
-  dn = getArg( "basedn" );
-  qesc = toLower( d_pldap->escape( target.toStringRootDot() ) );
-
-  // search for SOARecord of target
-  filter = strbind( ":target:", "&(associatedDomain=" + qesc + ")(sOARecord=*)", getArg( "filter-axfr" ) );
-  d_msgid = d_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
-  d_pldap->getSearchEntry( d_msgid, d_result, true );
-
-  if( d_result.count( "dn" ) && !d_result["dn"].empty() )
-  {
-    if( !mustDo( "basedn-axfr-override" ) )
-    {
-      dn = d_result["dn"][0];
+    if ( *endptr != '\0' ) {
+      // NOTE: this will not give the entry for which the TTL was off.
+      // TODO: improve this.
+      //   - Check how d_getdn is used, because if it's never false then we
+      //     might as well use it.
+      g_log << Logger::Warning << d_myname << " Invalid time to live for " << d_qname << ": " << d_result["dNSTTL"][0] << endl;
     }
-    d_result.erase( "dn" );
-  }
-
-  prepare();
-  filter = strbind( ":target:", "associatedDomain=*." + qesc, getArg( "filter-axfr" ) );
-  DLOG( g_log << Logger::Debug << d_myname << " Search = basedn: " << dn << ", filter: " << filter << endl );
-  d_msgid = d_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
-
-  return true;
-}
-
-
-
-inline bool LdapBackend::list_strict( const DNSName& target, int domain_id )
-{
-  if( target.isPartOf(DNSName("in-addr.arpa")) || target.isPartOf(DNSName("ip6.arpa")) )
-  {
-    g_log << Logger::Warning << d_myname << " Request for reverse zone AXFR, but this is not supported in strict mode" << endl;
-    return false;   // AXFR isn't supported in strict mode. Use simple mode and additional PTR records
-  }
-
-  return list_simple( target, domain_id );
-}
-
-
-
-void LdapBackend::lookup( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
-{
-  try
-  {
-    d_axfrqlen = 0;
-    d_qname = qname;
-    d_adomain = d_adomains.end();   // skip loops in get() first time
-    d_qtype = qtype;
-
-    if( d_qlog ) { g_log.log( "Query: '" + qname.toStringRootDot() + "|" + qtype.getName() + "'", Logger::Error ); }
-    (this->*d_lookup_fcnt)( qtype, qname, dnspkt, zoneid );
-  }
-  catch( LDAPTimeout &lt )
-  {
-    g_log << Logger::Warning << d_myname << " Unable to search LDAP directory: " << lt.what() << endl;
-    throw DBException( "LDAP server timeout" );
-  }
-  catch( LDAPNoConnection &lnc )
-  {
-    g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
-    if ( reconnect() )
-      this->lookup( qtype, qname, dnspkt, zoneid );
-    else
-      throw PDNSException( "Failed to reconnect to LDAP server" );
-  }
-  catch( LDAPException &le )
-  {
-    g_log << Logger::Error << d_myname << " Unable to search LDAP directory: " << le.what() << endl;
-    throw PDNSException( "LDAP server unreachable" );   // try to reconnect to another server
-  }
-  catch( std::exception &e )
-  {
-    g_log << Logger::Error << d_myname << " Caught STL exception for qname " << qname << ": " << e.what() << endl;
-    throw DBException( "STL exception" );
-  }
-}
-
-
-
-void LdapBackend::lookup_simple( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
-{
-  string filter, attr, qesc;
-  const char** attributes = ldap_attrany + 1;   // skip associatedDomain
-  const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
-
-
-  qesc = toLower( d_pldap->escape( qname.toStringRootDot() ) );
-  filter = "associatedDomain=" + qesc;
-
-  if( qtype.getCode() != QType::ANY )
-  {
-    attr = qtype.getName() + "Record";
-    filter = "&(" + filter + ")(" + attr + "=*)";
-    attronly[0] = attr.c_str();
-    attributes = attronly;
-  }
-
-  filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
-
-  DLOG( g_log << Logger::Debug << d_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
-  d_msgid = d_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attributes );
-}
-
-
-
-void LdapBackend::lookup_strict( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
-{
-  int len;
-  vector<string> parts;
-  string filter, attr, qesc;
-  const char** attributes = ldap_attrany + 1;   // skip associatedDomain
-  const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
-
-
-  qesc = toLower( d_pldap->escape( qname.toStringRootDot() ) );
-  stringtok( parts, qesc, "." );
-  len = qesc.length();
-
-  if( parts.size() == 6 && len > 13 && qesc.substr( len - 13, 13 ) == ".in-addr.arpa" )   // IPv4 reverse lookups
-  {
-    filter = "aRecord=" + ptr2ip4( parts );
-    attronly[0] = "associatedDomain";
-    attributes = attronly;
-  }
-  else if( parts.size() == 34 && len > 9 && ( qesc.substr( len - 9, 9 ) == ".ip6.arpa" ) )   // IPv6 reverse lookups
-  {
-    filter = "aAAARecord=" + ptr2ip6( parts );
-    attronly[0] = "associatedDomain";
-    attributes = attronly;
-  }
-  else   // IPv4 and IPv6 lookups
-  {
-    filter = "associatedDomain=" + qesc;
-    if( qtype.getCode() != QType::ANY )
-    {
-      attr = qtype.getName() + "Record";
-      filter = "&(" + filter + ")(" + attr + "=*)";
-      attronly[0] = attr.c_str();
-      attributes = attronly;
+    else {
+      result.ttl = ttl;
     }
-  }
-
-  filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
-
-  DLOG( g_log << Logger::Debug << d_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
-  d_msgid = d_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attributes );
-}
-
-
-
-void LdapBackend::lookup_tree( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
-{
-  string filter, attr, qesc, dn;
-  const char** attributes = ldap_attrany + 1;   // skip associatedDomain
-  const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
-  vector<string> parts;
-
 
-  qesc = toLower( d_pldap->escape( qname.toStringRootDot() ) );
-  filter = "associatedDomain=" + qesc;
-
-  if( qtype.getCode() != QType::ANY )
-  {
-    attr = qtype.getName() + "Record";
-    filter = "&(" + filter + ")(" + attr + "=*)";
-    attronly[0] = attr.c_str();
-    attributes = attronly;
-  }
-
-  filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
-
-  stringtok( parts, toLower( qname.toString() ), "." );
-  for(auto i = parts.crbegin(); i != parts.crend(); i++ )
-  {
-    dn = "dc=" + *i + "," + dn;
-  }
-
-  DLOG( g_log << Logger::Debug << d_myname << " Search = basedn: " << dn + getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
-  d_msgid = d_pldap->search( dn + getArg( "basedn" ), LDAP_SCOPE_BASE, filter, attributes );
-}
-
-
-inline bool LdapBackend::prepare()
-{
-  d_adomains.clear();
-  d_ttl = d_default_ttl;
-  d_last_modified = 0;
-
-  if( d_result.count( "dNSTTL" ) && !d_result["dNSTTL"].empty() )
-  {
-    char* endptr;
-
-    d_ttl = (uint32_t) strtol( d_result["dNSTTL"][0].c_str(), &endptr, 10 );
-    if( *endptr != '\0' )
-    {
-      g_log << Logger::Warning << d_myname << " Invalid time to live for " << d_qname << ": " << d_result["dNSTTL"][0] << endl;
-      d_ttl = d_default_ttl;
-    }
+    // We have to erase the attribute, otherwise this will mess up the records retrieval later.
     d_result.erase( "dNSTTL" );
   }
 
-  if( d_result.count( "modifyTimestamp" ) && !d_result["modifyTimestamp"].empty() )
-  {
-    if( ( d_last_modified = str2tstamp( d_result["modifyTimestamp"][0] ) ) == 0 )
-    {
+  if ( d_result.count( "modifyTimestamp" ) && !d_result["modifyTimestamp"].empty() ) {
+    time_t tstamp = 0;
+    if ( ( tstamp = str2tstamp( d_result["modifyTimestamp"][0] ) ) == 0 ) {
+      // Same note as above, we don't know which entry failed here
       g_log << Logger::Warning << d_myname << " Invalid modifyTimestamp for " << d_qname << ": " << d_result["modifyTimestamp"][0] << endl;
     }
-    d_result.erase( "modifyTimestamp" );
-  }
-
-  if( !(this->*d_prepare_fcnt)() )
-  {
-    return false;
-  }
-
-  d_adomain = d_adomains.begin();
-  d_attribute = d_result.begin();
-  d_value = d_attribute->second.begin();
-
-  return true;
-}
-
-
-
-inline bool LdapBackend::prepare_simple()
-{
-  if( !d_axfrqlen )   // request was a normal lookup()
-  {
-    d_adomains.push_back( d_qname );
-  }
-  else   // request was a list() for AXFR
-  {
-    if( d_result.count( "associatedDomain" ) )
-    {
-      for(auto i = d_result["associatedDomain"].begin(); i != d_result["associatedDomain"].end(); i++ ) {
-        if( i->size() >= d_axfrqlen && i->substr( i->size() - d_axfrqlen, d_axfrqlen ) == d_qname.toStringRootDot() /* ugh */ ) {
-          d_adomains.push_back( DNSName(*i) );
-        }
-      }
-      d_result.erase( "associatedDomain" );
+    else {
+      result.lastmod = tstamp;
     }
-  }
-
-  return true;
-}
-
 
-
-inline bool LdapBackend::prepare_strict()
-{
-  if( !d_axfrqlen )   // request was a normal lookup()
-  {
-    d_adomains.push_back( d_qname );
-    if( d_result.count( "associatedDomain" ) )
-    {
-      d_result["PTRRecord"] = d_result["associatedDomain"];
-      d_result.erase( "associatedDomain" );
-    }
-  }
-  else   // request was a list() for AXFR
-  {
-    if( d_result.count( "associatedDomain" ) )
-    {
-      for(auto i = d_result["associatedDomain"].begin(); i != d_result["associatedDomain"].end(); i++ ) {
-        if( i->size() >= d_axfrqlen && i->substr( i->size() - d_axfrqlen, d_axfrqlen ) == d_qname.toStringRootDot() /* ugh */ ) {
-          d_adomains.push_back( DNSName(*i) );
-        }
-      }
-      d_result.erase( "associatedDomain" );
-    }
+    // Here too we have to erase this attribute.
+    d_result.erase( "modifyTimestamp" );
   }
-
-  return true;
 }
 
 
-
-bool LdapBackend::get( DNSResourceRecord &rr )
-{
+void LdapBackend::extract_entry_results( const DNSName& domain, const DNSResult& result_template, QType qtype ) {
+  std:: string attrname, qstr;
   QType qt;
-  vector<string> parts;
-  string attrname, qstr;
-
-
-  try
-  {
-    do
-    {
-      while( d_adomain != d_adomains.end() )
-      {
-        while( d_attribute != d_result.end() )
-        {
-          attrname = d_attribute->first;
-          qstr = attrname.substr( 0, attrname.length() - 6 );   // extract qtype string from ldap attribute name
-          qt = const_cast<char*>(toUpper( qstr ).c_str());
-
-          while( d_value != d_attribute->second.end() )
-          {
-            if(d_qtype != qt && d_qtype != QType::ANY) {
-              d_value++;
-              continue;
-            }
-
-
-            rr.qtype = qt;
-            rr.qname = *d_adomain;
-            rr.ttl = d_ttl;
-            rr.last_modified = d_last_modified;
-            rr.content = *d_value;
-            d_value++;
-
-            DLOG( g_log << Logger::Debug << d_myname << " Record = qname: " << rr.qname << ", qtype: " << (rr.qtype).getName() << ", ttl: " << rr.ttl << ", content: " << rr.content << endl );
-            return true;
-          }
-
-          d_attribute++;
-          d_value = d_attribute->second.begin();
+  bool has_records = false;
+
+  for ( const auto& attribute : d_result ) {
+    // Find if we're dealing with a record attribute
+    if ( attribute.first.length() > 6 && attribute.first.compare( attribute.first.length() - 6, 6, "Record" ) == 0 ) {
+      has_records = true;
+      attrname = attribute.first;
+      // extract qtype string from ldap attribute name by removing the 'Record' suffix.
+      qstr = attrname.substr( 0, attrname.length() - 6 );
+      qt = toUpper( qstr );
+
+      for ( const auto& value : attribute.second ) {
+        if(qtype != qt && qtype != QType::ANY) {
+          continue;
         }
-        d_adomain++;
-        d_attribute = d_result.begin();
-        d_value = d_attribute->second.begin();
-      }
-    }
-    while( d_pldap->getSearchEntry( d_msgid, d_result, d_getdn ) && prepare() );
-
-  }
-  catch( LDAPTimeout &lt )
-  {
-    g_log << Logger::Warning << d_myname << " Search failed: " << lt.what() << endl;
-    throw DBException( "LDAP server timeout" );
-  }
-  catch( LDAPException &le )
-  {
-    g_log << Logger::Error << d_myname << " Search failed: " << le.what() << endl;
-    throw PDNSException( "LDAP server unreachable" );   // try to reconnect to another server
-  }
-  catch( std::exception &e )
-  {
-    g_log << Logger::Error << d_myname << " Caught STL exception for " << d_qname << ": " << e.what() << endl;
-    throw DBException( "STL exception" );
-  }
-
-  return false;
-}
-void LdapBackend::getUpdatedMasters( vector<DomainInfo>* domains )
-{
-  string filter;
-  int msgid=0;
-  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 = d_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
-  }
-  catch( LDAPTimeout &lt )
-  {
-    g_log << Logger::Warning << d_myname << " Unable to search LDAP directory: " << lt.what() << endl;
-    throw DBException( "LDAP server timeout" );
-  }
-  catch( LDAPNoConnection &lnc )
-  {
-    g_log << Logger::Warning << d_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 )
-  {
-    g_log << Logger::Error << d_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( d_pldap->getSearchEntry( msgid, result ) ) {
-    if( !result.count( "associatedDomain" ) || result["associatedDomain"].empty() )
-      continue;
-
-    DomainInfo di;
-    if ( !getDomainInfo( DNSName( result["associatedDomain"][0] ), di ) )
-      continue;
-
-    if( di.notified_serial < di.serial )
-      domains->push_back( di );
-  }
-}
 
+        DNSResult local_result = result_template;
+        local_result.qtype = qt;
+        local_result.qname = domain;
+        local_result.value = value;
 
-
-void LdapBackend::setNotified( uint32_t id, uint32_t serial )
-{
-  string filter;
-  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 = d_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
-    d_pldap->getSearchResults( msgid, results, true );
-  }
-  catch( LDAPTimeout &lt )
-  {
-    g_log << Logger::Warning << d_myname << " Unable to search LDAP directory: " << lt.what() << endl;
-    throw DBException( "LDAP server timeout" );
-  }
-  catch( LDAPNoConnection &lnc )
-  {
-    g_log << Logger::Warning << d_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 )
-  {
-    g_log << Logger::Error << d_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;
-
-  mods[0] = &mod;
-  mods[1] = NULL;
-
-  try
-  {
-    d_pldap->modify( dn, mods );
-  }
-  catch( LDAPNoConnection &lnc )
-  {
-    g_log << Logger::Warning << d_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 )
-  {
-    g_log << Logger::Error << d_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 DNSName& domain, DomainInfo& di, bool getSerial )
-{
-  string filter;
-  SOAData sd;
-  PowerLDAP::sentry_t result;
-  const char* attronly[] = {
-    "sOARecord",
-    "PdnsDomainId",
-    "PdnsDomainNotifiedSerial",
-    "PdnsDomainLastCheck",
-    "PdnsDomainMaster",
-    "PdnsDomainType",
-    NULL
-  };
-
-  try
-  {
-    // search for SOARecord of domain
-    filter = "(&(associatedDomain=" + toLower( d_pldap->escape( domain.toStringRootDot() ) ) + ")(SOARecord=*))";
-    d_msgid = d_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
-    d_pldap->getSearchEntry( d_msgid, result );
-  }
-  catch( LDAPTimeout &lt )
-  {
-    g_log << Logger::Warning << d_myname << " Unable to search LDAP directory: " << lt.what() << endl;
-    throw DBException( "LDAP server timeout" );
-  }
-  catch( LDAPNoConnection &lnc )
-  {
-    g_log << Logger::Warning << d_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 )
-  {
-    g_log << Logger::Error << d_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( result["sOARecord"][0], sd );
-
-    if ( result.count( "PdnsDomainId" ) && !result["PdnsDomainId"].empty() )
-      di.id = std::stoi( result["PdnsDomainId"][0] );
-    else
-      di.id = 0;
-
-    di.serial = sd.serial;
-    di.zone = DNSName(domain);
-
-    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() ) {
-      for(const auto& m : result["PdnsDomainMaster"])
-        di.masters.emplace_back(m, 53);
-    }
-
-    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;
+        d_results_cache.push_back( local_result );
+      }
     }
-
-    di.backend = this;
-    return true;
   }
-
-  return false;
 }
 
 
-
-
-
 class LdapFactory : public BackendFactory
 {
   public:
index 088989ea533859ef38552a912658926ba2bf5156..b4b9cd254ae758f121a5a7b8a334b2619ca56454 100644 (file)
@@ -23,6 +23,7 @@
 #include <algorithm>
 #include <sstream>
 #include <utility>
+#include <list>
 #include <string>
 #include <cstdlib>
 #include <cctype>
@@ -106,28 +107,34 @@ static const char* ldap_attrany[] = {
 
 class LdapBackend : public DNSBackend
 {
-    bool d_getdn;
+    string d_myname;
+
     bool d_qlog;
-    int d_msgid;
-    uint32_t d_ttl;
     uint32_t d_default_ttl;
-    unsigned int d_axfrqlen;
-    time_t d_last_modified;
-    string d_myname;
+    int d_reconnect_attempts;
+
+    bool d_getdn;
+    PowerLDAP::SearchResult::Ptr d_search;
+    PowerLDAP::sentry_t d_result;
+    bool d_in_list;
+
+    struct DNSResult {
+      QType qtype;
+      DNSName qname;
+      uint32_t ttl;
+      time_t lastmod;
+      std::string value;
+    };
+    std::list<DNSResult> d_results_cache;
+
     DNSName d_qname;
+    QType d_qtype;
+
     PowerLDAP* d_pldap;
     LdapAuthenticator *d_authenticator;
-    PowerLDAP::sentry_t d_result;
-    PowerLDAP::sentry_t::iterator d_attribute;
-    vector<string>::iterator d_value;
-    vector<DNSName>::iterator d_adomain;
-    vector<DNSName> d_adomains;
-    QType d_qtype;
-    int d_reconnect_attempts;
 
     bool (LdapBackend::*d_list_fcnt)( const DNSName&, int );
     void (LdapBackend::*d_lookup_fcnt)( const QType&, const DNSName&, DNSPacket*, int );
-    bool (LdapBackend::*d_prepare_fcnt)();
 
     bool list_simple( const DNSName& target, int domain_id );
     bool list_strict( const DNSName& target, int domain_id );
@@ -136,12 +143,19 @@ class LdapBackend : public DNSBackend
     void lookup_strict( const QType& qtype, const DNSName& qdomain, DNSPacket* p, int zoneid );
     void lookup_tree( const QType& qtype, const DNSName& qdomain, DNSPacket* p, int zoneid );
 
-    bool prepare();
-    bool prepare_simple();
-    bool prepare_strict();
-
     bool reconnect();
 
+    // Extracts common attributes from the current result stored in d_result and sets them in the given DNSResult.
+    // This will modify d_result by removing attributes that may interfere with the records extraction later.
+    void extract_common_attributes( DNSResult &result );
+
+    // Extract LDAP attributes for the current result stored in d_result and create a new DNSResult that will
+    // be appended in the results cache. The result parameter is used as a template that will be copied for
+    // each result extracted from the entry.
+    // The given domain will be added as the qname attribute of the result.
+    // The qtype parameter is used to filter extracted results.
+    void extract_entry_results( const DNSName& domain, const DNSResult& result, QType qtype );
+
   public:
 
     LdapBackend( const string &suffix="" );
diff --git a/modules/ldapbackend/master.cc b/modules/ldapbackend/master.cc
new file mode 100644 (file)
index 0000000..c83b573
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ * originally authored by Norbert Sendetzky
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "exceptions.hh"
+#include "ldapbackend.hh"
+#include <cstdlib>
+
+
+void LdapBackend::getUpdatedMasters( vector<DomainInfo>* domains )
+{
+  string filter;
+  PowerLDAP::SearchResult::Ptr search;
+  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" ) );
+    search = d_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
+  }
+  catch( LDAPTimeout &lt )
+  {
+    g_log << Logger::Warning << d_myname << " Unable to search LDAP directory: " << lt.what() << endl;
+    throw DBException( "LDAP server timeout" );
+  }
+  catch( LDAPNoConnection &lnc )
+  {
+    g_log << Logger::Warning << d_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 )
+  {
+    g_log << Logger::Error << d_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( search->getNext( result ) ) {
+    if( !result.count( "associatedDomain" ) || result["associatedDomain"].empty() )
+      continue;
+
+    DomainInfo di;
+    if ( !getDomainInfo( DNSName( result["associatedDomain"][0] ), di ) )
+      continue;
+
+    if( di.notified_serial < di.serial )
+      domains->push_back( di );
+  }
+}
+
+
+
+void LdapBackend::setNotified( uint32_t id, uint32_t serial )
+{
+  string filter;
+  PowerLDAP::SearchResult::Ptr search;
+  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" ) );
+    search = d_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
+    search->getAll( results, true );
+  }
+  catch( LDAPTimeout &lt )
+  {
+    g_log << Logger::Warning << d_myname << " Unable to search LDAP directory: " << lt.what() << endl;
+    throw DBException( "LDAP server timeout" );
+  }
+  catch( LDAPNoConnection &lnc )
+  {
+    g_log << Logger::Warning << d_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 )
+  {
+    g_log << Logger::Error << d_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;
+
+  mods[0] = &mod;
+  mods[1] = NULL;
+
+  try
+  {
+    d_pldap->modify( dn, mods );
+  }
+  catch( LDAPNoConnection &lnc )
+  {
+    g_log << Logger::Warning << d_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 )
+  {
+    g_log << Logger::Error << d_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" );
+  }
+}
diff --git a/modules/ldapbackend/native.cc b/modules/ldapbackend/native.cc
new file mode 100644 (file)
index 0000000..fd14890
--- /dev/null
@@ -0,0 +1,444 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ * originally authored by Norbert Sendetzky
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "exceptions.hh"
+#include "ldapbackend.hh"
+#include <cstdlib>
+
+
+bool LdapBackend::list( const DNSName& target, int domain_id, bool include_disabled )
+{
+  try
+  {
+    d_in_list = true;
+    d_qname = target;
+    d_qtype = QType::ANY;
+    d_results_cache.clear();
+
+    return (this->*d_list_fcnt)( target, domain_id );
+  }
+  catch( LDAPTimeout &lt )
+  {
+    g_log << Logger::Warning << d_myname << " Unable to get zone " << target << " from LDAP directory: " << lt.what() << endl;
+    throw DBException( "LDAP server timeout" );
+  }
+  catch( LDAPNoConnection &lnc )
+  {
+    g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
+    if ( reconnect() )
+      this->list( target, domain_id );
+    else
+      throw PDNSException( "Failed to reconnect to LDAP server" );
+  }
+  catch( LDAPException &le )
+  {
+    g_log << Logger::Error << d_myname << " Unable to get zone " << target << " from LDAP directory: " << le.what() << endl;
+    throw PDNSException( "LDAP server unreachable" );   // try to reconnect to another server
+  }
+  catch( std::exception &e )
+  {
+    g_log << Logger::Error << d_myname << " Caught STL exception for target " << target << ": " << e.what() << endl;
+    throw DBException( "STL exception" );
+  }
+
+  return false;
+}
+
+
+
+bool LdapBackend::list_simple( const DNSName& target, int domain_id )
+{
+  string dn;
+  string filter;
+  string qesc;
+
+
+  dn = getArg( "basedn" );
+  qesc = toLower( d_pldap->escape( target.toStringRootDot() ) );
+
+  // search for SOARecord of target
+  filter = strbind( ":target:", "&(associatedDomain=" + qesc + ")(sOARecord=*)", getArg( "filter-axfr" ) );
+  PowerLDAP::SearchResult::Ptr search = d_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
+  if ( !search->getNext( d_result, true ) )
+    return false;
+
+  if( d_result.count( "dn" ) && !d_result["dn"].empty() )
+  {
+    if( !mustDo( "basedn-axfr-override" ) )
+    {
+      dn = d_result["dn"][0];
+    }
+  }
+
+  // If we have any records associated with this entry let's parse them here
+  DNSResult soa_result;
+  soa_result.ttl = d_default_ttl;
+  soa_result.lastmod = 0;
+  this->extract_common_attributes( soa_result );
+  this->extract_entry_results( d_qname, soa_result, QType(uint16_t(QType::ANY)) );
+
+  filter = strbind( ":target:", "associatedDomain=*." + qesc, getArg( "filter-axfr" ) );
+  g_log << Logger::Debug << d_myname << " Search = basedn: " << dn << ", filter: " << filter << endl;
+  d_search = d_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
+
+  return true;
+}
+
+
+bool LdapBackend::list_strict( const DNSName& target, int domain_id )
+{
+  if( target.isPartOf(DNSName("in-addr.arpa")) || target.isPartOf(DNSName("ip6.arpa")) )
+  {
+    g_log << Logger::Warning << d_myname << " Request for reverse zone AXFR, but this is not supported in strict mode" << endl;
+    return false;   // AXFR isn't supported in strict mode. Use simple mode and additional PTR records
+  }
+
+  return list_simple( target, domain_id );
+}
+
+
+
+void LdapBackend::lookup( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
+{
+  try
+  {
+    d_in_list = false;
+    d_qname = qname;
+    d_qtype = qtype;
+    d_results_cache.clear();
+
+    if( d_qlog ) { g_log.log( "Query: '" + qname.toStringRootDot() + "|" + qtype.getName() + "'", Logger::Error ); }
+    (this->*d_lookup_fcnt)( qtype, qname, dnspkt, zoneid );
+  }
+  catch( LDAPTimeout &lt )
+  {
+    g_log << Logger::Warning << d_myname << " Unable to search LDAP directory: " << lt.what() << endl;
+    throw DBException( "LDAP server timeout" );
+  }
+  catch( LDAPNoConnection &lnc )
+  {
+    g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
+    if ( reconnect() )
+      this->lookup( qtype, qname, dnspkt, zoneid );
+    else
+      throw PDNSException( "Failed to reconnect to LDAP server" );
+  }
+  catch( LDAPException &le )
+  {
+    g_log << Logger::Error << d_myname << " Unable to search LDAP directory: " << le.what() << endl;
+    throw PDNSException( "LDAP server unreachable" );   // try to reconnect to another server
+  }
+  catch( std::exception &e )
+  {
+    g_log << Logger::Error << d_myname << " Caught STL exception for qname " << qname << ": " << e.what() << endl;
+    throw DBException( "STL exception" );
+  }
+}
+
+
+
+void LdapBackend::lookup_simple( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
+{
+  string filter, attr, qesc;
+  const char** attributes = ldap_attrany + 1;   // skip associatedDomain
+  const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
+
+
+  qesc = toLower( d_pldap->escape( qname.toStringRootDot() ) );
+  filter = "associatedDomain=" + qesc;
+
+  if( qtype.getCode() != QType::ANY )
+  {
+    attr = qtype.getName() + "Record";
+    filter = "&(" + filter + ")(" + attr + "=*)";
+    attronly[0] = attr.c_str();
+    attributes = attronly;
+  }
+
+  filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
+
+  g_log << Logger::Debug << d_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl;
+  d_search = d_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attributes );
+}
+
+
+
+void LdapBackend::lookup_strict( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
+{
+  int len;
+  vector<string> parts;
+  string filter, attr, qesc;
+  const char** attributes = ldap_attrany + 1;   // skip associatedDomain
+  const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
+
+
+  qesc = toLower( d_pldap->escape( qname.toStringRootDot() ) );
+  stringtok( parts, qesc, "." );
+  len = qesc.length();
+
+  if( parts.size() == 6 && len > 13 && qesc.substr( len - 13, 13 ) == ".in-addr.arpa" )   // IPv4 reverse lookups
+  {
+    filter = "aRecord=" + ptr2ip4( parts );
+    attronly[0] = "associatedDomain";
+    attributes = attronly;
+  }
+  else if( parts.size() == 34 && len > 9 && ( qesc.substr( len - 9, 9 ) == ".ip6.arpa" ) )   // IPv6 reverse lookups
+  {
+    filter = "aAAARecord=" + ptr2ip6( parts );
+    attronly[0] = "associatedDomain";
+    attributes = attronly;
+  }
+  else   // IPv4 and IPv6 lookups
+  {
+    filter = "associatedDomain=" + qesc;
+    if( qtype.getCode() != QType::ANY )
+    {
+      attr = qtype.getName() + "Record";
+      filter = "&(" + filter + ")(" + attr + "=*)";
+      attronly[0] = attr.c_str();
+      attributes = attronly;
+    }
+  }
+
+  filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
+
+  g_log << Logger::Debug << d_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl;
+  d_search = d_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attributes );
+}
+
+
+
+void LdapBackend::lookup_tree( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
+{
+  string filter, attr, qesc, dn;
+  const char** attributes = ldap_attrany + 1;   // skip associatedDomain
+  const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
+  vector<string> parts;
+
+
+  qesc = toLower( d_pldap->escape( qname.toStringRootDot() ) );
+  filter = "associatedDomain=" + qesc;
+
+  if( qtype.getCode() != QType::ANY )
+  {
+    attr = qtype.getName() + "Record";
+    filter = "&(" + filter + ")(" + attr + "=*)";
+    attronly[0] = attr.c_str();
+    attributes = attronly;
+  }
+
+  filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
+
+  stringtok( parts, toLower( qname.toString() ), "." );
+  for(auto i = parts.crbegin(); i != parts.crend(); i++ )
+  {
+    dn = "dc=" + *i + "," + dn;
+  }
+
+  g_log << Logger::Debug << d_myname << " Search = basedn: " << dn + getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl;
+  d_search = d_pldap->search( dn + getArg( "basedn" ), LDAP_SCOPE_BASE, filter, attributes );
+}
+
+
+bool LdapBackend::get( DNSResourceRecord &rr )
+{
+  if ( d_results_cache.empty() ) {
+    while ( d_results_cache.empty() ) {
+      bool exhausted = false;
+      bool valid_entry_found = false;
+
+      while ( !valid_entry_found && !exhausted ) {
+        try {
+          exhausted = !d_search->getNext( d_result, true );
+        }
+        catch( LDAPException &le )
+        {
+          g_log << Logger::Error << d_myname << " Failed to get next result: " << le.what() << endl;
+          throw PDNSException( "Get next result impossible" );
+        }
+
+        if ( !exhausted ) {
+          if ( !d_in_list ) {
+            // All entries are valid here
+            valid_entry_found = true;
+          }
+          else {
+            // If we're called after list() then the entry *must* contain
+            // associatedDomain, otherwise let's just skip it
+            if ( d_result.count( "associatedDomain" ) )
+              valid_entry_found = true;
+          }
+        }
+      }
+
+      if ( exhausted ) {
+        break;
+      }
+
+      DNSResult result_template;
+      result_template.ttl = d_default_ttl;
+      result_template.lastmod = 0;
+      this->extract_common_attributes( result_template );
+
+      std::vector<std::string> associatedDomains;
+
+      if ( d_result.count( "associatedDomain" ) ) {
+        if ( d_in_list ) {
+          // We can have more than one associatedDomain in the entry, so for each of them we have to check
+          // that they are indeed under the domain we've been asked to list (nothing enforces this, so you
+          // can have one associatedDomain set to "host.first-domain.com" and another one set to
+          // "host.second-domain.com"). Better not return the latter I guess :)
+          // We also have to generate one DNSResult per DNS-relevant attribute. As we've asked only for them
+          // and the others above we've already cleaned it's just a matter of iterating over them.
+
+          unsigned int axfrqlen = d_qname.toStringRootDot().length();
+          for ( auto i = d_result["associatedDomain"].begin(); i != d_result["associatedDomain"].end(); ++i ) {
+            // Sanity checks: is this associatedDomain attribute under the requested domain?
+            if ( i->size() >= axfrqlen && i->substr( i->size() - axfrqlen, axfrqlen ) == d_qname.toStringRootDot() )
+              associatedDomains.push_back( *i );
+          }
+        }
+        else {
+          // This was a lookup in strict mode, so we add the reverse lookup
+          // information manually.
+          d_result["pTRRecord"] = d_result["associatedDomain"];
+        }
+      }
+
+      if ( d_in_list ) {
+        for ( const auto& domain : associatedDomains )
+          this->extract_entry_results( DNSName( domain ), result_template, QType(uint16_t(QType::ANY)) );
+      }
+      else {
+        this->extract_entry_results( d_qname, result_template, QType(uint16_t(QType::ANY)) );
+      }
+    }
+
+    if ( d_results_cache.empty() )
+      return false;
+  }
+
+  DNSResult result = d_results_cache.back();
+  d_results_cache.pop_back();
+  rr.qtype = result.qtype;
+  rr.qname = result.qname;
+  rr.ttl = result.ttl;
+  rr.last_modified = 0;
+  rr.content = result.value;
+
+  g_log << Logger::Debug << d_myname << " Record = qname: " << rr.qname << ", qtype: " << (rr.qtype).getName() << ", ttl: " << rr.ttl << ", content: " << rr.content << endl;
+  return true;
+}
+
+
+bool LdapBackend::getDomainInfo( const DNSName& domain, DomainInfo& di, bool getSerial )
+{
+  string filter;
+  SOAData sd;
+  PowerLDAP::sentry_t result;
+  const char* attronly[] = {
+    "sOARecord",
+    "PdnsDomainId",
+    "PdnsDomainNotifiedSerial",
+    "PdnsDomainLastCheck",
+    "PdnsDomainMaster",
+    "PdnsDomainType",
+    NULL
+  };
+
+  try
+  {
+    // search for SOARecord of domain
+    filter = "(&(associatedDomain=" + toLower( d_pldap->escape( domain.toStringRootDot() ) ) + ")(SOARecord=*))";
+    d_search = d_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
+    d_search->getNext( result );
+  }
+  catch( LDAPTimeout &lt )
+  {
+    g_log << Logger::Warning << d_myname << " Unable to search LDAP directory: " << lt.what() << endl;
+    throw DBException( "LDAP server timeout" );
+  }
+  catch( LDAPNoConnection &lnc )
+  {
+    g_log << Logger::Warning << d_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 )
+  {
+    g_log << Logger::Error << d_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( result["sOARecord"][0], sd );
+
+    if ( result.count( "PdnsDomainId" ) && !result["PdnsDomainId"].empty() )
+      di.id = std::stoi( result["PdnsDomainId"][0] );
+    else
+      di.id = 0;
+
+    di.serial = sd.serial;
+    di.zone = DNSName(domain);
+
+    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() ) {
+      for(const auto &m : result["PdnsDomainMaster"])
+        di.masters.emplace_back(m, 53);
+    }
+
+    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;
+    }
+
+    di.backend = this;
+    return true;
+  }
+
+  return false;
+}
index 44e828374eef5cd6a06e3b2c5d93486cc549dba9..131e1c82cdddc9a0e8e7c5467f991de8b64db57b 100644 (file)
 #include <sys/time.h>
 
 
+PowerLDAP::SearchResult::SearchResult( int msgid, LDAP* ld )
+  : d_msgid( msgid ), d_ld( ld ), d_finished( false )
+{
+}
+
+
+PowerLDAP::SearchResult::~SearchResult()
+{
+  if ( !d_finished )
+    ldap_abandon_ext( d_ld, d_msgid, NULL, NULL ); // We don't really care about the return code as there's
+                                                   // not much we can do now
+}
+
+
+bool PowerLDAP::SearchResult::getNext( PowerLDAP::sentry_t& entry, bool dn, int timeout )
+{
+  int i;
+  char* attr;
+  BerElement* ber;
+  struct berval** berval;
+  vector<string> values;
+  LDAPMessage* result = NULL;
+  LDAPMessage* object;
+
+  while ( !d_finished && result == NULL ) {
+    i = ldapWaitResult( d_ld, d_msgid, 5, &result );
+    switch ( i ) {
+      case -1:
+        int err_code;
+        ldapGetOption( d_ld, LDAP_OPT_ERROR_NUMBER, &err_code );
+        if ( err_code == LDAP_SERVER_DOWN || err_code == LDAP_CONNECT_ERROR )
+          throw LDAPNoConnection();
+        else
+          throw LDAPException( "Error waiting for LDAP result: " + ldapGetError( d_ld, err_code ) );
+        break;
+      case 0:
+        throw LDAPTimeout();
+        break;
+      case LDAP_NO_SUCH_OBJECT:
+        return false;
+      case LDAP_RES_SEARCH_REFERENCE:
+        ldap_msgfree( result );
+        result = NULL;
+        break;
+      case LDAP_RES_SEARCH_RESULT:
+        d_finished = true;
+        ldap_msgfree( result );
+        break;
+      case LDAP_RES_SEARCH_ENTRY:
+        // Yay!
+        break;
+    }
+  }
+
+  if ( d_finished )
+    return false;
+
+  if( ( object = ldap_first_entry( d_ld, result ) ) == NULL )
+  {
+    ldap_msgfree( result );
+    throw LDAPException( "Couldn't get first result entry: " + ldapGetError( d_ld, -1 ) );
+  }
+
+  entry.clear();
+
+  if( dn )
+  {
+    attr = ldap_get_dn( d_ld, object );
+    values.push_back( string( attr ) );
+    ldap_memfree( attr );
+    entry["dn"] = values;
+  }
+
+  if( ( attr = ldap_first_attribute( d_ld, object, &ber ) ) != NULL )
+  {
+    do
+    {
+      if( ( berval = ldap_get_values_len( d_ld, object, attr ) ) != NULL )
+      {
+        values.clear();
+        for( i = 0; i < ldap_count_values_len( berval ); i++ )
+        {
+          values.push_back( berval[i]->bv_val );   // use berval[i]->bv_len for non string values?
+        }
+
+        entry[attr] = values;
+        ldap_value_free_len( berval );
+      }
+      ldap_memfree( attr );
+    }
+    while( ( attr = ldap_next_attribute( d_ld, object, ber ) ) != NULL );
+
+    ber_free( ber, 0 );
+  }
+
+  ldap_msgfree( result );
+  return true;
+}
+
+
+void PowerLDAP::SearchResult::getAll( PowerLDAP::sresult_t& results, bool dn, int timeout )
+{
+  PowerLDAP::sentry_t entry;
+
+  while( getNext( entry, dn, timeout ) )
+  {
+    results.push_back( entry );
+  }
+}
+
 
 PowerLDAP::PowerLDAP( const string& hosts, uint16_t port, bool tls, int timeout )
 {
@@ -154,7 +264,7 @@ void PowerLDAP::bind( const string& ldapbinddn, const string& ldapsecret, int me
   }
 #endif
 
-  waitResult( msgid, NULL );
+  ldapWaitResult( d_ld, msgid, d_timeout, NULL );
 }
 
 
@@ -180,7 +290,7 @@ void PowerLDAP::modify( const string &dn, LDAPMod *mods[], LDAPControl **scontro
 }
 
 
-int PowerLDAP::search( const string& base, int scope, const string& filter, const char** attr )
+PowerLDAP::SearchResult::Ptr PowerLDAP::search( const string& base, int scope, const string& filter, const char** attr )
 {
   int msgid, rc;
 
@@ -188,7 +298,7 @@ int PowerLDAP::search( const string& base, int scope, const string& filter, cons
     throw LDAPException( "Starting LDAP search: " + getError( rc ) );
   }
 
-  return msgid;
+  return SearchResult::Ptr( new SearchResult( msgid, d_ld ) );
 }
 
 
index 48d5d919b292c2f4ff414b5cc024654a40d861e9..81b064e976eda06194005ea9004f1795e9436c0a 100644 (file)
@@ -20,7 +20,9 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
+#include <list>
 #include <map>
+#include <memory>
 #include <string>
 #include <vector>
 #include <stdexcept>
@@ -35,6 +37,7 @@
 #ifndef POWERLDAP_HH
 #define POWERLDAP_HH
 
+using std::list;
 using std::map;
 using std::string;
 using std::vector;
@@ -52,11 +55,29 @@ class PowerLDAP
     const string getError( int rc = -1 );
     int waitResult( int msgid = LDAP_RES_ANY, LDAPMessage** result = NULL );
     void ensureConnect();
-    
+
   public:
     typedef map<string, vector<string> > sentry_t;
     typedef vector<sentry_t> sresult_t;
-  
+
+    class SearchResult {
+        LDAP* d_ld;
+        int d_msgid;
+        bool d_finished;
+
+        SearchResult( const SearchResult& other );
+        SearchResult& operator=( const SearchResult& other );
+
+      public:
+        typedef std::unique_ptr<SearchResult> Ptr;
+
+        SearchResult( int msgid, LDAP* ld );
+        ~SearchResult();
+
+        bool getNext( PowerLDAP::sentry_t& entry, bool dn = false, int timeout = 5 );
+        void getAll( PowerLDAP::sresult_t& results, bool dn = false, int timeout = 5 );
+    };
+
     PowerLDAP( const string& hosts, uint16_t port, bool tls, int timeout );
     ~PowerLDAP();
   
@@ -68,7 +89,7 @@ class PowerLDAP
     void bind( LdapAuthenticator *authenticator );
     void bind( const string& ldapbinddn = "", const string& ldapsecret = "", int method = LDAP_AUTH_SIMPLE );
     void simpleBind( const string& ldapbinddn = "", const string& ldapsecret = "" );
-    int search( const string& base, int scope, const string& filter, const char** attr = 0 );
+    SearchResult::Ptr search( const string& base, int scope, const string& filter, const char** attr = 0 );
     void modify( const string& dn, LDAPMod *mods[], LDAPControl **scontrols = 0, LDAPControl **ccontrols = 0 );
   
     bool getSearchEntry( int msgid, sentry_t& entry, bool dn = false );