2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 * originally authored by Norbert Sendetzky
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of version 2 of the GNU General Public License as
8 * published by the Free Software Foundation.
10 * In addition, for the avoidance of any doubt, permission is granted to
11 * link this program with OpenSSL and to (re)distribute the binaries
12 * produced as the result of such linking.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "exceptions.hh"
24 #include "ldapbackend.hh"
28 bool LdapBackend::list( const DNSName
& target
, int domain_id
, bool include_disabled
)
35 d_results_cache
.clear();
37 return (this->*d_list_fcnt
)( target
, domain_id
);
39 catch( LDAPTimeout
<
)
41 g_log
<< Logger::Warning
<< d_myname
<< " Unable to get zone " << target
<< " from LDAP directory: " << lt
.what() << endl
;
42 throw DBException( "LDAP server timeout" );
44 catch( LDAPNoConnection
&lnc
)
46 g_log
<< Logger::Warning
<< d_myname
<< " Connection to LDAP lost, trying to reconnect" << endl
;
48 this->list( target
, domain_id
);
50 throw PDNSException( "Failed to reconnect to LDAP server" );
52 catch( LDAPException
&le
)
54 g_log
<< Logger::Error
<< d_myname
<< " Unable to get zone " << target
<< " from LDAP directory: " << le
.what() << endl
;
55 throw PDNSException( "LDAP server unreachable" ); // try to reconnect to another server
57 catch( std::exception
&e
)
59 g_log
<< Logger::Error
<< d_myname
<< " Caught STL exception for target " << target
<< ": " << e
.what() << endl
;
60 throw DBException( "STL exception" );
68 bool LdapBackend::list_simple( const DNSName
& target
, int domain_id
)
75 dn
= getArg( "basedn" );
76 qesc
= toLower( d_pldap
->escape( target
.toStringRootDot() ) );
78 // search for SOARecord of target
79 filter
= strbind( ":target:", "&(associatedDomain=" + qesc
+ ")(sOARecord=*)", getArg( "filter-axfr" ) );
80 PowerLDAP::SearchResult::Ptr search
= d_pldap
->search( dn
, LDAP_SCOPE_SUBTREE
, filter
, (const char**) ldap_attrany
);
81 if ( !search
->getNext( d_result
, true ) )
84 if( d_result
.count( "dn" ) && !d_result
["dn"].empty() )
86 if( !mustDo( "basedn-axfr-override" ) )
88 dn
= d_result
["dn"][0];
92 // If we have any records associated with this entry let's parse them here
94 soa_result
.ttl
= d_default_ttl
;
95 soa_result
.lastmod
= 0;
96 this->extract_common_attributes( soa_result
);
97 this->extract_entry_results( d_qname
, soa_result
, QType(uint16_t(QType::ANY
)) );
99 filter
= strbind( ":target:", "associatedDomain=*." + qesc
, getArg( "filter-axfr" ) );
100 g_log
<< Logger::Debug
<< d_myname
<< " Search = basedn: " << dn
<< ", filter: " << filter
<< endl
;
101 d_search
= d_pldap
->search( dn
, LDAP_SCOPE_SUBTREE
, filter
, (const char**) ldap_attrany
);
107 bool LdapBackend::list_strict( const DNSName
& target
, int domain_id
)
109 if( target
.isPartOf(DNSName("in-addr.arpa")) || target
.isPartOf(DNSName("ip6.arpa")) )
111 g_log
<< Logger::Warning
<< d_myname
<< " Request for reverse zone AXFR, but this is not supported in strict mode" << endl
;
112 return false; // AXFR isn't supported in strict mode. Use simple mode and additional PTR records
115 return list_simple( target
, domain_id
);
120 void LdapBackend::lookup( const QType
&qtype
, const DNSName
&qname
, DNSPacket
*dnspkt
, int zoneid
)
127 d_results_cache
.clear();
129 if( d_qlog
) { g_log
.log( "Query: '" + qname
.toStringRootDot() + "|" + qtype
.getName() + "'", Logger::Error
); }
130 (this->*d_lookup_fcnt
)( qtype
, qname
, dnspkt
, zoneid
);
132 catch( LDAPTimeout
<
)
134 g_log
<< Logger::Warning
<< d_myname
<< " Unable to search LDAP directory: " << lt
.what() << endl
;
135 throw DBException( "LDAP server timeout" );
137 catch( LDAPNoConnection
&lnc
)
139 g_log
<< Logger::Warning
<< d_myname
<< " Connection to LDAP lost, trying to reconnect" << endl
;
141 this->lookup( qtype
, qname
, dnspkt
, zoneid
);
143 throw PDNSException( "Failed to reconnect to LDAP server" );
145 catch( LDAPException
&le
)
147 g_log
<< Logger::Error
<< d_myname
<< " Unable to search LDAP directory: " << le
.what() << endl
;
148 throw PDNSException( "LDAP server unreachable" ); // try to reconnect to another server
150 catch( std::exception
&e
)
152 g_log
<< Logger::Error
<< d_myname
<< " Caught STL exception for qname " << qname
<< ": " << e
.what() << endl
;
153 throw DBException( "STL exception" );
159 void LdapBackend::lookup_simple( const QType
&qtype
, const DNSName
&qname
, DNSPacket
*dnspkt
, int zoneid
)
161 string filter
, attr
, qesc
;
162 const char** attributes
= ldap_attrany
+ 1; // skip associatedDomain
163 const char* attronly
[] = { NULL
, "dNSTTL", "modifyTimestamp", "PdnsRecordTTL", "PdnsRecordAuth", "PdnsRecordOrdername", NULL
};
166 qesc
= toLower( d_pldap
->escape( qname
.toStringRootDot() ) );
167 filter
= "associatedDomain=" + qesc
;
169 if( qtype
.getCode() != QType::ANY
)
171 attr
= qtype
.getName() + "Record";
172 filter
= "&(" + filter
+ ")(" + attr
+ "=*)";
173 attronly
[0] = attr
.c_str();
174 attributes
= attronly
;
177 filter
= strbind( ":target:", filter
, getArg( "filter-lookup" ) );
179 g_log
<< Logger::Debug
<< d_myname
<< " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter
<< ", qtype: " << qtype
.getName() << endl
;
180 d_search
= d_pldap
->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE
, filter
, attributes
);
185 void LdapBackend::lookup_strict( const QType
&qtype
, const DNSName
&qname
, DNSPacket
*dnspkt
, int zoneid
)
188 vector
<string
> parts
;
189 string filter
, attr
, qesc
;
190 const char** attributes
= ldap_attrany
+ 1; // skip associatedDomain
191 const char* attronly
[] = { NULL
, "dNSTTL", "modifyTimestamp", "PdnsRecordTTL", "PdnsRecordAuth", "PdnsRecordOrdername", NULL
};
194 qesc
= toLower( d_pldap
->escape( qname
.toStringRootDot() ) );
195 stringtok( parts
, qesc
, "." );
198 if( parts
.size() == 6 && len
> 13 && qesc
.substr( len
- 13, 13 ) == ".in-addr.arpa" ) // IPv4 reverse lookups
200 filter
= "aRecord=" + ptr2ip4( parts
);
201 attronly
[0] = "associatedDomain";
202 attributes
= attronly
;
204 else if( parts
.size() == 34 && len
> 9 && ( qesc
.substr( len
- 9, 9 ) == ".ip6.arpa" ) ) // IPv6 reverse lookups
206 filter
= "aAAARecord=" + ptr2ip6( parts
);
207 attronly
[0] = "associatedDomain";
208 attributes
= attronly
;
210 else // IPv4 and IPv6 lookups
212 filter
= "associatedDomain=" + qesc
;
215 if( qtype
.getCode() != QType::ANY
)
217 attr
= qtype
.getName() + "Record";
218 filter
= "&(" + filter
+ ")(" + attr
+ "=*)";
219 attronly
[0] = attr
.c_str();
220 attributes
= attronly
;
223 filter
= strbind( ":target:", filter
, getArg( "filter-lookup" ) );
225 g_log
<< Logger::Debug
<< d_myname
<< " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter
<< ", qtype: " << qtype
.getName() << endl
;
226 d_search
= d_pldap
->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE
, filter
, attributes
);
231 void LdapBackend::lookup_tree( const QType
&qtype
, const DNSName
&qname
, DNSPacket
*dnspkt
, int zoneid
)
233 string filter
, attr
, qesc
, dn
;
234 const char** attributes
= ldap_attrany
+ 1; // skip associatedDomain
235 const char* attronly
[] = { NULL
, "dNSTTL", "modifyTimestamp", "PdnsRecordTTL", "PdnsRecordAuth", "PdnsRecordOrdername", NULL
};
236 vector
<string
> parts
;
239 qesc
= toLower( d_pldap
->escape( qname
.toStringRootDot() ) );
240 filter
= "associatedDomain=" + qesc
;
242 if( qtype
.getCode() != QType::ANY
)
244 attr
= qtype
.getName() + "Record";
245 filter
= "&(" + filter
+ ")(" + attr
+ "=*)";
246 attronly
[0] = attr
.c_str();
247 attributes
= attronly
;
250 filter
= strbind( ":target:", filter
, getArg( "filter-lookup" ) );
252 stringtok( parts
, toLower( qname
.toString() ), "." );
253 for(auto i
= parts
.crbegin(); i
!= parts
.crend(); i
++ )
255 dn
= "dc=" + *i
+ "," + dn
;
258 g_log
<< Logger::Debug
<< d_myname
<< " Search = basedn: " << dn
+ getArg( "basedn" ) << ", filter: " << filter
<< ", qtype: " << qtype
.getName() << endl
;
259 d_search
= d_pldap
->search( dn
+ getArg( "basedn" ), LDAP_SCOPE_BASE
, filter
, attributes
);
263 bool LdapBackend::get( DNSResourceRecord
&rr
)
265 if ( d_results_cache
.empty() ) {
266 while ( d_results_cache
.empty() ) {
267 bool exhausted
= false;
268 bool valid_entry_found
= false;
270 while ( !valid_entry_found
&& !exhausted
) {
272 exhausted
= !d_search
->getNext( d_result
, true );
274 catch( LDAPException
&le
)
276 g_log
<< Logger::Error
<< d_myname
<< " Failed to get next result: " << le
.what() << endl
;
277 throw PDNSException( "Get next result impossible" );
282 // All entries are valid here
283 valid_entry_found
= true;
286 // If we're called after list() then the entry *must* contain
287 // associatedDomain, otherwise let's just skip it
288 if ( d_result
.count( "associatedDomain" ) )
289 valid_entry_found
= true;
298 DNSResult result_template
;
299 result_template
.ttl
= d_default_ttl
;
300 result_template
.lastmod
= 0;
301 this->extract_common_attributes( result_template
);
303 std::vector
<std::string
> associatedDomains
;
305 if ( d_result
.count( "associatedDomain" ) ) {
307 // We can have more than one associatedDomain in the entry, so for each of them we have to check
308 // that they are indeed under the domain we've been asked to list (nothing enforces this, so you
309 // can have one associatedDomain set to "host.first-domain.com" and another one set to
310 // "host.second-domain.com"). Better not return the latter I guess :)
311 // We also have to generate one DNSResult per DNS-relevant attribute. As we've asked only for them
312 // and the others above we've already cleaned it's just a matter of iterating over them.
314 unsigned int axfrqlen
= d_qname
.toStringRootDot().length();
315 for ( auto i
= d_result
["associatedDomain"].begin(); i
!= d_result
["associatedDomain"].end(); ++i
) {
316 // Sanity checks: is this associatedDomain attribute under the requested domain?
317 if ( i
->size() >= axfrqlen
&& i
->substr( i
->size() - axfrqlen
, axfrqlen
) == d_qname
.toStringRootDot() )
318 associatedDomains
.push_back( *i
);
322 // This was a lookup in strict mode, so we add the reverse lookup
323 // information manually.
324 d_result
["pTRRecord"] = d_result
["associatedDomain"];
329 for ( const auto& domain
: associatedDomains
)
330 this->extract_entry_results( DNSName( domain
), result_template
, QType(uint16_t(QType::ANY
)) );
333 this->extract_entry_results( d_qname
, result_template
, QType(uint16_t(QType::ANY
)) );
337 if ( d_results_cache
.empty() )
341 DNSResult result
= d_results_cache
.back();
342 d_results_cache
.pop_back();
343 rr
.qtype
= result
.qtype
;
344 rr
.qname
= result
.qname
;
346 rr
.last_modified
= 0;
347 rr
.content
= result
.value
;
348 rr
.auth
= result
.auth
;
350 g_log
<< Logger::Debug
<< d_myname
<< " Record = qname: " << rr
.qname
<< ", qtype: " << (rr
.qtype
).getName() << ", ttl: " << rr
.ttl
<< ", content: " << rr
.content
<< endl
;
355 bool LdapBackend::getDomainInfo( const DNSName
& domain
, DomainInfo
& di
, bool getSerial
)
359 PowerLDAP::sentry_t result
;
360 const char* attronly
[] = {
363 "PdnsDomainNotifiedSerial",
364 "PdnsDomainLastCheck",
372 // search for SOARecord of domain
373 filter
= "(&(associatedDomain=" + toLower( d_pldap
->escape( domain
.toStringRootDot() ) ) + ")(SOARecord=*))";
374 d_search
= d_pldap
->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE
, filter
, attronly
);
375 if (!d_search
->getNext( result
)) {
379 catch( LDAPTimeout
<
)
381 g_log
<< Logger::Warning
<< d_myname
<< " Unable to search LDAP directory: " << lt
.what() << endl
;
382 throw DBException( "LDAP server timeout" );
384 catch( LDAPNoConnection
&lnc
)
386 g_log
<< Logger::Warning
<< d_myname
<< " Connection to LDAP lost, trying to reconnect" << endl
;
388 this->getDomainInfo( domain
, di
);
390 throw PDNSException( "Failed to reconnect to LDAP server" );
392 catch( LDAPException
&le
)
394 g_log
<< Logger::Error
<< d_myname
<< " Unable to search LDAP directory: " << le
.what() << endl
;
395 throw PDNSException( "LDAP server unreachable" ); // try to reconnect to another server
397 catch( std::exception
&e
)
399 throw DBException( "STL exception" );
402 if( result
.count( "sOARecord" ) && !result
["sOARecord"].empty() )
405 fillSOAData( result
["sOARecord"][0], sd
);
407 if ( result
.count( "PdnsDomainId" ) && !result
["PdnsDomainId"].empty() )
408 di
.id
= std::stoi( result
["PdnsDomainId"][0] );
412 di
.serial
= sd
.serial
;
413 di
.zone
= DNSName(domain
);
415 if( result
.count( "PdnsDomainLastCheck" ) && !result
["PdnsDomainLastCheck"].empty() )
416 di
.last_check
= pdns_stou( result
["PdnsDomainLastCheck"][0] );
420 if ( result
.count( "PdnsDomainNotifiedSerial" ) && !result
["PdnsDomainNotifiedSerial"].empty() )
421 di
.notified_serial
= pdns_stou( result
["PdnsDomainNotifiedSerial"][0] );
423 di
.notified_serial
= 0;
425 if ( result
.count( "PdnsDomainMaster" ) && !result
["PdnsDomainMaster"].empty() ) {
426 for(const auto &m
: result
["PdnsDomainMaster"])
427 di
.masters
.emplace_back(m
, 53);
430 if ( result
.count( "PdnsDomainType" ) && !result
["PdnsDomainType"].empty() ) {
431 string kind
= result
["PdnsDomainType"][0];
432 if ( kind
== "master" )
433 di
.kind
= DomainInfo::Master
;
434 else if ( kind
== "slave" )
435 di
.kind
= DomainInfo::Slave
;
437 di
.kind
= DomainInfo::Native
;
440 di
.kind
= DomainInfo::Native
;