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

Revision 977, 15.4 KB (checked in by ahu, 6 years ago)

ldap, opendbx updates

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