libldapbackend_la_SOURCES = \
ldapbackend.cc ldapbackend.hh \
+ master.cc native.cc \
powerldap.cc powerldap.hh \
utils.hh exceptions.hh \
ldaputils.hh ldaputils.cc \
-ldapbackend.lo powerldap.lo ldaputils.lo ldapauthenticator.lo
+ldapbackend.lo master.lo native.lo powerldap.lo ldaputils.lo ldapauthenticator.lo
explicit LDAPNoConnection() : LDAPException( "No connection to LDAP server" ) {}
};
+class LDAPNoSuchObject : public LDAPException
+{
+ public:
+ explicit LDAPNoSuchObject() : LDAPException( "No such object" ) {}
+};
+
#endif // LDAPEXCEPTIONS_HH
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 );
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" )
{
{
d_list_fcnt = &LdapBackend::list_strict;
d_lookup_fcnt = &LdapBackend::lookup_strict;
- d_prepare_fcnt = &LdapBackend::prepare_strict;
}
stringtok( hosts, getArg( "host" ), ", " );
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;
}
+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<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 < )
- {
- 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 < )
- {
- 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<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 < )
- {
- 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:
#include <algorithm>
#include <sstream>
#include <utility>
+#include <list>
#include <string>
#include <cstdlib>
#include <cctype>
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 );
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="" );
--- /dev/null
+/*
+ * 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 < )
+ {
+ 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<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" );
+ }
+}
--- /dev/null
+/*
+ * 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 < )
+ {
+ 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<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 < )
+ {
+ 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;
+}
#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 )
{
}
#endif
- waitResult( msgid, NULL );
+ ldapWaitResult( d_ld, msgid, d_timeout, NULL );
}
}
-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;
throw LDAPException( "Starting LDAP search: " + getError( rc ) );
}
- return msgid;
+ return SearchResult::Ptr( new SearchResult( msgid, d_ld ) );
}
* 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>
#ifndef POWERLDAP_HH
#define POWERLDAP_HH
+using std::list;
using std::map;
using std::string;
using std::vector;
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();
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 );