]> git.ipfire.org Git - thirdparty/pdns.git/blob - modules/ldapbackend/native.cc
auth: Check the return of getNext() in LdapBackend::getDomainInfo()
[thirdparty/pdns.git] / modules / ldapbackend / native.cc
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 * originally authored by Norbert Sendetzky
5 *
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.
9 *
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.
13 *
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.
18 *
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.
22 */
23 #include "exceptions.hh"
24 #include "ldapbackend.hh"
25 #include <cstdlib>
26
27
28 bool LdapBackend::list( const DNSName& target, int domain_id, bool include_disabled )
29 {
30 try
31 {
32 d_in_list = true;
33 d_qname = target;
34 d_qtype = QType::ANY;
35 d_results_cache.clear();
36
37 return (this->*d_list_fcnt)( target, domain_id );
38 }
39 catch( LDAPTimeout &lt )
40 {
41 g_log << Logger::Warning << d_myname << " Unable to get zone " << target << " from LDAP directory: " << lt.what() << endl;
42 throw DBException( "LDAP server timeout" );
43 }
44 catch( LDAPNoConnection &lnc )
45 {
46 g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
47 if ( reconnect() )
48 this->list( target, domain_id );
49 else
50 throw PDNSException( "Failed to reconnect to LDAP server" );
51 }
52 catch( LDAPException &le )
53 {
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
56 }
57 catch( std::exception &e )
58 {
59 g_log << Logger::Error << d_myname << " Caught STL exception for target " << target << ": " << e.what() << endl;
60 throw DBException( "STL exception" );
61 }
62
63 return false;
64 }
65
66
67
68 bool LdapBackend::list_simple( const DNSName& target, int domain_id )
69 {
70 string dn;
71 string filter;
72 string qesc;
73
74
75 dn = getArg( "basedn" );
76 qesc = toLower( d_pldap->escape( target.toStringRootDot() ) );
77
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 ) )
82 return false;
83
84 if( d_result.count( "dn" ) && !d_result["dn"].empty() )
85 {
86 if( !mustDo( "basedn-axfr-override" ) )
87 {
88 dn = d_result["dn"][0];
89 }
90 }
91
92 // If we have any records associated with this entry let's parse them here
93 DNSResult soa_result;
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)) );
98
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 );
102
103 return true;
104 }
105
106
107 bool LdapBackend::list_strict( const DNSName& target, int domain_id )
108 {
109 if( target.isPartOf(DNSName("in-addr.arpa")) || target.isPartOf(DNSName("ip6.arpa")) )
110 {
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
113 }
114
115 return list_simple( target, domain_id );
116 }
117
118
119
120 void LdapBackend::lookup( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
121 {
122 try
123 {
124 d_in_list = false;
125 d_qname = qname;
126 d_qtype = qtype;
127 d_results_cache.clear();
128
129 if( d_qlog ) { g_log.log( "Query: '" + qname.toStringRootDot() + "|" + qtype.getName() + "'", Logger::Error ); }
130 (this->*d_lookup_fcnt)( qtype, qname, dnspkt, zoneid );
131 }
132 catch( LDAPTimeout &lt )
133 {
134 g_log << Logger::Warning << d_myname << " Unable to search LDAP directory: " << lt.what() << endl;
135 throw DBException( "LDAP server timeout" );
136 }
137 catch( LDAPNoConnection &lnc )
138 {
139 g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
140 if ( reconnect() )
141 this->lookup( qtype, qname, dnspkt, zoneid );
142 else
143 throw PDNSException( "Failed to reconnect to LDAP server" );
144 }
145 catch( LDAPException &le )
146 {
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
149 }
150 catch( std::exception &e )
151 {
152 g_log << Logger::Error << d_myname << " Caught STL exception for qname " << qname << ": " << e.what() << endl;
153 throw DBException( "STL exception" );
154 }
155 }
156
157
158
159 void LdapBackend::lookup_simple( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
160 {
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 };
164
165
166 qesc = toLower( d_pldap->escape( qname.toStringRootDot() ) );
167 filter = "associatedDomain=" + qesc;
168
169 if( qtype.getCode() != QType::ANY )
170 {
171 attr = qtype.getName() + "Record";
172 filter = "&(" + filter + ")(" + attr + "=*)";
173 attronly[0] = attr.c_str();
174 attributes = attronly;
175 }
176
177 filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
178
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 );
181 }
182
183
184
185 void LdapBackend::lookup_strict( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
186 {
187 int len;
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 };
192
193
194 qesc = toLower( d_pldap->escape( qname.toStringRootDot() ) );
195 stringtok( parts, qesc, "." );
196 len = qesc.length();
197
198 if( parts.size() == 6 && len > 13 && qesc.substr( len - 13, 13 ) == ".in-addr.arpa" ) // IPv4 reverse lookups
199 {
200 filter = "aRecord=" + ptr2ip4( parts );
201 attronly[0] = "associatedDomain";
202 attributes = attronly;
203 }
204 else if( parts.size() == 34 && len > 9 && ( qesc.substr( len - 9, 9 ) == ".ip6.arpa" ) ) // IPv6 reverse lookups
205 {
206 filter = "aAAARecord=" + ptr2ip6( parts );
207 attronly[0] = "associatedDomain";
208 attributes = attronly;
209 }
210 else // IPv4 and IPv6 lookups
211 {
212 filter = "associatedDomain=" + qesc;
213 }
214
215 if( qtype.getCode() != QType::ANY )
216 {
217 attr = qtype.getName() + "Record";
218 filter = "&(" + filter + ")(" + attr + "=*)";
219 attronly[0] = attr.c_str();
220 attributes = attronly;
221 }
222
223 filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
224
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 );
227 }
228
229
230
231 void LdapBackend::lookup_tree( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
232 {
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;
237
238
239 qesc = toLower( d_pldap->escape( qname.toStringRootDot() ) );
240 filter = "associatedDomain=" + qesc;
241
242 if( qtype.getCode() != QType::ANY )
243 {
244 attr = qtype.getName() + "Record";
245 filter = "&(" + filter + ")(" + attr + "=*)";
246 attronly[0] = attr.c_str();
247 attributes = attronly;
248 }
249
250 filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
251
252 stringtok( parts, toLower( qname.toString() ), "." );
253 for(auto i = parts.crbegin(); i != parts.crend(); i++ )
254 {
255 dn = "dc=" + *i + "," + dn;
256 }
257
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 );
260 }
261
262
263 bool LdapBackend::get( DNSResourceRecord &rr )
264 {
265 if ( d_results_cache.empty() ) {
266 while ( d_results_cache.empty() ) {
267 bool exhausted = false;
268 bool valid_entry_found = false;
269
270 while ( !valid_entry_found && !exhausted ) {
271 try {
272 exhausted = !d_search->getNext( d_result, true );
273 }
274 catch( LDAPException &le )
275 {
276 g_log << Logger::Error << d_myname << " Failed to get next result: " << le.what() << endl;
277 throw PDNSException( "Get next result impossible" );
278 }
279
280 if ( !exhausted ) {
281 if ( !d_in_list ) {
282 // All entries are valid here
283 valid_entry_found = true;
284 }
285 else {
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;
290 }
291 }
292 }
293
294 if ( exhausted ) {
295 break;
296 }
297
298 DNSResult result_template;
299 result_template.ttl = d_default_ttl;
300 result_template.lastmod = 0;
301 this->extract_common_attributes( result_template );
302
303 std::vector<std::string> associatedDomains;
304
305 if ( d_result.count( "associatedDomain" ) ) {
306 if ( d_in_list ) {
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.
313
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 );
319 }
320 }
321 else {
322 // This was a lookup in strict mode, so we add the reverse lookup
323 // information manually.
324 d_result["pTRRecord"] = d_result["associatedDomain"];
325 }
326 }
327
328 if ( d_in_list ) {
329 for ( const auto& domain : associatedDomains )
330 this->extract_entry_results( DNSName( domain ), result_template, QType(uint16_t(QType::ANY)) );
331 }
332 else {
333 this->extract_entry_results( d_qname, result_template, QType(uint16_t(QType::ANY)) );
334 }
335 }
336
337 if ( d_results_cache.empty() )
338 return false;
339 }
340
341 DNSResult result = d_results_cache.back();
342 d_results_cache.pop_back();
343 rr.qtype = result.qtype;
344 rr.qname = result.qname;
345 rr.ttl = result.ttl;
346 rr.last_modified = 0;
347 rr.content = result.value;
348 rr.auth = result.auth;
349
350 g_log << Logger::Debug << d_myname << " Record = qname: " << rr.qname << ", qtype: " << (rr.qtype).getName() << ", ttl: " << rr.ttl << ", content: " << rr.content << endl;
351 return true;
352 }
353
354
355 bool LdapBackend::getDomainInfo( const DNSName& domain, DomainInfo& di, bool getSerial )
356 {
357 string filter;
358 SOAData sd;
359 PowerLDAP::sentry_t result;
360 const char* attronly[] = {
361 "sOARecord",
362 "PdnsDomainId",
363 "PdnsDomainNotifiedSerial",
364 "PdnsDomainLastCheck",
365 "PdnsDomainMaster",
366 "PdnsDomainType",
367 NULL
368 };
369
370 try
371 {
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 )) {
376 return false;
377 }
378 }
379 catch( LDAPTimeout &lt )
380 {
381 g_log << Logger::Warning << d_myname << " Unable to search LDAP directory: " << lt.what() << endl;
382 throw DBException( "LDAP server timeout" );
383 }
384 catch( LDAPNoConnection &lnc )
385 {
386 g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
387 if ( reconnect() )
388 this->getDomainInfo( domain, di );
389 else
390 throw PDNSException( "Failed to reconnect to LDAP server" );
391 }
392 catch( LDAPException &le )
393 {
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
396 }
397 catch( std::exception &e )
398 {
399 throw DBException( "STL exception" );
400 }
401
402 if( result.count( "sOARecord" ) && !result["sOARecord"].empty() )
403 {
404 sd.serial = 0;
405 fillSOAData( result["sOARecord"][0], sd );
406
407 if ( result.count( "PdnsDomainId" ) && !result["PdnsDomainId"].empty() )
408 di.id = std::stoi( result["PdnsDomainId"][0] );
409 else
410 di.id = 0;
411
412 di.serial = sd.serial;
413 di.zone = DNSName(domain);
414
415 if( result.count( "PdnsDomainLastCheck" ) && !result["PdnsDomainLastCheck"].empty() )
416 di.last_check = pdns_stou( result["PdnsDomainLastCheck"][0] );
417 else
418 di.last_check = 0;
419
420 if ( result.count( "PdnsDomainNotifiedSerial" ) && !result["PdnsDomainNotifiedSerial"].empty() )
421 di.notified_serial = pdns_stou( result["PdnsDomainNotifiedSerial"][0] );
422 else
423 di.notified_serial = 0;
424
425 if ( result.count( "PdnsDomainMaster" ) && !result["PdnsDomainMaster"].empty() ) {
426 for(const auto &m : result["PdnsDomainMaster"])
427 di.masters.emplace_back(m, 53);
428 }
429
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;
436 else
437 di.kind = DomainInfo::Native;
438 }
439 else {
440 di.kind = DomainInfo::Native;
441 }
442
443 di.backend = this;
444 return true;
445 }
446
447 return false;
448 }