From: Grégory Oestreicher Date: Sat, 24 Jun 2017 21:46:44 +0000 (+0200) Subject: LDAP search revamp X-Git-Tag: dnsdist-1.3.1~167^2~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4016570ed101d1773bcf586f8f2598fd68163ca3;p=thirdparty%2Fpdns.git LDAP search revamp Use a dedicated search result class, and factorize LDAP entries parsing. --- diff --git a/modules/ldapbackend/Makefile.am b/modules/ldapbackend/Makefile.am index 8eecd8f8bc..53020a64dd 100644 --- a/modules/ldapbackend/Makefile.am +++ b/modules/ldapbackend/Makefile.am @@ -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 \ diff --git a/modules/ldapbackend/OBJECTFILES b/modules/ldapbackend/OBJECTFILES index 0f75836884..d9864867e7 100644 --- a/modules/ldapbackend/OBJECTFILES +++ b/modules/ldapbackend/OBJECTFILES @@ -1 +1 @@ -ldapbackend.lo powerldap.lo ldaputils.lo ldapauthenticator.lo +ldapbackend.lo master.lo native.lo powerldap.lo ldaputils.lo ldapauthenticator.lo diff --git a/modules/ldapbackend/exceptions.hh b/modules/ldapbackend/exceptions.hh index 53e0145f5e..3436e38d18 100644 --- a/modules/ldapbackend/exceptions.hh +++ b/modules/ldapbackend/exceptions.hh @@ -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 diff --git a/modules/ldapbackend/ldapbackend.cc b/modules/ldapbackend/ldapbackend.cc index 5f913aae36..b6b4687d61 100644 --- a/modules/ldapbackend/ldapbackend.cc +++ b/modules/ldapbackend/ldapbackend.cc @@ -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 < ) - { - 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 < ) - { - 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 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 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 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(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 < ) - { - 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* 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 < ) - { - 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 < ) - { - 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( 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 < ) - { - 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: diff --git a/modules/ldapbackend/ldapbackend.hh b/modules/ldapbackend/ldapbackend.hh index 088989ea53..b4b9cd254a 100644 --- a/modules/ldapbackend/ldapbackend.hh +++ b/modules/ldapbackend/ldapbackend.hh @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -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 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::iterator d_value; - vector::iterator d_adomain; - vector 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 index 0000000000..c83b573984 --- /dev/null +++ b/modules/ldapbackend/master.cc @@ -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 + + +void LdapBackend::getUpdatedMasters( vector* 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 < ) + { + 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 < ) + { + 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( 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 index 0000000000..fd14890913 --- /dev/null +++ b/modules/ldapbackend/native.cc @@ -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 + + +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 < ) + { + 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 < ) + { + 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 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 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 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 < ) + { + 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; +} diff --git a/modules/ldapbackend/powerldap.cc b/modules/ldapbackend/powerldap.cc index 44e828374e..131e1c82cd 100644 --- a/modules/ldapbackend/powerldap.cc +++ b/modules/ldapbackend/powerldap.cc @@ -31,6 +31,116 @@ #include +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 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 ) ); } diff --git a/modules/ldapbackend/powerldap.hh b/modules/ldapbackend/powerldap.hh index 48d5d919b2..81b064e976 100644 --- a/modules/ldapbackend/powerldap.hh +++ b/modules/ldapbackend/powerldap.hh @@ -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 #include +#include #include #include #include @@ -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 > sentry_t; typedef vector 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 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 );