]> git.ipfire.org Git - thirdparty/pdns.git/blob - modules/ldapbackend/ldapbackend.cc
make DomainInfo not carry IP addresses as strings. And some subsequent cleanups..
[thirdparty/pdns.git] / modules / ldapbackend / ldapbackend.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 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 #include "exceptions.hh"
27 #include "ldapauthenticator_p.hh"
28 #include "ldapbackend.hh"
29 #include <cstdlib>
30
31 unsigned int ldap_host_index = 0;
32
33 LdapBackend::LdapBackend( const string &suffix )
34 {
35 string hoststr;
36 unsigned int i, idx;
37 vector<string> hosts;
38
39
40 try
41 {
42 m_msgid = 0;
43 m_qname.clear();
44 m_pldap = NULL;
45 m_authenticator = NULL;
46 m_ttl = 0;
47 m_axfrqlen = 0;
48 m_last_modified = 0;
49 m_qlog = arg().mustDo( "query-logging" );
50 m_default_ttl = arg().asNum( "default-ttl" );
51 m_myname = "[LdapBackend]";
52
53 setArgPrefix( "ldap" + suffix );
54
55 m_getdn = false;
56 m_reconnect_attempts = getArgAsNum( "reconnect-attempts" );
57 m_list_fcnt = &LdapBackend::list_simple;
58 m_lookup_fcnt = &LdapBackend::lookup_simple;
59 m_prepare_fcnt = &LdapBackend::prepare_simple;
60
61 if( getArg( "method" ) == "tree" )
62 {
63 m_lookup_fcnt = &LdapBackend::lookup_tree;
64 }
65
66 if( getArg( "method" ) == "strict" || mustDo( "disable-ptrrecord" ) )
67 {
68 m_list_fcnt = &LdapBackend::list_strict;
69 m_lookup_fcnt = &LdapBackend::lookup_strict;
70 m_prepare_fcnt = &LdapBackend::prepare_strict;
71 }
72
73 stringtok( hosts, getArg( "host" ), ", " );
74 idx = ldap_host_index++ % hosts.size();
75 hoststr = hosts[idx];
76
77 for( i = 1; i < hosts.size(); i++ )
78 {
79 hoststr += " " + hosts[ ( idx + i ) % hosts.size() ];
80 }
81
82 g_log << Logger::Info << m_myname << " LDAP servers = " << hoststr << endl;
83
84 m_pldap = new PowerLDAP( hoststr.c_str(), LDAP_PORT, mustDo( "starttls" ), getArgAsNum( "timeout" ) );
85 m_pldap->setOption( LDAP_OPT_DEREF, LDAP_DEREF_ALWAYS );
86
87 string bindmethod = getArg( "bindmethod" );
88 if ( bindmethod == "gssapi" ) {
89 setenv( "KRB5CCNAME", getArg( "krb5-ccache" ).c_str(), 1 );
90 m_authenticator = new LdapGssapiAuthenticator( getArg( "krb5-keytab" ), getArg( "krb5-ccache" ), getArgAsNum( "timeout" ) );
91 }
92 else {
93 m_authenticator = new LdapSimpleAuthenticator( getArg( "binddn" ), getArg( "secret" ), getArgAsNum( "timeout" ) );
94 }
95 m_pldap->bind( m_authenticator );
96
97 g_log << Logger::Notice << m_myname << " Ldap connection succeeded" << endl;
98 return;
99 }
100 catch( LDAPTimeout &lt )
101 {
102 g_log << Logger::Error << m_myname << " Ldap connection to server failed because of timeout" << endl;
103 }
104 catch( LDAPException &le )
105 {
106 g_log << Logger::Error << m_myname << " Ldap connection to server failed: " << le.what() << endl;
107 }
108 catch( std::exception &e )
109 {
110 g_log << Logger::Error << m_myname << " Caught STL exception: " << e.what() << endl;
111 }
112
113 if( m_pldap != NULL ) { delete( m_pldap ); }
114 throw( PDNSException( "Unable to connect to ldap server" ) );
115 }
116
117
118
119 LdapBackend::~LdapBackend()
120 {
121 delete( m_pldap );
122 delete( m_authenticator );
123 g_log << Logger::Notice << m_myname << " Ldap connection closed" << endl;
124 }
125
126
127
128 bool LdapBackend::reconnect()
129 {
130 int attempts = m_reconnect_attempts;
131 bool connected = false;
132 while ( !connected && attempts > 0 ) {
133 g_log << Logger::Debug << m_myname << " Reconnection attempts left: " << attempts << endl;
134 connected = m_pldap->connect();
135 if ( !connected )
136 Utility::usleep( 250 );
137 --attempts;
138 }
139
140 if ( connected )
141 m_pldap->bind( m_authenticator );
142
143 return connected;
144 }
145
146
147
148 bool LdapBackend::list( const DNSName& target, int domain_id, bool include_disabled )
149 {
150 try
151 {
152 m_qname = target;
153 m_qtype = QType::ANY;
154 m_axfrqlen = target.toStringRootDot().length();
155 m_adomain = m_adomains.end(); // skip loops in get() first time
156
157 return (this->*m_list_fcnt)( target, domain_id );
158 }
159 catch( LDAPTimeout &lt )
160 {
161 g_log << Logger::Warning << m_myname << " Unable to get zone " << target << " from LDAP directory: " << lt.what() << endl;
162 throw( DBException( "LDAP server timeout" ) );
163 }
164 catch( LDAPNoConnection &lnc )
165 {
166 g_log << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
167 if ( reconnect() )
168 this->list( target, domain_id );
169 else
170 throw PDNSException( "Failed to reconnect to LDAP server" );
171 }
172 catch( LDAPException &le )
173 {
174 g_log << Logger::Error << m_myname << " Unable to get zone " << target << " from LDAP directory: " << le.what() << endl;
175 throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
176 }
177 catch( std::exception &e )
178 {
179 g_log << Logger::Error << m_myname << " Caught STL exception for target " << target << ": " << e.what() << endl;
180 throw( DBException( "STL exception" ) );
181 }
182
183 return false;
184 }
185
186
187
188 inline bool LdapBackend::list_simple( const DNSName& target, int domain_id )
189 {
190 string dn;
191 string filter;
192 string qesc;
193
194
195 dn = getArg( "basedn" );
196 qesc = toLower( m_pldap->escape( target.toStringRootDot() ) );
197
198 // search for SOARecord of target
199 filter = strbind( ":target:", "&(associatedDomain=" + qesc + ")(sOARecord=*)", getArg( "filter-axfr" ) );
200 m_msgid = m_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
201 m_pldap->getSearchEntry( m_msgid, m_result, true );
202
203 if( m_result.count( "dn" ) && !m_result["dn"].empty() )
204 {
205 if( !mustDo( "basedn-axfr-override" ) )
206 {
207 dn = m_result["dn"][0];
208 }
209 m_result.erase( "dn" );
210 }
211
212 prepare();
213 filter = strbind( ":target:", "associatedDomain=*." + qesc, getArg( "filter-axfr" ) );
214 DLOG( g_log << Logger::Debug << m_myname << " Search = basedn: " << dn << ", filter: " << filter << endl );
215 m_msgid = m_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
216
217 return true;
218 }
219
220
221
222 inline bool LdapBackend::list_strict( const DNSName& target, int domain_id )
223 {
224 if( target.isPartOf(DNSName("in-addr.arpa")) || target.isPartOf(DNSName("ip6.arpa")) )
225 {
226 g_log << Logger::Warning << m_myname << " Request for reverse zone AXFR, but this is not supported in strict mode" << endl;
227 return false; // AXFR isn't supported in strict mode. Use simple mode and additional PTR records
228 }
229
230 return list_simple( target, domain_id );
231 }
232
233
234
235 void LdapBackend::lookup( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
236 {
237 try
238 {
239 m_axfrqlen = 0;
240 m_qname = qname;
241 m_adomain = m_adomains.end(); // skip loops in get() first time
242 m_qtype = qtype;
243
244 if( m_qlog ) { g_log.log( "Query: '" + qname.toStringRootDot() + "|" + qtype.getName() + "'", Logger::Error ); }
245 (this->*m_lookup_fcnt)( qtype, qname, dnspkt, zoneid );
246 }
247 catch( LDAPTimeout &lt )
248 {
249 g_log << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl;
250 throw( DBException( "LDAP server timeout" ) );
251 }
252 catch( LDAPNoConnection &lnc )
253 {
254 g_log << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
255 if ( reconnect() )
256 this->lookup( qtype, qname, dnspkt, zoneid );
257 else
258 throw PDNSException( "Failed to reconnect to LDAP server" );
259 }
260 catch( LDAPException &le )
261 {
262 g_log << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
263 throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
264 }
265 catch( std::exception &e )
266 {
267 g_log << Logger::Error << m_myname << " Caught STL exception for qname " << qname << ": " << e.what() << endl;
268 throw( DBException( "STL exception" ) );
269 }
270 }
271
272
273
274 void LdapBackend::lookup_simple( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
275 {
276 string filter, attr, qesc;
277 const char** attributes = ldap_attrany + 1; // skip associatedDomain
278 const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
279
280
281 qesc = toLower( m_pldap->escape( qname.toStringRootDot() ) );
282 filter = "associatedDomain=" + qesc;
283
284 if( qtype.getCode() != QType::ANY )
285 {
286 attr = qtype.getName() + "Record";
287 filter = "&(" + filter + ")(" + attr + "=*)";
288 attronly[0] = attr.c_str();
289 attributes = attronly;
290 }
291
292 filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
293
294 DLOG( g_log << Logger::Debug << m_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
295 m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attributes );
296 }
297
298
299
300 void LdapBackend::lookup_strict( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
301 {
302 int len;
303 vector<string> parts;
304 string filter, attr, qesc;
305 const char** attributes = ldap_attrany + 1; // skip associatedDomain
306 const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
307
308
309 qesc = toLower( m_pldap->escape( qname.toStringRootDot() ) );
310 stringtok( parts, qesc, "." );
311 len = qesc.length();
312
313 if( parts.size() == 6 && len > 13 && qesc.substr( len - 13, 13 ) == ".in-addr.arpa" ) // IPv4 reverse lookups
314 {
315 filter = "aRecord=" + ptr2ip4( parts );
316 attronly[0] = "associatedDomain";
317 attributes = attronly;
318 }
319 else if( parts.size() == 34 && len > 9 && ( qesc.substr( len - 9, 9 ) == ".ip6.arpa" ) ) // IPv6 reverse lookups
320 {
321 filter = "aAAARecord=" + ptr2ip6( parts );
322 attronly[0] = "associatedDomain";
323 attributes = attronly;
324 }
325 else // IPv4 and IPv6 lookups
326 {
327 filter = "associatedDomain=" + qesc;
328 if( qtype.getCode() != QType::ANY )
329 {
330 attr = qtype.getName() + "Record";
331 filter = "&(" + filter + ")(" + attr + "=*)";
332 attronly[0] = attr.c_str();
333 attributes = attronly;
334 }
335 }
336
337 filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
338
339 DLOG( g_log << Logger::Debug << m_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
340 m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attributes );
341 }
342
343
344
345 void LdapBackend::lookup_tree( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
346 {
347 string filter, attr, qesc, dn;
348 const char** attributes = ldap_attrany + 1; // skip associatedDomain
349 const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
350 vector<string> parts;
351
352
353 qesc = toLower( m_pldap->escape( qname.toStringRootDot() ) );
354 filter = "associatedDomain=" + qesc;
355
356 if( qtype.getCode() != QType::ANY )
357 {
358 attr = qtype.getName() + "Record";
359 filter = "&(" + filter + ")(" + attr + "=*)";
360 attronly[0] = attr.c_str();
361 attributes = attronly;
362 }
363
364 filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
365
366 stringtok( parts, toLower( qname.toString() ), "." );
367 for(auto i = parts.crbegin(); i != parts.crend(); i++ )
368 {
369 dn = "dc=" + *i + "," + dn;
370 }
371
372 DLOG( g_log << Logger::Debug << m_myname << " Search = basedn: " << dn + getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
373 m_msgid = m_pldap->search( dn + getArg( "basedn" ), LDAP_SCOPE_BASE, filter, attributes );
374 }
375
376
377 inline bool LdapBackend::prepare()
378 {
379 m_adomains.clear();
380 m_ttl = m_default_ttl;
381 m_last_modified = 0;
382
383 if( m_result.count( "dNSTTL" ) && !m_result["dNSTTL"].empty() )
384 {
385 char* endptr;
386
387 m_ttl = (uint32_t) strtol( m_result["dNSTTL"][0].c_str(), &endptr, 10 );
388 if( *endptr != '\0' )
389 {
390 g_log << Logger::Warning << m_myname << " Invalid time to live for " << m_qname << ": " << m_result["dNSTTL"][0] << endl;
391 m_ttl = m_default_ttl;
392 }
393 m_result.erase( "dNSTTL" );
394 }
395
396 if( m_result.count( "modifyTimestamp" ) && !m_result["modifyTimestamp"].empty() )
397 {
398 if( ( m_last_modified = str2tstamp( m_result["modifyTimestamp"][0] ) ) == 0 )
399 {
400 g_log << Logger::Warning << m_myname << " Invalid modifyTimestamp for " << m_qname << ": " << m_result["modifyTimestamp"][0] << endl;
401 }
402 m_result.erase( "modifyTimestamp" );
403 }
404
405 if( !(this->*m_prepare_fcnt)() )
406 {
407 return false;
408 }
409
410 m_adomain = m_adomains.begin();
411 m_attribute = m_result.begin();
412 m_value = m_attribute->second.begin();
413
414 return true;
415 }
416
417
418
419 inline bool LdapBackend::prepare_simple()
420 {
421 if( !m_axfrqlen ) // request was a normal lookup()
422 {
423 m_adomains.push_back( m_qname );
424 }
425 else // request was a list() for AXFR
426 {
427 if( m_result.count( "associatedDomain" ) )
428 {
429 for(auto i = m_result["associatedDomain"].begin(); i != m_result["associatedDomain"].end(); i++ ) {
430 if( i->size() >= m_axfrqlen && i->substr( i->size() - m_axfrqlen, m_axfrqlen ) == m_qname.toStringRootDot() /* ugh */ ) {
431 m_adomains.push_back( DNSName(*i) );
432 }
433 }
434 m_result.erase( "associatedDomain" );
435 }
436 }
437
438 return true;
439 }
440
441
442
443 inline bool LdapBackend::prepare_strict()
444 {
445 if( !m_axfrqlen ) // request was a normal lookup()
446 {
447 m_adomains.push_back( m_qname );
448 if( m_result.count( "associatedDomain" ) )
449 {
450 m_result["PTRRecord"] = m_result["associatedDomain"];
451 m_result.erase( "associatedDomain" );
452 }
453 }
454 else // request was a list() for AXFR
455 {
456 if( m_result.count( "associatedDomain" ) )
457 {
458 for(auto i = m_result["associatedDomain"].begin(); i != m_result["associatedDomain"].end(); i++ ) {
459 if( i->size() >= m_axfrqlen && i->substr( i->size() - m_axfrqlen, m_axfrqlen ) == m_qname.toStringRootDot() /* ugh */ ) {
460 m_adomains.push_back( DNSName(*i) );
461 }
462 }
463 m_result.erase( "associatedDomain" );
464 }
465 }
466
467 return true;
468 }
469
470
471
472 bool LdapBackend::get( DNSResourceRecord &rr )
473 {
474 QType qt;
475 vector<string> parts;
476 string attrname, qstr;
477
478
479 try
480 {
481 do
482 {
483 while( m_adomain != m_adomains.end() )
484 {
485 while( m_attribute != m_result.end() )
486 {
487 attrname = m_attribute->first;
488 qstr = attrname.substr( 0, attrname.length() - 6 ); // extract qtype string from ldap attribute name
489 qt = const_cast<char*>(toUpper( qstr ).c_str());
490
491 while( m_value != m_attribute->second.end() )
492 {
493 if(m_qtype != qt && m_qtype != QType::ANY) {
494 m_value++;
495 continue;
496 }
497
498
499 rr.qtype = qt;
500 rr.qname = *m_adomain;
501 rr.ttl = m_ttl;
502 rr.last_modified = m_last_modified;
503 rr.content = *m_value;
504 m_value++;
505
506 DLOG( g_log << Logger::Debug << m_myname << " Record = qname: " << rr.qname << ", qtype: " << (rr.qtype).getName() << ", ttl: " << rr.ttl << ", content: " << rr.content << endl );
507 return true;
508 }
509
510 m_attribute++;
511 m_value = m_attribute->second.begin();
512 }
513 m_adomain++;
514 m_attribute = m_result.begin();
515 m_value = m_attribute->second.begin();
516 }
517 }
518 while( m_pldap->getSearchEntry( m_msgid, m_result, m_getdn ) && prepare() );
519
520 }
521 catch( LDAPTimeout &lt )
522 {
523 g_log << Logger::Warning << m_myname << " Search failed: " << lt.what() << endl;
524 throw( DBException( "LDAP server timeout" ) );
525 }
526 catch( LDAPException &le )
527 {
528 g_log << Logger::Error << m_myname << " Search failed: " << le.what() << endl;
529 throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
530 }
531 catch( std::exception &e )
532 {
533 g_log << Logger::Error << m_myname << " Caught STL exception for " << m_qname << ": " << e.what() << endl;
534 throw( DBException( "STL exception" ) );
535 }
536
537 return false;
538 }
539
540
541
542 void LdapBackend::getUpdatedMasters( vector<DomainInfo>* domains )
543 {
544 string filter;
545 int msgid=0;
546 PowerLDAP::sentry_t result;
547 const char* attronly[] = {
548 "associatedDomain",
549 NULL
550 };
551
552 try
553 {
554 // First get all domains on which we are master.
555 filter = strbind( ":target:", "&(SOARecord=*)(PdnsDomainId=*)", getArg( "filter-axfr" ) );
556 msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
557 }
558 catch( LDAPTimeout &lt )
559 {
560 g_log << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl;
561 throw( DBException( "LDAP server timeout" ) );
562 }
563 catch( LDAPNoConnection &lnc )
564 {
565 g_log << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
566 if ( reconnect() )
567 this->getUpdatedMasters( domains );
568 else
569 throw PDNSException( "Failed to reconnect to LDAP server" );
570 }
571 catch( LDAPException &le )
572 {
573 g_log << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
574 throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
575 }
576 catch( std::exception &e )
577 {
578 throw( DBException( "STL exception" ) );
579 }
580
581 while( m_pldap->getSearchEntry( msgid, result ) ) {
582 if( !result.count( "associatedDomain" ) || result["associatedDomain"].empty() )
583 continue;
584
585 DomainInfo di;
586 if ( !getDomainInfo( DNSName( result["associatedDomain"][0] ), di ) )
587 continue;
588
589 if( di.notified_serial < di.serial )
590 domains->push_back( di );
591 }
592 }
593
594
595
596 void LdapBackend::setNotified( uint32_t id, uint32_t serial )
597 {
598 string filter;
599 int msgid;
600 PowerLDAP::sresult_t results;
601 PowerLDAP::sentry_t entry;
602 const char* attronly[] = { "associatedDomain", NULL };
603
604 try
605 {
606 // Try to find the notified domain
607 filter = strbind( ":target:", "PdnsDomainId=" + std::to_string( id ), getArg( "filter-axfr" ) );
608 msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
609 m_pldap->getSearchResults( msgid, results, true );
610 }
611 catch( LDAPTimeout &lt )
612 {
613 g_log << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl;
614 throw( DBException( "LDAP server timeout" ) );
615 }
616 catch( LDAPNoConnection &lnc )
617 {
618 g_log << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
619 if ( reconnect() )
620 this->setNotified( id, serial );
621 else
622 throw PDNSException( "Failed to reconnect to LDAP server" );
623 }
624 catch( LDAPException &le )
625 {
626 g_log << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
627 throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
628 }
629 catch( std::exception &e )
630 {
631 throw( DBException( "STL exception" ) );
632 }
633
634 if ( results.empty() )
635 throw PDNSException( "No results found when trying to update domain notified_serial for ID " + std::to_string( id ) );
636
637 entry = results.front();
638 string dn = entry["dn"][0];
639 string serialStr = std::to_string( serial );
640 LDAPMod *mods[2];
641 LDAPMod mod;
642 char *vals[2];
643
644 mod.mod_op = LDAP_MOD_REPLACE;
645 mod.mod_type = (char*)"PdnsDomainNotifiedSerial";
646 vals[0] = const_cast<char*>( serialStr.c_str() );
647 vals[1] = NULL;
648 mod.mod_values = vals;
649
650 mods[0] = &mod;
651 mods[1] = NULL;
652
653 try
654 {
655 m_pldap->modify( dn, mods );
656 }
657 catch( LDAPNoConnection &lnc )
658 {
659 g_log << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
660 if ( reconnect() )
661 this->setNotified( id, serial );
662 else
663 throw PDNSException( "Failed to reconnect to LDAP server" );
664 }
665 catch( LDAPException &le )
666 {
667 g_log << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
668 throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
669 }
670 catch( std::exception &e )
671 {
672 throw( DBException( "STL exception" ) );
673 }
674 }
675
676
677
678 bool LdapBackend::getDomainInfo( const DNSName& domain, DomainInfo& di, bool getSerial )
679 {
680 string filter;
681 SOAData sd;
682 PowerLDAP::sentry_t result;
683 const char* attronly[] = {
684 "sOARecord",
685 "PdnsDomainId",
686 "PdnsDomainNotifiedSerial",
687 "PdnsDomainLastCheck",
688 "PdnsDomainMaster",
689 "PdnsDomainType",
690 NULL
691 };
692
693 try
694 {
695 // search for SOARecord of domain
696 filter = "(&(associatedDomain=" + toLower( m_pldap->escape( domain.toStringRootDot() ) ) + ")(SOARecord=*))";
697 m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
698 m_pldap->getSearchEntry( m_msgid, result );
699 }
700 catch( LDAPTimeout &lt )
701 {
702 g_log << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl;
703 throw( DBException( "LDAP server timeout" ) );
704 }
705 catch( LDAPNoConnection &lnc )
706 {
707 g_log << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
708 if ( reconnect() )
709 this->getDomainInfo( domain, di );
710 else
711 throw PDNSException( "Failed to reconnect to LDAP server" );
712 }
713 catch( LDAPException &le )
714 {
715 g_log << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
716 throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
717 }
718 catch( std::exception &e )
719 {
720 throw( DBException( "STL exception" ) );
721 }
722
723 if( result.count( "sOARecord" ) && !result["sOARecord"].empty() )
724 {
725 sd.serial = 0;
726 fillSOAData( result["sOARecord"][0], sd );
727
728 if ( result.count( "PdnsDomainId" ) && !result["PdnsDomainId"].empty() )
729 di.id = std::stoi( result["PdnsDomainId"][0] );
730 else
731 di.id = 0;
732
733 di.serial = sd.serial;
734 di.zone = DNSName(domain);
735
736 if( result.count( "PdnsDomainLastCheck" ) && !result["PdnsDomainLastCheck"].empty() )
737 di.last_check = pdns_stou( result["PdnsDomainLastCheck"][0] );
738 else
739 di.last_check = 0;
740
741 if ( result.count( "PdnsDomainNotifiedSerial" ) && !result["PdnsDomainNotifiedSerial"].empty() )
742 di.notified_serial = pdns_stou( result["PdnsDomainNotifiedSerial"][0] );
743 else
744 di.notified_serial = 0;
745
746 if ( result.count( "PdnsDomainMaster" ) && !result["PdnsDomainMaster"].empty() ) {
747 for(const auto& m : result["PdnsDomainMaster"])
748 di.masters.emplace_back(m, 53);
749 }
750
751 if ( result.count( "PdnsDomainType" ) && !result["PdnsDomainType"].empty() ) {
752 string kind = result["PdnsDomainType"][0];
753 if ( kind == "master" )
754 di.kind = DomainInfo::Master;
755 else if ( kind == "slave" )
756 di.kind = DomainInfo::Slave;
757 else
758 di.kind = DomainInfo::Native;
759 }
760 else {
761 di.kind = DomainInfo::Native;
762 }
763
764 di.backend = this;
765 return true;
766 }
767
768 return false;
769 }
770
771
772
773
774
775 class LdapFactory : public BackendFactory
776 {
777 public:
778
779 LdapFactory() : BackendFactory( "ldap" ) {}
780
781 void declareArguments( const string &suffix="" )
782 {
783 declare( suffix, "host", "One or more LDAP server with ports or LDAP URIs (separated by spaces)","ldap://127.0.0.1:389/" );
784 declare( suffix, "starttls", "Use TLS to encrypt connection (unused for LDAP URIs)", "no" );
785 declare( suffix, "basedn", "Search root in ldap tree (must be set)","" );
786 declare( suffix, "basedn-axfr-override", "Override base dn for AXFR subtree search", "no" );
787 declare( suffix, "bindmethod", "Bind method to use (simple or gssapi)", "simple" );
788 declare( suffix, "binddn", "User dn for non anonymous binds","" );
789 declare( suffix, "secret", "User password for non anonymous binds", "" );
790 declare( suffix, "krb5-keytab", "The keytab to use for GSSAPI authentication", "" );
791 declare( suffix, "krb5-ccache", "The credentials cache used for GSSAPI authentication", "" );
792 declare( suffix, "timeout", "Seconds before connecting to server fails", "5" );
793 declare( suffix, "method", "How to search entries (simple, strict or tree)", "simple" );
794 declare( suffix, "filter-axfr", "LDAP filter for limiting AXFR results", "(:target:)" );
795 declare( suffix, "filter-lookup", "LDAP filter for limiting IP or name lookups", "(:target:)" );
796 declare( suffix, "disable-ptrrecord", "Deprecated, use ldap-method=strict instead", "no" );
797 declare( suffix, "reconnect-attempts", "Number of attempts to re-establish a lost LDAP connection", "5" );
798 }
799
800
801 DNSBackend* make( const string &suffix="" )
802 {
803 return new LdapBackend( suffix );
804 }
805 };
806
807
808
809
810
811 class LdapLoader
812 {
813 LdapFactory factory;
814
815 public:
816
817 LdapLoader()
818 {
819 BackendMakers().report( &factory );
820 g_log << Logger::Info << "[ldapbackend] This is the ldap backend version " VERSION
821 #ifndef REPRODUCIBLE
822 << " (" __DATE__ " " __TIME__ ")"
823 #endif
824 << " reporting" << endl;
825 }
826 };
827
828
829 static LdapLoader ldaploader;