2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2020 Joel Sing <joel@sing.id.au>
5 * Copyright (C) 2019 Sadie Powell <sadie@witchery.services>
6 * Copyright (C) 2019 Robby <robby@chatbelgie.be>
7 * Copyright (C) 2014-2015 Attila Molnar <attilamolnar@hush.com>
8 * Copyright (C) 2014 Thiago Crepaldi <thiago@thiagocrepaldi.com>
9 * Copyright (C) 2013-2014, 2017 Adam <Adam@anope.org>
11 * This file is part of InspIRCd. InspIRCd is free software: you can
12 * redistribute it and/or modify it under the terms of the GNU General Public
13 * License as published by the Free Software Foundation, version 2.
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 #include "modules/ldap.h"
31 std::string killreason;
35 LocalStringExt* vhosts;
36 std::vector<std::pair<std::string, std::string> > requiredattributes;
39 class BindInterface : public LDAPInterface
41 const std::string provider;
42 const std::string uid;
44 bool checkingAttributes;
48 static std::string SafeReplace(const std::string& text, std::map<std::string, std::string>& replacements)
51 result.reserve(text.length());
53 for (unsigned int i = 0; i < text.length(); ++i)
58 // find the first nonalpha
60 unsigned int start = i;
62 while (i < text.length() - 1 && isalpha(text[i + 1]))
65 std::string key(text, start, (i - start) + 1);
66 result.append(replacements[key]);
75 static void SetVHost(User* user, const std::string& DN)
79 irc::commasepstream stream(DN);
81 // mashed map of key:value parts of the DN
82 std::map<std::string, std::string> dnParts;
85 while (stream.GetToken(dnPart))
87 std::string::size_type pos = dnPart.find('=');
88 if (pos == std::string::npos) // malformed
91 std::string key(dnPart, 0, pos);
92 std::string value(dnPart, pos + 1, dnPart.length() - pos + 1); // +1s to skip the = itself
96 // change host according to config key
97 vhosts->set(user, SafeReplace(vhost, dnParts));
102 BindInterface(Module* c, const std::string& p, const std::string& u, const std::string& dn)
104 , provider(p), uid(u), DN(dn), checkingAttributes(false), passed(false), attrCount(0)
108 void OnResult(const LDAPResult& r) CXX11_OVERRIDE
110 User* user = ServerInstance->FindUUID(uid);
111 dynamic_reference<LDAPProvider> LDAP(me, provider);
115 if (!checkingAttributes || !--attrCount)
120 if (!checkingAttributes && requiredattributes.empty())
123 ServerInstance->SNO->WriteToSnoMask('c', "Successful connection from %s (dn=%s)", user->GetFullRealHost().c_str(), DN.c_str());
125 // We're done, there are no attributes to check
127 authed->set(user, 1);
133 // Already checked attributes?
134 if (checkingAttributes)
138 // Only one has to pass
142 ServerInstance->SNO->WriteToSnoMask('c', "Successful connection from %s (dn=%s)", user->GetFullRealHost().c_str(), DN.c_str());
145 authed->set(user, 1);
148 // Delete this if this is the last ref
155 // check required attributes
156 checkingAttributes = true;
158 for (std::vector<std::pair<std::string, std::string> >::const_iterator it = requiredattributes.begin(); it != requiredattributes.end(); ++it)
160 // Note that only one of these has to match for it to be success
161 const std::string& attr = it->first;
162 const std::string& val = it->second;
164 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "LDAP compare: %s=%s", attr.c_str(), val.c_str());
167 LDAP->Compare(this, DN, attr, val);
170 catch (LDAPException &ex)
173 ServerInstance->SNO->WriteToSnoMask('c', "Unable to compare attributes %s=%s: %s", attr.c_str(), val.c_str(), ex.GetReason().c_str());
181 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (dn=%s) (unable to validate attributes)", user->GetFullRealHost().c_str(), DN.c_str());
182 ServerInstance->Users->QuitUser(user, killreason);
187 void OnError(const LDAPResult& err) CXX11_OVERRIDE
189 if (checkingAttributes && --attrCount)
198 User* user = ServerInstance->FindUUID(uid);
202 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (%s)", user->GetFullRealHost().c_str(), err.getError().c_str());
203 ServerInstance->Users->QuitUser(user, killreason);
210 class SearchInterface : public LDAPInterface
212 const std::string provider;
213 const std::string uid;
216 SearchInterface(Module* c, const std::string& p, const std::string& u)
217 : LDAPInterface(c), provider(p), uid(u)
221 void OnResult(const LDAPResult& r) CXX11_OVERRIDE
223 LocalUser* user = IS_LOCAL(ServerInstance->FindUUID(uid));
224 dynamic_reference<LDAPProvider> LDAP(me, provider);
225 if (!LDAP || r.empty() || !user)
228 ServerInstance->Users->QuitUser(user, killreason);
235 const LDAPAttributes& a = r.get(0);
236 std::string bindDn = a.get("dn");
239 ServerInstance->Users->QuitUser(user, killreason);
244 LDAP->Bind(new BindInterface(this->creator, provider, uid, bindDn), bindDn, user->password);
246 catch (LDAPException& ex)
248 ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: " + ex.GetReason());
253 void OnError(const LDAPResult& err) CXX11_OVERRIDE
255 ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: %s", err.getError().c_str());
256 User* user = ServerInstance->FindUUID(uid);
258 ServerInstance->Users->QuitUser(user, killreason);
263 class AdminBindInterface : public LDAPInterface
265 const std::string provider;
266 const std::string uuid;
267 const std::string base;
268 const std::string what;
271 AdminBindInterface(Module* c, const std::string& p, const std::string& u, const std::string& b, const std::string& w)
272 : LDAPInterface(c), provider(p), uuid(u), base(b), what(w)
276 void OnResult(const LDAPResult& r) CXX11_OVERRIDE
278 dynamic_reference<LDAPProvider> LDAP(me, provider);
283 LDAP->Search(new SearchInterface(this->creator, provider, uuid), base, what);
285 catch (LDAPException& ex)
287 ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: " + ex.GetReason());
293 void OnError(const LDAPResult& err) CXX11_OVERRIDE
295 ServerInstance->SNO->WriteToSnoMask('a', "Error binding as manager to LDAP server: " + err.getError());
300 class ModuleLDAPAuth : public Module
302 dynamic_reference<LDAPProvider> LDAP;
303 LocalIntExt ldapAuthed;
304 LocalStringExt ldapVhost;
306 std::string attribute;
307 std::vector<std::string> allowpatterns;
308 std::vector<std::string> whitelistedcidrs;
314 , ldapAuthed("ldapauth", ExtensionItem::EXT_USER, this)
315 , ldapVhost("ldapauth_vhost", ExtensionItem::EXT_USER, this)
318 authed = &ldapAuthed;
322 void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
324 ConfigTag* tag = ServerInstance->Config->ConfValue("ldapauth");
325 whitelistedcidrs.clear();
326 requiredattributes.clear();
328 base = tag->getString("baserdn");
329 attribute = tag->getString("attribute");
330 killreason = tag->getString("killreason");
331 vhost = tag->getString("host");
332 // Set to true if failed connects should be reported to operators
333 verbose = tag->getBool("verbose");
334 useusername = tag->getBool("userfield");
336 LDAP.SetProvider("LDAP/" + tag->getString("dbid"));
338 ConfigTagList whitelisttags = ServerInstance->Config->ConfTags("ldapwhitelist");
340 for (ConfigIter i = whitelisttags.first; i != whitelisttags.second; ++i)
342 std::string cidr = i->second->getString("cidr");
344 whitelistedcidrs.push_back(cidr);
348 ConfigTagList attributetags = ServerInstance->Config->ConfTags("ldaprequire");
350 for (ConfigIter i = attributetags.first; i != attributetags.second; ++i)
352 const std::string attr = i->second->getString("attribute");
353 const std::string val = i->second->getString("value");
355 if (!attr.empty() && !val.empty())
356 requiredattributes.push_back(make_pair(attr, val));
359 std::string allowpattern = tag->getString("allowpattern");
360 irc::spacesepstream ss(allowpattern);
361 for (std::string more; ss.GetToken(more); )
363 allowpatterns.push_back(more);
367 void OnUserConnect(LocalUser *user) CXX11_OVERRIDE
369 std::string* cc = ldapVhost.get(user);
372 user->ChangeDisplayedHost(*cc);
373 ldapVhost.unset(user);
377 ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE
379 for (std::vector<std::string>::const_iterator i = allowpatterns.begin(); i != allowpatterns.end(); ++i)
381 if (InspIRCd::Match(user->nick, *i))
383 ldapAuthed.set(user,1);
384 return MOD_RES_PASSTHRU;
388 for (std::vector<std::string>::iterator i = whitelistedcidrs.begin(); i != whitelistedcidrs.end(); i++)
390 if (InspIRCd::MatchCIDR(user->GetIPString(), *i, ascii_case_insensitive_map))
392 ldapAuthed.set(user,1);
393 return MOD_RES_PASSTHRU;
397 if (user->password.empty())
400 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (no password provided)", user->GetFullRealHost().c_str());
401 ServerInstance->Users->QuitUser(user, killreason);
408 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (unable to find LDAP provider)", user->GetFullRealHost().c_str());
409 ServerInstance->Users->QuitUser(user, killreason);
414 std::string::size_type pos = user->password.find(':');
415 if (pos != std::string::npos)
417 what = attribute + "=" + user->password.substr(0, pos);
419 // Trim the user: prefix, leaving just 'pass' for later password check
420 user->password = user->password.substr(pos + 1);
424 what = attribute + "=" + (useusername ? user->ident : user->nick);
429 LDAP->BindAsManager(new AdminBindInterface(this, LDAP.GetProvider(), user->uuid, base, what));
431 catch (LDAPException &ex)
433 ServerInstance->SNO->WriteToSnoMask('a', "LDAP exception: " + ex.GetReason());
434 ServerInstance->Users->QuitUser(user, killreason);
440 ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
442 return ldapAuthed.get(user) ? MOD_RES_PASSTHRU : MOD_RES_DENY;
445 Version GetVersion() CXX11_OVERRIDE
447 return Version("Allows connecting users to be authenticated against an LDAP database.", VF_VENDOR);
451 MODULE_INIT(ModuleLDAPAuth)