]> git.ipfire.org Git - thirdparty/pdns.git/blob - modules/ldapbackend/ldapbackend.cc
Merge pull request #11431 from jroessler-ox/docs-kskzskroll-update
[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 try {
40 d_qname.clear();
41 d_pldap = nullptr;
42 d_authenticator = nullptr;
43 d_qlog = arg().mustDo("query-logging");
44 d_default_ttl = arg().asNum("default-ttl");
45 d_myname = "[LdapBackend]";
46 d_in_list = false;
47
48 setArgPrefix("ldap" + suffix);
49
50 d_getdn = false;
51 d_reconnect_attempts = getArgAsNum("reconnect-attempts");
52 d_list_fcnt = &LdapBackend::list_simple;
53 d_lookup_fcnt = &LdapBackend::lookup_simple;
54
55 if (getArg("method") == "tree") {
56 d_lookup_fcnt = &LdapBackend::lookup_tree;
57 }
58
59 if (getArg("method") == "strict" || mustDo("disable-ptrrecord")) {
60 d_list_fcnt = &LdapBackend::list_strict;
61 d_lookup_fcnt = &LdapBackend::lookup_strict;
62 }
63
64 stringtok(hosts, getArg("host"), ", ");
65 idx = ldap_host_index++ % hosts.size();
66 hoststr = hosts[idx];
67
68 for (i = 1; i < hosts.size(); i++) {
69 hoststr += " " + hosts[(idx + i) % hosts.size()];
70 }
71
72 g_log << Logger::Info << d_myname << " LDAP servers = " << hoststr << endl;
73
74 d_pldap = new PowerLDAP(hoststr.c_str(), LDAP_PORT, mustDo("starttls"), getArgAsNum("timeout"));
75 d_pldap->setOption(LDAP_OPT_DEREF, LDAP_DEREF_ALWAYS);
76
77 string bindmethod = getArg("bindmethod");
78 if (bindmethod == "gssapi") {
79 setenv("KRB5CCNAME", getArg("krb5-ccache").c_str(), 1);
80 d_authenticator = new LdapGssapiAuthenticator(getArg("krb5-keytab"), getArg("krb5-ccache"), getArgAsNum("timeout"));
81 }
82 else {
83 d_authenticator = new LdapSimpleAuthenticator(getArg("binddn"), getArg("secret"), getArgAsNum("timeout"));
84 }
85 d_pldap->bind(d_authenticator);
86
87 g_log << Logger::Notice << d_myname << " Ldap connection succeeded" << endl;
88 return;
89 }
90 catch (LDAPTimeout& lt) {
91 g_log << Logger::Error << d_myname << " Ldap connection to server failed because of timeout" << endl;
92 }
93 catch (LDAPException& le) {
94 g_log << Logger::Error << d_myname << " Ldap connection to server failed: " << le.what() << endl;
95 }
96 catch (std::exception& e) {
97 g_log << Logger::Error << d_myname << " Caught STL exception: " << e.what() << endl;
98 }
99
100 if (d_pldap != nullptr) {
101 delete (d_pldap);
102 }
103 throw PDNSException("Unable to connect to ldap server");
104 }
105
106 LdapBackend::~LdapBackend()
107 {
108 d_search.reset(); // This is necessary otherwise d_pldap will get deleted first and
109 // we may hang in SearchResult::~SearchResult() waiting for the
110 // current operation to be abandoned
111 delete (d_pldap);
112 delete (d_authenticator);
113 g_log << Logger::Notice << d_myname << " Ldap connection closed" << endl;
114 }
115
116 bool LdapBackend::reconnect()
117 {
118 int attempts = d_reconnect_attempts;
119 bool connected = false;
120 while (!connected && attempts > 0) {
121 g_log << Logger::Debug << d_myname << " Reconnection attempts left: " << attempts << endl;
122 connected = d_pldap->connect();
123 if (!connected)
124 Utility::usleep(250);
125 --attempts;
126 }
127
128 if (connected)
129 d_pldap->bind(d_authenticator);
130
131 return connected;
132 }
133
134 void LdapBackend::extract_common_attributes(DNSResult& result)
135 {
136 if (d_result.count("dNSTTL") && !d_result["dNSTTL"].empty()) {
137 char* endptr;
138 uint32_t ttl = (uint32_t)strtol(d_result["dNSTTL"][0].c_str(), &endptr, 10);
139
140 if (*endptr != '\0') {
141 // NOTE: this will not give the entry for which the TTL was off.
142 // TODO: improve this.
143 // - Check how d_getdn is used, because if it's never false then we
144 // might as well use it.
145 g_log << Logger::Warning << d_myname << " Invalid time to live for " << d_qname << ": " << d_result["dNSTTL"][0] << endl;
146 }
147 else {
148 result.ttl = ttl;
149 }
150
151 // We have to erase the attribute, otherwise this will mess up the records retrieval later.
152 d_result.erase("dNSTTL");
153 }
154
155 if (d_result.count("modifyTimestamp") && !d_result["modifyTimestamp"].empty()) {
156 time_t tstamp = 0;
157 if ((tstamp = str2tstamp(d_result["modifyTimestamp"][0])) == 0) {
158 // Same note as above, we don't know which entry failed here
159 g_log << Logger::Warning << d_myname << " Invalid modifyTimestamp for " << d_qname << ": " << d_result["modifyTimestamp"][0] << endl;
160 }
161 else {
162 result.lastmod = tstamp;
163 }
164
165 // Here too we have to erase this attribute.
166 d_result.erase("modifyTimestamp");
167 }
168 }
169
170 void LdapBackend::extract_entry_results(const DNSName& domain, const DNSResult& result_template, QType qtype)
171 {
172 std::string attrname, qstr;
173 QType qt;
174 bool has_records = false;
175
176 for (const auto& attribute : d_result) {
177 // Find if we're dealing with a record attribute
178 if (attribute.first.length() > 6 && attribute.first.compare(attribute.first.length() - 6, 6, "Record") == 0) {
179 has_records = true;
180 attrname = attribute.first;
181 // extract qtype string from ldap attribute name by removing the 'Record' suffix.
182 qstr = attrname.substr(0, attrname.length() - 6);
183 qt = toUpper(qstr);
184
185 for (const auto& value : attribute.second) {
186 if (qtype != qt && qtype != QType::ANY) {
187 continue;
188 }
189
190 DNSResult local_result = result_template;
191 local_result.qtype = qt;
192 local_result.qname = domain;
193 local_result.value = value;
194 local_result.auth = true;
195
196 // Now let's see if we have some PDNS record data
197
198 // TTL
199 if (d_result.count("PdnsRecordTTL") && !d_result["PdnsRecordTTL"].empty()) {
200 for (const auto& rdata : d_result["PdnsRecordTTL"]) {
201 std::string qtype2;
202 std::size_t pos = rdata.find_first_of('|', 0);
203 if (pos == std::string::npos)
204 continue;
205
206 qtype2 = rdata.substr(0, pos);
207 if (qtype2 != QType(local_result.qtype).toString())
208 continue;
209
210 pdns::checked_stoi_into(local_result.ttl, rdata.substr(pos + 1));
211 }
212 }
213
214 // Not authoritative
215 if (d_result.count("PdnsRecordNoAuth") && !d_result["PdnsRecordNoAuth"].empty()) {
216 for (const auto& rdata : d_result["PdnsRecordNoAuth"]) {
217 if (rdata == QType(local_result.qtype).toString())
218 local_result.auth = false;
219 }
220 }
221
222 // Ordername
223 if (d_result.count("PdnsRecordOrdername") && !d_result["PdnsRecordOrdername"].empty()) {
224 std::string defaultOrdername;
225
226 for (const auto& rdata : d_result["PdnsRecordOrdername"]) {
227 std::string qtype2;
228 std::size_t pos = rdata.find_first_of('|', 0);
229 if (pos == std::string::npos) {
230 // This is the default ordername for all records in this entry
231 defaultOrdername = rdata;
232 continue;
233 }
234
235 qtype2 = rdata.substr(0, pos);
236 if (qtype2 != QType(local_result.qtype).toString())
237 continue;
238
239 local_result.ordername = rdata.substr(pos + 1);
240 }
241
242 if (local_result.ordername.empty() && !defaultOrdername.empty())
243 local_result.ordername = defaultOrdername;
244 }
245
246 d_results_cache.push_back(local_result);
247 }
248 }
249 }
250
251 if (!has_records) {
252 // This is an ENT
253 DNSResult local_result = result_template;
254 local_result.qname = domain;
255 if (!d_result.count("PdnsRecordOrdername") || d_result["PdnsRecordOrdername"].empty()) {
256 // An ENT with an order name is authoritative
257 local_result.auth = false;
258 }
259 d_results_cache.push_back(local_result);
260 }
261 }
262
263 class LdapFactory : public BackendFactory
264 {
265 public:
266 LdapFactory() :
267 BackendFactory("ldap") {}
268
269 void declareArguments(const string& suffix = "") override
270 {
271 declare(suffix, "host", "One or more LDAP server with ports or LDAP URIs (separated by spaces)", "ldap://127.0.0.1:389/");
272 declare(suffix, "starttls", "Use TLS to encrypt connection (unused for LDAP URIs)", "no");
273 declare(suffix, "basedn", "Search root in ldap tree (must be set)", "");
274 declare(suffix, "basedn-axfr-override", "Override base dn for AXFR subtree search", "no");
275 declare(suffix, "bindmethod", "Bind method to use (simple or gssapi)", "simple");
276 declare(suffix, "binddn", "User dn for non anonymous binds", "");
277 declare(suffix, "secret", "User password for non anonymous binds", "");
278 declare(suffix, "krb5-keytab", "The keytab to use for GSSAPI authentication", "");
279 declare(suffix, "krb5-ccache", "The credentials cache used for GSSAPI authentication", "");
280 declare(suffix, "timeout", "Seconds before connecting to server fails", "5");
281 declare(suffix, "method", "How to search entries (simple, strict or tree)", "simple");
282 declare(suffix, "filter-axfr", "LDAP filter for limiting AXFR results", "(:target:)");
283 declare(suffix, "filter-lookup", "LDAP filter for limiting IP or name lookups", "(:target:)");
284 declare(suffix, "disable-ptrrecord", "Deprecated, use ldap-method=strict instead", "no");
285 declare(suffix, "reconnect-attempts", "Number of attempts to re-establish a lost LDAP connection", "5");
286 }
287
288 DNSBackend* make(const string& suffix = "") override
289 {
290 return new LdapBackend(suffix);
291 }
292 };
293
294 class LdapLoader
295 {
296 LdapFactory factory;
297
298 public:
299 LdapLoader()
300 {
301 BackendMakers().report(&factory);
302 g_log << Logger::Info << "[ldapbackend] This is the ldap backend version " VERSION
303 #ifndef REPRODUCIBLE
304 << " (" __DATE__ " " __TIME__ ")"
305 #endif
306 << " reporting" << endl;
307 }
308 };
309
310 static LdapLoader ldaploader;