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"
27 bool LdapBackend::list(const DNSName
& target
, int domain_id
, bool /* include_disabled */)
33 d_results_cache
.clear();
35 return (this->*d_list_fcnt
)(target
, domain_id
);
37 catch (LDAPTimeout
& lt
) {
38 g_log
<< Logger::Warning
<< d_myname
<< " Unable to get zone " << target
<< " from LDAP directory: " << lt
.what() << endl
;
39 throw DBException("LDAP server timeout");
41 catch (LDAPNoConnection
& lnc
) {
42 g_log
<< Logger::Warning
<< d_myname
<< " Connection to LDAP lost, trying to reconnect" << endl
;
44 this->list(target
, domain_id
);
46 throw PDNSException("Failed to reconnect to LDAP server");
48 catch (LDAPException
& le
) {
49 g_log
<< Logger::Error
<< d_myname
<< " Unable to get zone " << target
<< " from LDAP directory: " << le
.what() << endl
;
50 throw PDNSException("LDAP server unreachable"); // try to reconnect to another server
52 catch (std::exception
& e
) {
53 g_log
<< Logger::Error
<< d_myname
<< " Caught STL exception for target " << target
<< ": " << e
.what() << endl
;
54 throw DBException("STL exception");
60 bool LdapBackend::list_simple(const DNSName
& target
, int /* domain_id */)
66 dn
= getArg("basedn");
67 qesc
= toLower(d_pldap
->escape(target
.toStringRootDot()));
69 // search for SOARecord of target
70 filter
= strbind(":target:", "&(associatedDomain=" + qesc
+ ")(sOARecord=*)", getArg("filter-axfr"));
71 PowerLDAP::SearchResult::Ptr search
= d_pldap
->search(dn
, LDAP_SCOPE_SUBTREE
, filter
, (const char**)ldap_attrany
);
72 if (!search
->getNext(d_result
, true))
75 if (d_result
.count("dn") && !d_result
["dn"].empty()) {
76 if (!mustDo("basedn-axfr-override")) {
77 dn
= d_result
["dn"][0];
81 // If we have any records associated with this entry let's parse them here
83 soa_result
.ttl
= d_default_ttl
;
84 soa_result
.lastmod
= 0;
85 this->extract_common_attributes(soa_result
);
86 this->extract_entry_results(d_qname
, soa_result
, QType(uint16_t(QType::ANY
)));
88 filter
= strbind(":target:", "associatedDomain=*." + qesc
, getArg("filter-axfr"));
89 g_log
<< Logger::Debug
<< d_myname
<< " Search = basedn: " << dn
<< ", filter: " << filter
<< endl
;
90 d_search
= d_pldap
->search(dn
, LDAP_SCOPE_SUBTREE
, filter
, (const char**)ldap_attrany
);
95 bool LdapBackend::list_strict(const DNSName
& target
, int domain_id
)
97 if (target
.isPartOf(DNSName("in-addr.arpa")) || target
.isPartOf(DNSName("ip6.arpa"))) {
98 g_log
<< Logger::Warning
<< d_myname
<< " Request for reverse zone AXFR, but this is not supported in strict mode" << endl
;
99 return false; // AXFR isn't supported in strict mode. Use simple mode and additional PTR records
102 return list_simple(target
, domain_id
);
105 void LdapBackend::lookup(const QType
& qtype
, const DNSName
& qname
, int zoneid
, DNSPacket
* dnspkt
)
111 d_results_cache
.clear();
114 g_log
.log("Query: '" + qname
.toStringRootDot() + "|" + qtype
.toString() + "'", Logger::Error
);
116 (this->*d_lookup_fcnt
)(qtype
, qname
, dnspkt
, zoneid
);
118 catch (LDAPTimeout
& lt
) {
119 g_log
<< Logger::Warning
<< d_myname
<< " Unable to search LDAP directory: " << lt
.what() << endl
;
120 throw DBException("LDAP server timeout");
122 catch (LDAPNoConnection
& lnc
) {
123 g_log
<< Logger::Warning
<< d_myname
<< " Connection to LDAP lost, trying to reconnect" << endl
;
125 this->lookup(qtype
, qname
, zoneid
, dnspkt
);
127 throw PDNSException("Failed to reconnect to LDAP server");
129 catch (LDAPException
& le
) {
130 g_log
<< Logger::Error
<< d_myname
<< " Unable to search LDAP directory: " << le
.what() << endl
;
131 throw PDNSException("LDAP server unreachable"); // try to reconnect to another server
133 catch (std::exception
& e
) {
134 g_log
<< Logger::Error
<< d_myname
<< " Caught STL exception for qname " << qname
<< ": " << e
.what() << endl
;
135 throw DBException("STL exception");
139 void LdapBackend::lookup_simple(const QType
& qtype
, const DNSName
& qname
, DNSPacket
* /* dnspkt */, int /* zoneid */)
141 string filter
, attr
, qesc
;
142 const char** attributes
= ldap_attrany
+ 1; // skip associatedDomain
143 const char* attronly
[] = {NULL
, "dNSTTL", "modifyTimestamp", "PdnsRecordTTL", "PdnsRecordAuth", "PdnsRecordOrdername", NULL
};
145 qesc
= toLower(d_pldap
->escape(qname
.toStringRootDot()));
146 filter
= "associatedDomain=" + qesc
;
148 if (qtype
.getCode() != QType::ANY
) {
149 attr
= qtype
.toString() + "Record";
150 filter
= "&(" + filter
+ ")(" + attr
+ "=*)";
151 attronly
[0] = attr
.c_str();
152 attributes
= attronly
;
155 filter
= strbind(":target:", filter
, getArg("filter-lookup"));
157 g_log
<< Logger::Debug
<< d_myname
<< " Search = basedn: " << getArg("basedn") << ", filter: " << filter
<< ", qtype: " << qtype
.toString() << endl
;
158 d_search
= d_pldap
->search(getArg("basedn"), LDAP_SCOPE_SUBTREE
, filter
, attributes
);
161 void LdapBackend::lookup_strict(const QType
& qtype
, const DNSName
& qname
, DNSPacket
* /* dnspkt */, int /* zoneid */)
164 vector
<string
> parts
;
165 string filter
, attr
, qesc
;
166 const char** attributes
= ldap_attrany
+ 1; // skip associatedDomain
167 const char* attronly
[] = {NULL
, "dNSTTL", "modifyTimestamp", "PdnsRecordTTL", "PdnsRecordAuth", "PdnsRecordOrdername", NULL
};
169 qesc
= toLower(d_pldap
->escape(qname
.toStringRootDot()));
170 stringtok(parts
, qesc
, ".");
173 if (parts
.size() == 6 && len
> 13 && qesc
.substr(len
- 13, 13) == ".in-addr.arpa") // IPv4 reverse lookups
175 filter
= "aRecord=" + ptr2ip4(parts
);
176 attronly
[0] = "associatedDomain";
177 attributes
= attronly
;
179 else if (parts
.size() == 34 && len
> 9 && (qesc
.substr(len
- 9, 9) == ".ip6.arpa")) // IPv6 reverse lookups
181 filter
= "aAAARecord=" + ptr2ip6(parts
);
182 attronly
[0] = "associatedDomain";
183 attributes
= attronly
;
185 else // IPv4 and IPv6 lookups
187 filter
= "associatedDomain=" + qesc
;
190 if (qtype
.getCode() != QType::ANY
) {
191 attr
= qtype
.toString() + "Record";
192 filter
= "&(" + filter
+ ")(" + attr
+ "=*)";
193 attronly
[0] = attr
.c_str();
194 attributes
= attronly
;
197 filter
= strbind(":target:", filter
, getArg("filter-lookup"));
199 g_log
<< Logger::Debug
<< d_myname
<< " Search = basedn: " << getArg("basedn") << ", filter: " << filter
<< ", qtype: " << qtype
.toString() << endl
;
200 d_search
= d_pldap
->search(getArg("basedn"), LDAP_SCOPE_SUBTREE
, filter
, attributes
);
203 void LdapBackend::lookup_tree(const QType
& qtype
, const DNSName
& qname
, DNSPacket
* /* dnspkt */, int /* zoneid */)
205 string filter
, attr
, qesc
, dn
;
206 const char** attributes
= ldap_attrany
+ 1; // skip associatedDomain
207 const char* attronly
[] = {NULL
, "dNSTTL", "modifyTimestamp", "PdnsRecordTTL", "PdnsRecordAuth", "PdnsRecordOrdername", NULL
};
208 vector
<string
> parts
;
210 qesc
= toLower(d_pldap
->escape(qname
.toStringRootDot()));
211 filter
= "associatedDomain=" + qesc
;
213 if (qtype
.getCode() != QType::ANY
) {
214 attr
= qtype
.toString() + "Record";
215 filter
= "&(" + filter
+ ")(" + attr
+ "=*)";
216 attronly
[0] = attr
.c_str();
217 attributes
= attronly
;
220 filter
= strbind(":target:", filter
, getArg("filter-lookup"));
222 stringtok(parts
, toLower(qname
.toString()), ".");
223 for (auto i
= parts
.crbegin(); i
!= parts
.crend(); i
++) {
224 dn
= "dc=" + *i
+ "," + dn
;
227 g_log
<< Logger::Debug
<< d_myname
<< " Search = basedn: " << dn
+ getArg("basedn") << ", filter: " << filter
<< ", qtype: " << qtype
.toString() << endl
;
228 d_search
= d_pldap
->search(dn
+ getArg("basedn"), LDAP_SCOPE_BASE
, filter
, attributes
);
231 bool LdapBackend::get(DNSResourceRecord
& rr
)
233 if (d_results_cache
.empty()) {
234 while (d_results_cache
.empty()) {
235 bool exhausted
= false;
236 bool valid_entry_found
= false;
238 while (!valid_entry_found
&& !exhausted
) {
240 exhausted
= !d_search
->getNext(d_result
, true);
242 catch (LDAPException
& le
) {
243 g_log
<< Logger::Error
<< d_myname
<< " Failed to get next result: " << le
.what() << endl
;
244 throw PDNSException("Get next result impossible");
249 // All entries are valid here
250 valid_entry_found
= true;
253 // If we're called after list() then the entry *must* contain
254 // associatedDomain, otherwise let's just skip it
255 if (d_result
.count("associatedDomain"))
256 valid_entry_found
= true;
265 DNSResult result_template
;
266 result_template
.ttl
= d_default_ttl
;
267 result_template
.lastmod
= 0;
268 this->extract_common_attributes(result_template
);
270 std::vector
<std::string
> associatedDomains
;
272 if (d_result
.count("associatedDomain")) {
274 // We can have more than one associatedDomain in the entry, so for each of them we have to check
275 // that they are indeed under the domain we've been asked to list (nothing enforces this, so you
276 // can have one associatedDomain set to "host.first-domain.com" and another one set to
277 // "host.second-domain.com"). Better not return the latter I guess :)
278 // We also have to generate one DNSResult per DNS-relevant attribute. As we've asked only for them
279 // and the others above we've already cleaned it's just a matter of iterating over them.
281 unsigned int axfrqlen
= d_qname
.toStringRootDot().length();
282 for (auto i
= d_result
["associatedDomain"].begin(); i
!= d_result
["associatedDomain"].end(); ++i
) {
283 // Sanity checks: is this associatedDomain attribute under the requested domain?
284 if (i
->size() >= axfrqlen
&& i
->substr(i
->size() - axfrqlen
, axfrqlen
) == d_qname
.toStringRootDot())
285 associatedDomains
.push_back(*i
);
289 // This was a lookup in strict mode, so we add the reverse lookup
290 // information manually.
291 d_result
["pTRRecord"] = d_result
["associatedDomain"];
296 for (const auto& domain
: associatedDomains
)
297 this->extract_entry_results(DNSName(domain
), result_template
, QType(uint16_t(QType::ANY
)));
300 this->extract_entry_results(d_qname
, result_template
, QType(uint16_t(QType::ANY
)));
304 if (d_results_cache
.empty())
308 DNSResult result
= d_results_cache
.back();
309 d_results_cache
.pop_back();
310 rr
.qtype
= result
.qtype
;
311 rr
.qname
= result
.qname
;
313 rr
.last_modified
= 0;
314 rr
.content
= result
.value
;
315 rr
.auth
= result
.auth
;
317 g_log
<< Logger::Debug
<< d_myname
<< " Record = qname: " << rr
.qname
<< ", qtype: " << (rr
.qtype
).toString() << ", ttl: " << rr
.ttl
<< ", content: " << rr
.content
<< endl
;
321 bool LdapBackend::getDomainInfo(const DNSName
& domain
, DomainInfo
& di
, bool /* getSerial */)
325 PowerLDAP::sentry_t result
;
326 const char* attronly
[] = {
329 "PdnsDomainNotifiedSerial",
330 "PdnsDomainLastCheck",
336 // search for SOARecord of domain
337 filter
= "(&(associatedDomain=" + toLower(d_pldap
->escape(domain
.toStringRootDot())) + ")(SOARecord=*))";
338 d_search
= d_pldap
->search(getArg("basedn"), LDAP_SCOPE_SUBTREE
, filter
, attronly
);
339 if (!d_search
->getNext(result
)) {
343 catch (LDAPTimeout
& lt
) {
344 g_log
<< Logger::Warning
<< d_myname
<< " Unable to search LDAP directory: " << lt
.what() << endl
;
345 throw DBException("LDAP server timeout");
347 catch (LDAPNoConnection
& lnc
) {
348 g_log
<< Logger::Warning
<< d_myname
<< " Connection to LDAP lost, trying to reconnect" << endl
;
350 this->getDomainInfo(domain
, di
);
352 throw PDNSException("Failed to reconnect to LDAP server");
354 catch (LDAPException
& le
) {
355 g_log
<< Logger::Error
<< d_myname
<< " Unable to search LDAP directory: " << le
.what() << endl
;
356 throw PDNSException("LDAP server unreachable"); // try to reconnect to another server
358 catch (std::exception
& e
) {
359 throw DBException("STL exception");
362 if (result
.count("sOARecord") && !result
["sOARecord"].empty()) {
364 fillSOAData(result
["sOARecord"][0], sd
);
366 if (result
.count("PdnsDomainId") && !result
["PdnsDomainId"].empty())
367 di
.id
= std::stoi(result
["PdnsDomainId"][0]);
371 di
.serial
= sd
.serial
;
372 di
.zone
= DNSName(domain
);
374 if (result
.count("PdnsDomainLastCheck") && !result
["PdnsDomainLastCheck"].empty())
375 pdns::checked_stoi_into(di
.last_check
, result
["PdnsDomainLastCheck"][0]);
379 if (result
.count("PdnsDomainNotifiedSerial") && !result
["PdnsDomainNotifiedSerial"].empty())
380 pdns::checked_stoi_into(di
.notified_serial
, result
["PdnsDomainNotifiedSerial"][0]);
382 di
.notified_serial
= 0;
384 if (result
.count("PdnsDomainMaster") && !result
["PdnsDomainMaster"].empty()) {
385 for (const auto& m
: result
["PdnsDomainMaster"])
386 di
.primaries
.emplace_back(m
, 53);
389 if (result
.count("PdnsDomainType") && !result
["PdnsDomainType"].empty()) {
390 string kind
= result
["PdnsDomainType"][0];
391 if (kind
== "master")
392 di
.kind
= DomainInfo::Primary
;
393 else if (kind
== "slave")
394 di
.kind
= DomainInfo::Secondary
;
396 di
.kind
= DomainInfo::Native
;
399 di
.kind
= DomainInfo::Native
;