root/trunk/pdns/modules/ldapbackend/ldapbackend.cc @ 973

Revision 973, 14.9 KB (checked in by ahu, 6 years ago)

fillSOAData is no longer part of DNSPacket, update modules

  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1#include "ldapbackend.hh"
2
3
4
5unsigned int ldap_host_index = 0;
6
7
8
9LdapBackend::LdapBackend( const string &suffix )
10{
11        string hoststr;
12        unsigned int i, idx;
13        vector<string> hosts;
14
15
16        try
17        {
18                m_msgid = 0;
19                m_qname = "";
20                m_pldap = NULL;
21                m_qlog = arg().mustDo( "query-logging" );
22                m_default_ttl = arg().asNum( "default-ttl" );
23                m_myname = "[LdapBackend]";
24
25                setArgPrefix( "ldap" + suffix );
26
27                m_getdn = false;
28                m_list_fcnt = &LdapBackend::list_simple;
29                m_lookup_fcnt = &LdapBackend::lookup_simple;
30                m_prepare_fcnt = &LdapBackend::prepare_simple;
31
32                if( getArg( "method" ) == "tree" )
33                {
34                        m_lookup_fcnt = &LdapBackend::lookup_tree;
35                }
36
37                if( getArg( "method" ) == "strict" || mustDo( "disable-ptrrecord" ) )
38                {
39                        m_list_fcnt = &LdapBackend::list_strict;
40                        m_lookup_fcnt = &LdapBackend::lookup_strict;
41                        m_prepare_fcnt = &LdapBackend::prepare_strict;
42                }
43
44                stringtok( hosts, getArg( "host" ), ", " );
45                idx = ldap_host_index++ % hosts.size();
46                hoststr = hosts[idx];
47
48                for( i = 1; i < hosts.size(); i++ )
49                {
50                        hoststr += " " + hosts[ ( idx + i ) % hosts.size() ];
51                }
52
53                L << Logger::Info << m_myname << " LDAP servers = " << hoststr << endl;
54
55                m_pldap = new PowerLDAP( hoststr.c_str(), LDAP_PORT, mustDo( "starttls" ) );
56                m_pldap->setOption( LDAP_OPT_DEREF, LDAP_DEREF_ALWAYS );
57                m_pldap->simpleBind( getArg( "binddn" ), getArg( "secret" ) );
58        }
59        catch( LDAPException &le )
60        {
61                if( m_pldap != NULL ) { delete( m_pldap ); }
62                L << Logger::Error << m_myname << " Ldap connection to server failed: " << le.what() << endl;
63                throw( AhuException( "Unable to connect to ldap server" ) );
64        }
65        catch( exception &e )
66        {
67                if( m_pldap != NULL ) { delete( m_pldap ); }
68                L << Logger::Error << m_myname << " Caught STL exception: " << e.what() << endl;
69                throw( AhuException( "Unable to connect to ldap server" ) );
70        }
71
72        L << Logger::Notice << m_myname << " Ldap connection succeeded" << endl;
73}
74
75
76
77LdapBackend::~LdapBackend()
78{
79        if( m_pldap != NULL ) { delete( m_pldap ); }
80        L << Logger::Notice << m_myname << " Ldap connection closed" << endl;
81}
82
83
84
85bool LdapBackend::list( const string& target, int domain_id )
86{
87        try
88        {
89                m_qname = target;
90                m_axfrqlen = target.length();
91                m_adomain = m_adomains.end();   // skip loops in get() first time
92
93                return (this->*m_list_fcnt)( target, domain_id );
94        }
95        catch( LDAPTimeout &lt )
96        {
97                L << Logger::Warning << m_myname << " Unable to get zone " + target + " from LDAP directory: " << lt.what() << endl;
98                throw( DBException( "LDAP server timeout" ) );
99        }
100        catch( LDAPException &le )
101        {
102                L << Logger::Error << m_myname << " Unable to get zone " + target + " from LDAP directory: " << le.what() << endl;
103                throw( AhuException( "LDAP server unreachable" ) );   // try to reconnect to another server
104        }
105        catch( exception &e )
106        {
107                L << Logger::Error << m_myname << " Caught STL exception for target " << target << ": " << e.what() << endl;
108                throw( DBException( "STL exception" ) );
109        }
110
111        return false;
112}
113
114
115
116inline bool LdapBackend::list_simple( const string& target, int domain_id )
117{
118        string dn;
119        string filter;
120    string qesc;
121
122
123        dn = getArg( "basedn" );
124        qesc = toLower( m_pldap->escape( target ) );
125
126        // search for SOARecord of target
127        filter = strbind( ":target:", "(associatedDomain=" + qesc + ")", getArg( "filter-axfr" ) );
128        m_msgid = m_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
129        m_pldap->getSearchEntry( m_msgid, m_result, true );
130
131        if( m_result.count( "dn" ) && !m_result["dn"].empty() )
132        {
133                dn = m_result["dn"][0];
134                m_result.erase( "dn" );
135        }
136
137        prepare();
138        filter = strbind( ":target:", "(associatedDomain=*." + qesc + ")", getArg( "filter-axfr" ) );
139        DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << dn << ", filter: " << filter << endl );
140        m_msgid = m_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
141
142        return true;
143}
144
145
146
147inline bool LdapBackend::list_strict( const string& target, int domain_id )
148{
149        if( target.size() > 13 && target.substr( target.size() - 13, 13 ) == ".in-addr.arpa" ||
150                target.size() > 9 && target.substr( target.size() - 9, 9 ) == ".ip6.arpa" )
151        {
152                L << Logger::Warning << m_myname << " Request for reverse zone AXFR, but this is not supported in strict mode" << endl;
153                return false;   // AXFR isn't supported in strict mode. Use simple mode and additional PTR records
154        }
155
156        return list_simple( target, domain_id );
157}
158
159
160
161void LdapBackend::lookup( const QType &qtype, const string &qname, DNSPacket *dnspkt, int zoneid )
162{
163        try
164        {
165                m_axfrqlen = 0;
166                m_qname = qname;
167                m_adomain = m_adomains.end();   // skip loops in get() first time
168
169                if( m_qlog ) { L.log( "Query: '" + qname + "|" + qtype.getName() + "'", Logger::Error ); }
170                (this->*m_lookup_fcnt)( qtype, qname, dnspkt, zoneid );
171        }
172        catch( LDAPTimeout &lt )
173        {
174                L << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl;
175                throw( DBException( "LDAP server timeout" ) );
176        }
177        catch( LDAPException &le )
178        {
179                L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
180                throw( AhuException( "LDAP server unreachable" ) );   // try to reconnect to another server
181        }
182        catch( exception &e )
183        {
184                L << Logger::Error << m_myname << " Caught STL exception for qname " << qname << ": " << e.what() << endl;
185                throw( DBException( "STL exception" ) );
186        }
187}
188
189
190
191void LdapBackend::lookup_simple( const QType &qtype, const string &qname, DNSPacket *dnspkt, int zoneid )
192{
193        string filter, attr, qesc;
194        char** attributes = ldap_attrany + 1;   // skip associatedDomain
195        char* attronly[] = { NULL, "dNSTTL", NULL };
196
197
198        qesc = toLower( m_pldap->escape( qname ) );
199        filter = "(associatedDomain=" + qesc + ")";
200
201        if( qtype.getCode() != QType::ANY )
202        {
203                attr = qtype.getName() + "Record";
204                filter = "(&" + filter + "(" + attr + "=*))";
205                attronly[0] = (char*) attr.c_str();
206                attributes = attronly;
207        }
208
209        filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
210
211        DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
212        m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, (const char**) attributes );
213}
214
215
216
217void LdapBackend::lookup_strict( const QType &qtype, const string &qname, DNSPacket *dnspkt, int zoneid )
218{
219        int len;
220        vector<string> parts;
221        string filter, attr, qesc;
222        char** attributes = ldap_attrany + 1;   // skip associatedDomain
223        char* attronly[] = { NULL, "dNSTTL", NULL };
224
225
226        qesc = toLower( m_pldap->escape( qname ) );
227        stringtok( parts, qesc, "." );
228        len = qesc.length();
229
230         if( parts.size() == 6 && len > 13 && qesc.substr( len - 13, 13 ) == ".in-addr.arpa" )   // IPv4 reverse lookups
231        {
232                filter = "(aRecord=" + ptr2ip4( parts ) + ")";
233                attronly[0] = "associatedDomain";
234                attributes = attronly;
235        }
236        else if( parts.size() == 34 && len > 9 && ( qesc.substr( len - 9, 9 ) == ".ip6.arpa" ) )   // IPv6 reverse lookups
237        {
238                filter = "(aAAARecord=" + ptr2ip6( parts ) + ")";
239                attronly[0] = "associatedDomain";
240                attributes = attronly;
241        }
242        else   // IPv4 and IPv6 lookups
243        {
244                filter = "(associatedDomain=" + qesc + ")";
245                if( qtype.getCode() != QType::ANY )
246                {
247                        attr = qtype.getName() + "Record";
248                        filter = "(&" + filter + "(" + attr + "=*))";
249                        attronly[0] = (char*) attr.c_str();
250                        attributes = attronly;
251                }
252        }
253
254        filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
255
256        DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
257        m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, (const char**) attributes );
258}
259
260
261
262void LdapBackend::lookup_tree( const QType &qtype, const string &qname, DNSPacket *dnspkt, int zoneid )
263{
264        string filter, attr, qesc, dn;
265        char** attributes = ldap_attrany + 1;   // skip associatedDomain
266        char* attronly[] = { NULL, "dNSTTL", NULL };
267        vector<string>::reverse_iterator i;
268        vector<string> parts;
269
270
271        qesc = toLower( m_pldap->escape( qname ) );
272        filter = "(associatedDomain=" + qesc + ")";
273
274        if( qtype.getCode() != QType::ANY )
275        {
276                attr = qtype.getName() + "Record";
277                filter = "(&" + filter + "(" + attr + "=*))";
278                attronly[0] = (char*) attr.c_str();
279                attributes = attronly;
280        }
281
282        filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
283
284        stringtok( parts, qesc, "." );
285        for( i = parts.rbegin(); i != parts.rend(); i++ )
286        {
287                dn = "dc=" + *i + "," + dn;
288        }
289
290        DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << dn + getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
291        m_msgid = m_pldap->search( dn + getArg( "basedn" ), LDAP_SCOPE_BASE, filter, (const char**) attributes );
292}
293
294
295
296inline bool LdapBackend::prepare()
297{
298        m_adomains.clear();
299        m_ttl = m_default_ttl;
300
301        if( m_result.count( "dNSTTL" ) && !m_result["dNSTTL"].empty() )
302        {
303                char* endptr;
304
305                m_ttl = (uint32_t) strtol( m_result["dNSTTL"][0].c_str(), &endptr, 10 );
306                if( *endptr != '\0' )
307                {
308                        L << Logger::Warning << m_myname << " Invalid time to life for " << m_qname << ": " << m_result["dNSTTL"][0] << endl;
309                        m_ttl = m_default_ttl;
310                }
311                m_result.erase( "dNSTTL" );
312        }
313
314        if( !(this->*m_prepare_fcnt)() )
315        {
316                return false;
317        }
318
319        m_adomain = m_adomains.begin();
320        m_attribute = m_result.begin();
321        m_value = m_attribute->second.begin();
322
323        return true;
324}
325
326
327
328inline bool LdapBackend::prepare_simple()
329{
330        if( !m_axfrqlen )   // request was a normal lookup()
331        {
332                m_adomains.push_back( m_qname );
333        }
334        else   // request was a list() for AXFR
335        {
336                if( m_result.count( "associatedDomain" ) )
337                {
338                        vector<string>::iterator i;
339                        for( i = m_result["associatedDomain"].begin(); i != m_result["associatedDomain"].end(); i++ ) {
340                                if( i->size() >= m_axfrqlen && i->substr( i->size() - m_axfrqlen, m_axfrqlen ) == m_qname ) {
341                                        m_adomains.push_back( *i );
342                                }
343                        }
344                        m_result.erase( "associatedDomain" );
345                }
346        }
347
348        return true;
349}
350
351
352
353inline bool LdapBackend::prepare_strict()
354{
355        if( !m_axfrqlen )   // request was a normal lookup()
356        {
357                m_adomains.push_back( m_qname );
358                if( m_result.count( "associatedDomain" ) )
359                {
360                        m_result["PTRRecord"] = m_result["associatedDomain"];
361                        m_result.erase( "associatedDomain" );
362                }
363        }
364        else   // request was a list() for AXFR
365        {
366                if( m_result.count( "associatedDomain" ) )
367                {
368                        vector<string>::iterator i;
369                        for( i = m_result["associatedDomain"].begin(); i != m_result["associatedDomain"].end(); i++ ) {
370                                if( i->size() >= m_axfrqlen && i->substr( i->size() - m_axfrqlen, m_axfrqlen ) == m_qname ) {
371                                        m_adomains.push_back( *i );
372                                }
373                        }
374                        m_result.erase( "associatedDomain" );
375                }
376        }
377
378        return true;
379}
380
381
382
383bool LdapBackend::get( DNSResourceRecord &rr )
384{
385        QType qt;
386        vector<string> parts;
387        string attrname, content, qstr;
388
389
390        try
391        {
392                do
393                {
394                        while( m_adomain != m_adomains.end() )
395                        {
396                                while( m_attribute != m_result.end() )
397                                {
398                                        attrname = m_attribute->first;
399                                        qstr = attrname.substr( 0, attrname.length() - 6 );   // extract qtype string from ldap attribute name
400                                        qt = QType( const_cast<char*>(toUpper( qstr ).c_str()) );
401
402                                        while( m_value != m_attribute->second.end() )
403                                        {
404                                                content = *m_value;
405
406                                                rr.qtype = qt;
407                                                rr.qname = *m_adomain;
408                                                rr.priority = 0;
409                                                rr.ttl = m_ttl;
410
411                                                if( qt.getCode() == QType::MX || qt.getCode() == QType::SRV )   // Priority, e.g. 10 smtp.example.com
412                                                {
413                                                        char* endptr;
414                                                        string::size_type first = content.find_first_of( " " );
415
416                                                        if( first == string::npos )
417                                                        {
418                                                                L << Logger::Warning << m_myname << " Invalid " << attrname << " without priority for " << m_qname << ": " << content << endl;
419                                                                m_value++;
420                                                                continue;
421                                                        }
422
423                                                        rr.priority = (uint16_t) strtoul( (content.substr( 0, first )).c_str(), &endptr, 10 );
424                                                        if( *endptr != '\0' )
425                                                        {
426                                                                L << Logger::Warning << m_myname << " Invalid " << attrname << " without priority for " << m_qname << ": " << content << endl;
427                                                                m_value++;
428                                                                continue;
429                                                        }
430
431                                                        content = content.substr( first + 1, content.length() - first - 1 );
432                                                }
433
434                                                rr.content = content;
435                                                m_value++;
436
437                                                DLOG( L << Logger::Debug << m_myname << " Record = qname: " << rr.qname << ", qtype: " << (rr.qtype).getName() << ", priority: " << rr.priority << ", ttl: " << rr.ttl << ", content: " << rr.content << endl );
438                                                return true;
439                                        }
440
441                                        m_attribute++;
442                                        m_value = m_attribute->second.begin();
443                                }
444                                m_adomain++;
445                                m_attribute = m_result.begin();
446                                m_value = m_attribute->second.begin();
447                        }
448                }
449                while( m_pldap->getSearchEntry( m_msgid, m_result, m_getdn ) && prepare() );
450
451        }
452        catch( LDAPTimeout &lt )
453        {
454                L << Logger::Warning << m_myname << " Search failed: " << lt.what() << endl;
455                throw( DBException( "LDAP server timeout" ) );
456        }
457        catch( LDAPException &le )
458        {
459                L << Logger::Error << m_myname << " Search failed: " << le.what() << endl;
460                throw( AhuException( "LDAP server unreachable" ) );   // try to reconnect to another server
461        }
462        catch( exception &e )
463        {
464                L << Logger::Error << m_myname << " Caught STL exception for " << m_qname << ": " << e.what() << endl;
465                throw( DBException( "STL exception" ) );
466        }
467
468        return false;
469}
470
471
472
473 bool LdapBackend::getDomainInfo( const string& domain, DomainInfo& di )
474{
475        string filter;
476        SOAData sd;
477        char* attronly[] = { "sOARecord", NULL };
478
479
480        // search for SOARecord of domain
481        filter = "(&(associatedDomain=" + toLower( m_pldap->escape( domain ) ) + ")(SOARecord=*))";
482        m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, (const char**) attronly );
483        m_pldap->getSearchEntry( m_msgid, m_result );
484
485        if( m_result.count( "sOARecord" ) && !m_result["sOARecord"].empty() )
486        {
487                sd.serial = 0;
488                fillSOAData( m_result["sOARecord"][0], sd );
489       
490                di.id = 0;
491                di.serial = sd.serial;
492                di.zone = domain;
493                di.last_check = 0;
494                di.backend = this;
495                di.kind = DomainInfo::Master;
496
497                return true;
498        }
499       
500        return false;
501}
502
503
504
505
506
507class LdapFactory : public BackendFactory
508{
509
510public:
511
512        LdapFactory() : BackendFactory( "ldap" ) {}
513
514
515        void declareArguments( const string &suffix="" )
516        {
517                declare( suffix, "host", "One or more LDAP server with ports or LDAP URIs (separated by spaces)","ldap://127.0.0.1:389/" );
518                declare( suffix, "starttls", "Use TLS to encrypt connection (unused for LDAP URIs)", "no" );
519                declare( suffix, "basedn", "Search root in ldap tree (must be set)","" );
520                declare( suffix, "binddn", "User dn for non anonymous binds","" );
521                declare( suffix, "secret", "User password for non anonymous binds", "" );
522                declare( suffix, "method", "How to search entries (simple, strict or tree)", "simple" );
523                declare( suffix, "filter-axfr", "LDAP filter for limiting AXFR results", ":target:" );
524                declare( suffix, "filter-lookup", "LDAP filter for limiting IP or name lookups", ":target:" );
525                declare( suffix, "disable-ptrrecord", "Depricated, use ldap-method=strict instead", "no" );
526        }
527
528
529        DNSBackend* make( const string &suffix="" )
530        {
531                return new LdapBackend( suffix );
532        }
533};
534
535
536
537
538
539class LdapLoader
540{
541        LdapFactory factory;
542
543public:
544
545        LdapLoader()
546        {
547                BackendMakers().report( &factory );
548                L << Logger::Info << " [LdapBackend] This is the ldap module version "VERSION" ("__DATE__", "__TIME__") reporting" << endl;
549        }
550};
551
552
553static LdapLoader ldaploader;
Note: See TracBrowser for help on using the browser.