]> git.ipfire.org Git - thirdparty/pdns.git/blob - modules/ldapbackend/native.cc
Merge pull request #14237 from romeroalx/fix-docs-pip-pinning
[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 bool LdapBackend::list(const DNSName& target, int domain_id, bool /* include_disabled */)
28 {
29 try {
30 d_in_list = true;
31 d_qname = target;
32 d_qtype = QType::ANY;
33 d_results_cache.clear();
34
35 return (this->*d_list_fcnt)(target, domain_id);
36 }
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");
40 }
41 catch (LDAPNoConnection& lnc) {
42 g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
43 if (reconnect())
44 this->list(target, domain_id);
45 else
46 throw PDNSException("Failed to reconnect to LDAP server");
47 }
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
51 }
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");
55 }
56
57 return false;
58 }
59
60 bool LdapBackend::list_simple(const DNSName& target, int /* domain_id */)
61 {
62 string dn;
63 string filter;
64 string qesc;
65
66 dn = getArg("basedn");
67 qesc = toLower(d_pldap->escape(target.toStringRootDot()));
68
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))
73 return false;
74
75 if (d_result.count("dn") && !d_result["dn"].empty()) {
76 if (!mustDo("basedn-axfr-override")) {
77 dn = d_result["dn"][0];
78 }
79 }
80
81 // If we have any records associated with this entry let's parse them here
82 DNSResult soa_result;
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)));
87
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);
91
92 return true;
93 }
94
95 bool LdapBackend::list_strict(const DNSName& target, int domain_id)
96 {
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
100 }
101
102 return list_simple(target, domain_id);
103 }
104
105 void LdapBackend::lookup(const QType& qtype, const DNSName& qname, int zoneid, DNSPacket* dnspkt)
106 {
107 try {
108 d_in_list = false;
109 d_qname = qname;
110 d_qtype = qtype;
111 d_results_cache.clear();
112
113 if (d_qlog) {
114 g_log.log("Query: '" + qname.toStringRootDot() + "|" + qtype.toString() + "'", Logger::Error);
115 }
116 (this->*d_lookup_fcnt)(qtype, qname, dnspkt, zoneid);
117 }
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");
121 }
122 catch (LDAPNoConnection& lnc) {
123 g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
124 if (reconnect())
125 this->lookup(qtype, qname, zoneid, dnspkt);
126 else
127 throw PDNSException("Failed to reconnect to LDAP server");
128 }
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
132 }
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");
136 }
137 }
138
139 void LdapBackend::lookup_simple(const QType& qtype, const DNSName& qname, DNSPacket* /* dnspkt */, int /* zoneid */)
140 {
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};
144
145 qesc = toLower(d_pldap->escape(qname.toStringRootDot()));
146 filter = "associatedDomain=" + qesc;
147
148 if (qtype.getCode() != QType::ANY) {
149 attr = qtype.toString() + "Record";
150 filter = "&(" + filter + ")(" + attr + "=*)";
151 attronly[0] = attr.c_str();
152 attributes = attronly;
153 }
154
155 filter = strbind(":target:", filter, getArg("filter-lookup"));
156
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);
159 }
160
161 void LdapBackend::lookup_strict(const QType& qtype, const DNSName& qname, DNSPacket* /* dnspkt */, int /* zoneid */)
162 {
163 int len;
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};
168
169 qesc = toLower(d_pldap->escape(qname.toStringRootDot()));
170 stringtok(parts, qesc, ".");
171 len = qesc.length();
172
173 if (parts.size() == 6 && len > 13 && qesc.substr(len - 13, 13) == ".in-addr.arpa") // IPv4 reverse lookups
174 {
175 filter = "aRecord=" + ptr2ip4(parts);
176 attronly[0] = "associatedDomain";
177 attributes = attronly;
178 }
179 else if (parts.size() == 34 && len > 9 && (qesc.substr(len - 9, 9) == ".ip6.arpa")) // IPv6 reverse lookups
180 {
181 filter = "aAAARecord=" + ptr2ip6(parts);
182 attronly[0] = "associatedDomain";
183 attributes = attronly;
184 }
185 else // IPv4 and IPv6 lookups
186 {
187 filter = "associatedDomain=" + qesc;
188 }
189
190 if (qtype.getCode() != QType::ANY) {
191 attr = qtype.toString() + "Record";
192 filter = "&(" + filter + ")(" + attr + "=*)";
193 attronly[0] = attr.c_str();
194 attributes = attronly;
195 }
196
197 filter = strbind(":target:", filter, getArg("filter-lookup"));
198
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);
201 }
202
203 void LdapBackend::lookup_tree(const QType& qtype, const DNSName& qname, DNSPacket* /* dnspkt */, int /* zoneid */)
204 {
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;
209
210 qesc = toLower(d_pldap->escape(qname.toStringRootDot()));
211 filter = "associatedDomain=" + qesc;
212
213 if (qtype.getCode() != QType::ANY) {
214 attr = qtype.toString() + "Record";
215 filter = "&(" + filter + ")(" + attr + "=*)";
216 attronly[0] = attr.c_str();
217 attributes = attronly;
218 }
219
220 filter = strbind(":target:", filter, getArg("filter-lookup"));
221
222 stringtok(parts, toLower(qname.toString()), ".");
223 for (auto i = parts.crbegin(); i != parts.crend(); i++) {
224 dn = "dc=" + *i + "," + dn;
225 }
226
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);
229 }
230
231 bool LdapBackend::get(DNSResourceRecord& rr)
232 {
233 if (d_results_cache.empty()) {
234 while (d_results_cache.empty()) {
235 bool exhausted = false;
236 bool valid_entry_found = false;
237
238 while (!valid_entry_found && !exhausted) {
239 try {
240 exhausted = !d_search->getNext(d_result, true);
241 }
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");
245 }
246
247 if (!exhausted) {
248 if (!d_in_list) {
249 // All entries are valid here
250 valid_entry_found = true;
251 }
252 else {
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;
257 }
258 }
259 }
260
261 if (exhausted) {
262 break;
263 }
264
265 DNSResult result_template;
266 result_template.ttl = d_default_ttl;
267 result_template.lastmod = 0;
268 this->extract_common_attributes(result_template);
269
270 std::vector<std::string> associatedDomains;
271
272 if (d_result.count("associatedDomain")) {
273 if (d_in_list) {
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.
280
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);
286 }
287 }
288 else {
289 // This was a lookup in strict mode, so we add the reverse lookup
290 // information manually.
291 d_result["pTRRecord"] = d_result["associatedDomain"];
292 }
293 }
294
295 if (d_in_list) {
296 for (const auto& domain : associatedDomains)
297 this->extract_entry_results(DNSName(domain), result_template, QType(uint16_t(QType::ANY)));
298 }
299 else {
300 this->extract_entry_results(d_qname, result_template, QType(uint16_t(QType::ANY)));
301 }
302 }
303
304 if (d_results_cache.empty())
305 return false;
306 }
307
308 DNSResult result = d_results_cache.back();
309 d_results_cache.pop_back();
310 rr.qtype = result.qtype;
311 rr.qname = result.qname;
312 rr.ttl = result.ttl;
313 rr.last_modified = 0;
314 rr.content = result.value;
315 rr.auth = result.auth;
316
317 g_log << Logger::Debug << d_myname << " Record = qname: " << rr.qname << ", qtype: " << (rr.qtype).toString() << ", ttl: " << rr.ttl << ", content: " << rr.content << endl;
318 return true;
319 }
320
321 bool LdapBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool /* getSerial */)
322 {
323 string filter;
324 SOAData sd;
325 PowerLDAP::sentry_t result;
326 const char* attronly[] = {
327 "sOARecord",
328 "PdnsDomainId",
329 "PdnsDomainNotifiedSerial",
330 "PdnsDomainLastCheck",
331 "PdnsDomainMaster",
332 "PdnsDomainType",
333 NULL};
334
335 try {
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)) {
340 return false;
341 }
342 }
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");
346 }
347 catch (LDAPNoConnection& lnc) {
348 g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
349 if (reconnect())
350 this->getDomainInfo(domain, di);
351 else
352 throw PDNSException("Failed to reconnect to LDAP server");
353 }
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
357 }
358 catch (std::exception& e) {
359 throw DBException("STL exception");
360 }
361
362 if (result.count("sOARecord") && !result["sOARecord"].empty()) {
363 sd.serial = 0;
364 fillSOAData(result["sOARecord"][0], sd);
365
366 if (result.count("PdnsDomainId") && !result["PdnsDomainId"].empty())
367 di.id = std::stoi(result["PdnsDomainId"][0]);
368 else
369 di.id = 0;
370
371 di.serial = sd.serial;
372 di.zone = DNSName(domain);
373
374 if (result.count("PdnsDomainLastCheck") && !result["PdnsDomainLastCheck"].empty())
375 pdns::checked_stoi_into(di.last_check, result["PdnsDomainLastCheck"][0]);
376 else
377 di.last_check = 0;
378
379 if (result.count("PdnsDomainNotifiedSerial") && !result["PdnsDomainNotifiedSerial"].empty())
380 pdns::checked_stoi_into(di.notified_serial, result["PdnsDomainNotifiedSerial"][0]);
381 else
382 di.notified_serial = 0;
383
384 if (result.count("PdnsDomainMaster") && !result["PdnsDomainMaster"].empty()) {
385 for (const auto& m : result["PdnsDomainMaster"])
386 di.primaries.emplace_back(m, 53);
387 }
388
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;
395 else
396 di.kind = DomainInfo::Native;
397 }
398 else {
399 di.kind = DomainInfo::Native;
400 }
401
402 di.backend = this;
403 return true;
404 }
405
406 return false;
407 }