X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Fmodules%2Fm_ldapauth.cpp;h=8fc0ca0b01f9639b2c5b2e0e525cc18fc46b1c37;hb=3151d60c1ecc9462e4c335282ee6c31672f45111;hp=fb1eea70097dc2d8d5247715e48bf47e0db6eed9;hpb=9c6c3755833a058242124fdddf4bc8931748c166;p=user%2Fhenk%2Fcode%2Finspircd.git diff --git a/src/modules/m_ldapauth.cpp b/src/modules/m_ldapauth.cpp deleted file mode 120000 index fb1eea700..000000000 --- a/src/modules/m_ldapauth.cpp +++ /dev/null @@ -1 +0,0 @@ -extra/m_ldapauth.cpp \ No newline at end of file diff --git a/src/modules/m_ldapauth.cpp b/src/modules/m_ldapauth.cpp new file mode 100644 index 000000000..8fc0ca0b0 --- /dev/null +++ b/src/modules/m_ldapauth.cpp @@ -0,0 +1,451 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2020 Joel Sing + * Copyright (C) 2019 Sadie Powell + * Copyright (C) 2019 Robby + * Copyright (C) 2014-2015 Attila Molnar + * Copyright (C) 2014 Thiago Crepaldi + * Copyright (C) 2013-2014, 2017 Adam + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include "inspircd.h" +#include "modules/ldap.h" + +namespace +{ + Module* me; + std::string killreason; + LocalIntExt* authed; + bool verbose; + std::string vhost; + LocalStringExt* vhosts; + std::vector > requiredattributes; +} + +class BindInterface : public LDAPInterface +{ + const std::string provider; + const std::string uid; + std::string DN; + bool checkingAttributes; + bool passed; + int attrCount; + + static std::string SafeReplace(const std::string& text, std::map& replacements) + { + std::string result; + result.reserve(text.length()); + + for (unsigned int i = 0; i < text.length(); ++i) + { + char c = text[i]; + if (c == '$') + { + // find the first nonalpha + i++; + unsigned int start = i; + + while (i < text.length() - 1 && isalpha(text[i + 1])) + ++i; + + std::string key(text, start, (i - start) + 1); + result.append(replacements[key]); + } + else + result.push_back(c); + } + + return result; + } + + static void SetVHost(User* user, const std::string& DN) + { + if (!vhost.empty()) + { + irc::commasepstream stream(DN); + + // mashed map of key:value parts of the DN + std::map dnParts; + + std::string dnPart; + while (stream.GetToken(dnPart)) + { + std::string::size_type pos = dnPart.find('='); + if (pos == std::string::npos) // malformed + continue; + + std::string key(dnPart, 0, pos); + std::string value(dnPart, pos + 1, dnPart.length() - pos + 1); // +1s to skip the = itself + dnParts[key] = value; + } + + // change host according to config key + vhosts->set(user, SafeReplace(vhost, dnParts)); + } + } + + public: + BindInterface(Module* c, const std::string& p, const std::string& u, const std::string& dn) + : LDAPInterface(c) + , provider(p), uid(u), DN(dn), checkingAttributes(false), passed(false), attrCount(0) + { + } + + void OnResult(const LDAPResult& r) CXX11_OVERRIDE + { + User* user = ServerInstance->FindUUID(uid); + dynamic_reference LDAP(me, provider); + + if (!user || !LDAP) + { + if (!checkingAttributes || !--attrCount) + delete this; + return; + } + + if (!checkingAttributes && requiredattributes.empty()) + { + if (verbose) + ServerInstance->SNO->WriteToSnoMask('c', "Successful connection from %s (dn=%s)", user->GetFullRealHost().c_str(), DN.c_str()); + + // We're done, there are no attributes to check + SetVHost(user, DN); + authed->set(user, 1); + + delete this; + return; + } + + // Already checked attributes? + if (checkingAttributes) + { + if (!passed) + { + // Only one has to pass + passed = true; + + if (verbose) + ServerInstance->SNO->WriteToSnoMask('c', "Successful connection from %s (dn=%s)", user->GetFullRealHost().c_str(), DN.c_str()); + + SetVHost(user, DN); + authed->set(user, 1); + } + + // Delete this if this is the last ref + if (!--attrCount) + delete this; + + return; + } + + // check required attributes + checkingAttributes = true; + + for (std::vector >::const_iterator it = requiredattributes.begin(); it != requiredattributes.end(); ++it) + { + // Note that only one of these has to match for it to be success + const std::string& attr = it->first; + const std::string& val = it->second; + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "LDAP compare: %s=%s", attr.c_str(), val.c_str()); + try + { + LDAP->Compare(this, DN, attr, val); + ++attrCount; + } + catch (LDAPException &ex) + { + if (verbose) + ServerInstance->SNO->WriteToSnoMask('c', "Unable to compare attributes %s=%s: %s", attr.c_str(), val.c_str(), ex.GetReason().c_str()); + } + } + + // Nothing done + if (!attrCount) + { + if (verbose) + ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (dn=%s) (unable to validate attributes)", user->GetFullRealHost().c_str(), DN.c_str()); + ServerInstance->Users->QuitUser(user, killreason); + delete this; + } + } + + void OnError(const LDAPResult& err) CXX11_OVERRIDE + { + if (checkingAttributes && --attrCount) + return; + + if (passed) + { + delete this; + return; + } + + User* user = ServerInstance->FindUUID(uid); + if (user) + { + if (verbose) + ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (%s)", user->GetFullRealHost().c_str(), err.getError().c_str()); + ServerInstance->Users->QuitUser(user, killreason); + } + + delete this; + } +}; + +class SearchInterface : public LDAPInterface +{ + const std::string provider; + const std::string uid; + + public: + SearchInterface(Module* c, const std::string& p, const std::string& u) + : LDAPInterface(c), provider(p), uid(u) + { + } + + void OnResult(const LDAPResult& r) CXX11_OVERRIDE + { + LocalUser* user = IS_LOCAL(ServerInstance->FindUUID(uid)); + dynamic_reference LDAP(me, provider); + if (!LDAP || r.empty() || !user) + { + if (user) + ServerInstance->Users->QuitUser(user, killreason); + delete this; + return; + } + + try + { + const LDAPAttributes& a = r.get(0); + std::string bindDn = a.get("dn"); + if (bindDn.empty()) + { + ServerInstance->Users->QuitUser(user, killreason); + delete this; + return; + } + + LDAP->Bind(new BindInterface(this->creator, provider, uid, bindDn), bindDn, user->password); + } + catch (LDAPException& ex) + { + ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: " + ex.GetReason()); + } + delete this; + } + + void OnError(const LDAPResult& err) CXX11_OVERRIDE + { + ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: %s", err.getError().c_str()); + User* user = ServerInstance->FindUUID(uid); + if (user) + ServerInstance->Users->QuitUser(user, killreason); + delete this; + } +}; + +class AdminBindInterface : public LDAPInterface +{ + const std::string provider; + const std::string uuid; + const std::string base; + const std::string what; + + public: + AdminBindInterface(Module* c, const std::string& p, const std::string& u, const std::string& b, const std::string& w) + : LDAPInterface(c), provider(p), uuid(u), base(b), what(w) + { + } + + void OnResult(const LDAPResult& r) CXX11_OVERRIDE + { + dynamic_reference LDAP(me, provider); + if (LDAP) + { + try + { + LDAP->Search(new SearchInterface(this->creator, provider, uuid), base, what); + } + catch (LDAPException& ex) + { + ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: " + ex.GetReason()); + } + } + delete this; + } + + void OnError(const LDAPResult& err) CXX11_OVERRIDE + { + ServerInstance->SNO->WriteToSnoMask('a', "Error binding as manager to LDAP server: " + err.getError()); + delete this; + } +}; + +class ModuleLDAPAuth : public Module +{ + dynamic_reference LDAP; + LocalIntExt ldapAuthed; + LocalStringExt ldapVhost; + std::string base; + std::string attribute; + std::vector allowpatterns; + std::vector whitelistedcidrs; + bool useusername; + +public: + ModuleLDAPAuth() + : LDAP(this, "LDAP") + , ldapAuthed("ldapauth", ExtensionItem::EXT_USER, this) + , ldapVhost("ldapauth_vhost", ExtensionItem::EXT_USER, this) + { + me = this; + authed = &ldapAuthed; + vhosts = &ldapVhost; + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + ConfigTag* tag = ServerInstance->Config->ConfValue("ldapauth"); + whitelistedcidrs.clear(); + requiredattributes.clear(); + + base = tag->getString("baserdn"); + attribute = tag->getString("attribute"); + killreason = tag->getString("killreason"); + vhost = tag->getString("host"); + // Set to true if failed connects should be reported to operators + verbose = tag->getBool("verbose"); + useusername = tag->getBool("userfield"); + + LDAP.SetProvider("LDAP/" + tag->getString("dbid")); + + ConfigTagList whitelisttags = ServerInstance->Config->ConfTags("ldapwhitelist"); + + for (ConfigIter i = whitelisttags.first; i != whitelisttags.second; ++i) + { + std::string cidr = i->second->getString("cidr"); + if (!cidr.empty()) { + whitelistedcidrs.push_back(cidr); + } + } + + ConfigTagList attributetags = ServerInstance->Config->ConfTags("ldaprequire"); + + for (ConfigIter i = attributetags.first; i != attributetags.second; ++i) + { + const std::string attr = i->second->getString("attribute"); + const std::string val = i->second->getString("value"); + + if (!attr.empty() && !val.empty()) + requiredattributes.push_back(make_pair(attr, val)); + } + + std::string allowpattern = tag->getString("allowpattern"); + irc::spacesepstream ss(allowpattern); + for (std::string more; ss.GetToken(more); ) + { + allowpatterns.push_back(more); + } + } + + void OnUserConnect(LocalUser *user) CXX11_OVERRIDE + { + std::string* cc = ldapVhost.get(user); + if (cc) + { + user->ChangeDisplayedHost(*cc); + ldapVhost.unset(user); + } + } + + ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE + { + for (std::vector::const_iterator i = allowpatterns.begin(); i != allowpatterns.end(); ++i) + { + if (InspIRCd::Match(user->nick, *i)) + { + ldapAuthed.set(user,1); + return MOD_RES_PASSTHRU; + } + } + + for (std::vector::iterator i = whitelistedcidrs.begin(); i != whitelistedcidrs.end(); i++) + { + if (InspIRCd::MatchCIDR(user->GetIPString(), *i, ascii_case_insensitive_map)) + { + ldapAuthed.set(user,1); + return MOD_RES_PASSTHRU; + } + } + + if (user->password.empty()) + { + if (verbose) + ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (no password provided)", user->GetFullRealHost().c_str()); + ServerInstance->Users->QuitUser(user, killreason); + return MOD_RES_DENY; + } + + if (!LDAP) + { + if (verbose) + ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (unable to find LDAP provider)", user->GetFullRealHost().c_str()); + ServerInstance->Users->QuitUser(user, killreason); + return MOD_RES_DENY; + } + + std::string what; + std::string::size_type pos = user->password.find(':'); + if (pos != std::string::npos) + { + what = attribute + "=" + user->password.substr(0, pos); + + // Trim the user: prefix, leaving just 'pass' for later password check + user->password = user->password.substr(pos + 1); + } + else + { + what = attribute + "=" + (useusername ? user->ident : user->nick); + } + + try + { + LDAP->BindAsManager(new AdminBindInterface(this, LDAP.GetProvider(), user->uuid, base, what)); + } + catch (LDAPException &ex) + { + ServerInstance->SNO->WriteToSnoMask('a', "LDAP exception: " + ex.GetReason()); + ServerInstance->Users->QuitUser(user, killreason); + } + + return MOD_RES_DENY; + } + + ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE + { + return ldapAuthed.get(user) ? MOD_RES_PASSTHRU : MOD_RES_DENY; + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Allows connecting users to be authenticated against an LDAP database.", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleLDAPAuth)