]>
git.ipfire.org Git - thirdparty/pdns.git/blob - modules/ldapbackend/ldapbackend.cc
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.
26 #include "exceptions.hh"
27 #include "ldapauthenticator_p.hh"
28 #include "ldapbackend.hh"
31 unsigned int ldap_host_index
= 0;
33 LdapBackend::LdapBackend(const string
& suffix
)
42 d_authenticator
= nullptr;
43 d_qlog
= arg().mustDo("query-logging");
44 d_default_ttl
= arg().asNum("default-ttl");
45 d_myname
= "[LdapBackend]";
48 setArgPrefix("ldap" + suffix
);
51 d_reconnect_attempts
= getArgAsNum("reconnect-attempts");
52 d_list_fcnt
= &LdapBackend::list_simple
;
53 d_lookup_fcnt
= &LdapBackend::lookup_simple
;
55 if (getArg("method") == "tree") {
56 d_lookup_fcnt
= &LdapBackend::lookup_tree
;
59 if (getArg("method") == "strict" || mustDo("disable-ptrrecord")) {
60 d_list_fcnt
= &LdapBackend::list_strict
;
61 d_lookup_fcnt
= &LdapBackend::lookup_strict
;
64 stringtok(hosts
, getArg("host"), ", ");
65 idx
= ldap_host_index
++ % hosts
.size();
68 for (i
= 1; i
< hosts
.size(); i
++) {
69 hoststr
+= " " + hosts
[(idx
+ i
) % hosts
.size()];
72 g_log
<< Logger::Info
<< d_myname
<< " LDAP servers = " << hoststr
<< endl
;
74 d_pldap
= new PowerLDAP(hoststr
.c_str(), LDAP_PORT
, mustDo("starttls"), getArgAsNum("timeout"));
75 d_pldap
->setOption(LDAP_OPT_DEREF
, LDAP_DEREF_ALWAYS
);
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"));
83 d_authenticator
= new LdapSimpleAuthenticator(getArg("binddn"), getArg("secret"), getArgAsNum("timeout"));
85 d_pldap
->bind(d_authenticator
);
87 g_log
<< Logger::Notice
<< d_myname
<< " Ldap connection succeeded" << endl
;
90 catch (LDAPTimeout
& lt
) {
91 g_log
<< Logger::Error
<< d_myname
<< " Ldap connection to server failed because of timeout" << endl
;
93 catch (LDAPException
& le
) {
94 g_log
<< Logger::Error
<< d_myname
<< " Ldap connection to server failed: " << le
.what() << endl
;
96 catch (std::exception
& e
) {
97 g_log
<< Logger::Error
<< d_myname
<< " Caught STL exception: " << e
.what() << endl
;
100 if (d_pldap
!= nullptr) {
103 throw PDNSException("Unable to connect to ldap server");
106 LdapBackend::~LdapBackend()
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
112 delete (d_authenticator
);
113 g_log
<< Logger::Notice
<< d_myname
<< " Ldap connection closed" << endl
;
116 bool LdapBackend::reconnect()
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();
124 Utility::usleep(250);
129 d_pldap
->bind(d_authenticator
);
134 void LdapBackend::extract_common_attributes(DNSResult
& result
)
136 if (d_result
.count("dNSTTL") && !d_result
["dNSTTL"].empty()) {
138 uint32_t ttl
= (uint32_t)strtol(d_result
["dNSTTL"][0].c_str(), &endptr
, 10);
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
;
151 // We have to erase the attribute, otherwise this will mess up the records retrieval later.
152 d_result
.erase("dNSTTL");
155 if (d_result
.count("modifyTimestamp") && !d_result
["modifyTimestamp"].empty()) {
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
;
162 result
.lastmod
= tstamp
;
165 // Here too we have to erase this attribute.
166 d_result
.erase("modifyTimestamp");
170 void LdapBackend::extract_entry_results(const DNSName
& domain
, const DNSResult
& result_template
, QType qtype
)
172 std::string attrname
, qstr
;
174 bool has_records
= false;
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) {
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);
185 for (const auto& value
: attribute
.second
) {
186 if (qtype
!= qt
&& qtype
!= QType::ANY
) {
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;
196 // Now let's see if we have some PDNS record data
199 if (d_result
.count("PdnsRecordTTL") && !d_result
["PdnsRecordTTL"].empty()) {
200 for (const auto& rdata
: d_result
["PdnsRecordTTL"]) {
202 std::size_t pos
= rdata
.find_first_of('|', 0);
203 if (pos
== std::string::npos
)
206 qtype2
= rdata
.substr(0, pos
);
207 if (qtype2
!= QType(local_result
.qtype
).toString())
210 pdns::checked_stoi_into(local_result
.ttl
, rdata
.substr(pos
+ 1));
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;
223 if (d_result
.count("PdnsRecordOrdername") && !d_result
["PdnsRecordOrdername"].empty()) {
224 std::string defaultOrdername
;
226 for (const auto& rdata
: d_result
["PdnsRecordOrdername"]) {
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
;
235 qtype2
= rdata
.substr(0, pos
);
236 if (qtype2
!= QType(local_result
.qtype
).toString())
239 local_result
.ordername
= rdata
.substr(pos
+ 1);
242 if (local_result
.ordername
.empty() && !defaultOrdername
.empty())
243 local_result
.ordername
= defaultOrdername
;
246 d_results_cache
.push_back(local_result
);
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;
259 d_results_cache
.push_back(local_result
);
263 class LdapFactory
: public BackendFactory
267 BackendFactory("ldap") {}
269 void declareArguments(const string
& suffix
= "") override
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");
288 DNSBackend
* make(const string
& suffix
= "") override
290 return new LdapBackend(suffix
);
301 BackendMakers().report(&factory
);
302 g_log
<< Logger::Info
<< "[ldapbackend] This is the ldap backend version " VERSION
304 << " (" __DATE__
" " __TIME__
")"
306 << " reporting" << endl
;
310 static LdapLoader ldaploader
;