2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2013 Adam <Adam@anope.org>
5 * Copyright (C) 2011 Pierre Carrier <pierre@spotify.com>
6 * Copyright (C) 2009-2010 Robin Burchell <robin+git@viroteck.net>
7 * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
8 * Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com>
9 * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc>
10 * Copyright (C) 2008 Dennis Friis <peavey@inspircd.org>
11 * Copyright (C) 2007 Carsten Valdemar Munk <carsten.munk+inspircd@gmail.com>
13 * This file is part of InspIRCd. InspIRCd is free software: you can
14 * redistribute it and/or modify it under the terms of the GNU General Public
15 * License as published by the Free Software Foundation, version 2.
17 * This program is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
22 * You should have received a copy of the GNU General Public License
23 * along with this program. If not, see <http://www.gnu.org/licenses/>.
28 #include "modules/ldap.h"
33 std::string killreason;
37 LocalStringExt* vhosts;
38 std::vector<std::pair<std::string, std::string> > requiredattributes;
41 class BindInterface : public LDAPInterface
43 const std::string provider;
44 const std::string uid;
46 bool checkingAttributes;
50 static std::string SafeReplace(const std::string& text, std::map<std::string, std::string>& replacements)
53 result.reserve(text.length());
55 for (unsigned int i = 0; i < text.length(); ++i)
60 // find the first nonalpha
62 unsigned int start = i;
64 while (i < text.length() - 1 && isalpha(text[i + 1]))
67 std::string key(text, start, (i - start) + 1);
68 result.append(replacements[key]);
77 static void SetVHost(User* user, const std::string& DN)
81 irc::commasepstream stream(DN);
83 // mashed map of key:value parts of the DN
84 std::map<std::string, std::string> dnParts;
87 while (stream.GetToken(dnPart))
89 std::string::size_type pos = dnPart.find('=');
90 if (pos == std::string::npos) // malformed
93 std::string key(dnPart, 0, pos);
94 std::string value(dnPart, pos + 1, dnPart.length() - pos + 1); // +1s to skip the = itself
98 // change host according to config key
99 vhosts->set(user, SafeReplace(vhost, dnParts));
104 BindInterface(Module* c, const std::string& p, const std::string& u, const std::string& dn)
106 , provider(p), uid(u), DN(dn), checkingAttributes(false), passed(false), attrCount(0)
110 void OnResult(const LDAPResult& r) CXX11_OVERRIDE
112 User* user = ServerInstance->FindUUID(uid);
113 dynamic_reference<LDAPProvider> LDAP(me, provider);
117 if (!checkingAttributes || !--attrCount)
122 if (!checkingAttributes && requiredattributes.empty())
124 // We're done, there are no attributes to check
126 authed->set(user, 1);
132 // Already checked attributes?
133 if (checkingAttributes)
137 // Only one has to pass
141 authed->set(user, 1);
144 // Delete this if this is the last ref
151 // check required attributes
152 checkingAttributes = true;
154 for (std::vector<std::pair<std::string, std::string> >::const_iterator it = requiredattributes.begin(); it != requiredattributes.end(); ++it)
156 // Note that only one of these has to match for it to be success
157 const std::string& attr = it->first;
158 const std::string& val = it->second;
160 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "LDAP compare: %s=%s", attr.c_str(), val.c_str());
163 LDAP->Compare(this, DN, attr, val);
166 catch (LDAPException &ex)
169 ServerInstance->SNO->WriteToSnoMask('c', "Unable to compare attributes %s=%s: %s", attr.c_str(), val.c_str(), ex.GetReason().c_str());
177 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (unable to validate attributes)", user->GetFullRealHost().c_str());
178 ServerInstance->Users->QuitUser(user, killreason);
183 void OnError(const LDAPResult& err) CXX11_OVERRIDE
185 if (checkingAttributes && --attrCount)
194 User* user = ServerInstance->FindUUID(uid);
198 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (%s)", user->GetFullRealHost().c_str(), err.getError().c_str());
199 ServerInstance->Users->QuitUser(user, killreason);
206 class SearchInterface : public LDAPInterface
208 const std::string provider;
209 const std::string uid;
212 SearchInterface(Module* c, const std::string& p, const std::string& u)
213 : LDAPInterface(c), provider(p), uid(u)
217 void OnResult(const LDAPResult& r) CXX11_OVERRIDE
219 LocalUser* user = static_cast<LocalUser*>(ServerInstance->FindUUID(uid));
220 dynamic_reference<LDAPProvider> LDAP(me, provider);
221 if (!LDAP || r.empty() || !user)
224 ServerInstance->Users->QuitUser(user, killreason);
231 const LDAPAttributes& a = r.get(0);
232 std::string bindDn = a.get("dn");
235 ServerInstance->Users->QuitUser(user, killreason);
240 LDAP->Bind(new BindInterface(this->creator, provider, uid, bindDn), bindDn, user->password);
242 catch (LDAPException& ex)
244 ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: " + ex.GetReason());
249 void OnError(const LDAPResult& err) CXX11_OVERRIDE
251 ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: %s", err.getError().c_str());
252 User* user = ServerInstance->FindUUID(uid);
254 ServerInstance->Users->QuitUser(user, killreason);
259 class AdminBindInterface : public LDAPInterface
261 const std::string provider;
262 const std::string uuid;
263 const std::string base;
264 const std::string what;
267 AdminBindInterface(Module* c, const std::string& p, const std::string& u, const std::string& b, const std::string& w)
268 : LDAPInterface(c), provider(p), uuid(u), base(b), what(w)
272 void OnResult(const LDAPResult& r) CXX11_OVERRIDE
274 dynamic_reference<LDAPProvider> LDAP(me, provider);
279 LDAP->Search(new SearchInterface(this->creator, provider, uuid), base, what);
281 catch (LDAPException& ex)
283 ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: " + ex.GetReason());
289 void OnError(const LDAPResult& err) CXX11_OVERRIDE
291 ServerInstance->SNO->WriteToSnoMask('a', "Error binding as manager to LDAP server: " + err.getError());
296 class ModuleLDAPAuth : public Module
298 dynamic_reference<LDAPProvider> LDAP;
299 LocalIntExt ldapAuthed;
300 LocalStringExt ldapVhost;
302 std::string attribute;
303 std::vector<std::string> allowpatterns;
304 std::vector<std::string> whitelistedcidrs;
310 , ldapAuthed("ldapauth", ExtensionItem::EXT_USER, this)
311 , ldapVhost("ldapauth_vhost", ExtensionItem::EXT_USER, this)
314 authed = &ldapAuthed;
318 void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
320 ConfigTag* tag = ServerInstance->Config->ConfValue("ldapauth");
321 whitelistedcidrs.clear();
322 requiredattributes.clear();
324 base = tag->getString("baserdn");
325 attribute = tag->getString("attribute");
326 killreason = tag->getString("killreason");
327 vhost = tag->getString("host");
328 // Set to true if failed connects should be reported to operators
329 verbose = tag->getBool("verbose");
330 useusername = tag->getBool("userfield");
332 LDAP.SetProvider("LDAP/" + tag->getString("dbid"));
334 ConfigTagList whitelisttags = ServerInstance->Config->ConfTags("ldapwhitelist");
336 for (ConfigIter i = whitelisttags.first; i != whitelisttags.second; ++i)
338 std::string cidr = i->second->getString("cidr");
340 whitelistedcidrs.push_back(cidr);
344 ConfigTagList attributetags = ServerInstance->Config->ConfTags("ldaprequire");
346 for (ConfigIter i = attributetags.first; i != attributetags.second; ++i)
348 const std::string attr = i->second->getString("attribute");
349 const std::string val = i->second->getString("value");
351 if (!attr.empty() && !val.empty())
352 requiredattributes.push_back(make_pair(attr, val));
355 std::string allowpattern = tag->getString("allowpattern");
356 irc::spacesepstream ss(allowpattern);
357 for (std::string more; ss.GetToken(more); )
359 allowpatterns.push_back(more);
363 void OnUserConnect(LocalUser *user) CXX11_OVERRIDE
365 std::string* cc = ldapVhost.get(user);
368 user->ChangeDisplayedHost(*cc);
369 ldapVhost.unset(user);
373 ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE
375 for (std::vector<std::string>::const_iterator i = allowpatterns.begin(); i != allowpatterns.end(); ++i)
377 if (InspIRCd::Match(user->nick, *i))
379 ldapAuthed.set(user,1);
380 return MOD_RES_PASSTHRU;
384 for (std::vector<std::string>::iterator i = whitelistedcidrs.begin(); i != whitelistedcidrs.end(); i++)
386 if (InspIRCd::MatchCIDR(user->GetIPString(), *i, ascii_case_insensitive_map))
388 ldapAuthed.set(user,1);
389 return MOD_RES_PASSTHRU;
393 if (user->password.empty())
396 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (No password provided)", user->GetFullRealHost().c_str());
397 ServerInstance->Users->QuitUser(user, killreason);
404 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (Unable to find LDAP provider)", user->GetFullRealHost().c_str());
405 ServerInstance->Users->QuitUser(user, killreason);
410 std::string::size_type pos = user->password.find(':');
411 if (pos != std::string::npos)
413 what = attribute + "=" + user->password.substr(0, pos);
415 // Trim the user: prefix, leaving just 'pass' for later password check
416 user->password = user->password.substr(pos + 1);
420 what = attribute + "=" + (useusername ? user->ident : user->nick);
425 LDAP->BindAsManager(new AdminBindInterface(this, LDAP.GetProvider(), user->uuid, base, what));
427 catch (LDAPException &ex)
429 ServerInstance->SNO->WriteToSnoMask('a', "LDAP exception: " + ex.GetReason());
430 ServerInstance->Users->QuitUser(user, killreason);
436 ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
438 return ldapAuthed.get(user) ? MOD_RES_PASSTHRU : MOD_RES_DENY;
441 Version GetVersion() CXX11_OVERRIDE
443 return Version("Allow/Deny connections based upon answer from LDAP server", VF_VENDOR);
447 MODULE_INIT(ModuleLDAPAuth)