]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_ldapauth.cpp
ccffef17aa0e628ffb39b8b39797475272508743
[user/henk/code/inspircd.git] / src / modules / m_ldapauth.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2019 Sadie Powell <sadie@witchery.services>
5  *   Copyright (C) 2019 Robby <robby@chatbelgie.be>
6  *   Copyright (C) 2014-2015 Attila Molnar <attilamolnar@hush.com>
7  *   Copyright (C) 2014 Thiago Crepaldi <thiago@thiagocrepaldi.com>
8  *   Copyright (C) 2013-2014, 2017 Adam <Adam@anope.org>
9  *
10  * This file is part of InspIRCd.  InspIRCd is free software: you can
11  * redistribute it and/or modify it under the terms of the GNU General Public
12  * License as published by the Free Software Foundation, version 2.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22
23
24 #include "inspircd.h"
25 #include "modules/ldap.h"
26
27 namespace
28 {
29         Module* me;
30         std::string killreason;
31         LocalIntExt* authed;
32         bool verbose;
33         std::string vhost;
34         LocalStringExt* vhosts;
35         std::vector<std::pair<std::string, std::string> > requiredattributes;
36 }
37
38 class BindInterface : public LDAPInterface
39 {
40         const std::string provider;
41         const std::string uid;
42         std::string DN;
43         bool checkingAttributes;
44         bool passed;
45         int attrCount;
46
47         static std::string SafeReplace(const std::string& text, std::map<std::string, std::string>& replacements)
48         {
49                 std::string result;
50                 result.reserve(text.length());
51
52                 for (unsigned int i = 0; i < text.length(); ++i)
53                 {
54                         char c = text[i];
55                         if (c == '$')
56                         {
57                                 // find the first nonalpha
58                                 i++;
59                                 unsigned int start = i;
60
61                                 while (i < text.length() - 1 && isalpha(text[i + 1]))
62                                         ++i;
63
64                                 std::string key(text, start, (i - start) + 1);
65                                 result.append(replacements[key]);
66                         }
67                         else
68                                 result.push_back(c);
69                 }
70
71                 return result;
72         }
73
74         static void SetVHost(User* user, const std::string& DN)
75         {
76                 if (!vhost.empty())
77                 {
78                         irc::commasepstream stream(DN);
79
80                         // mashed map of key:value parts of the DN
81                         std::map<std::string, std::string> dnParts;
82
83                         std::string dnPart;
84                         while (stream.GetToken(dnPart))
85                         {
86                                 std::string::size_type pos = dnPart.find('=');
87                                 if (pos == std::string::npos) // malformed
88                                         continue;
89
90                                 std::string key(dnPart, 0, pos);
91                                 std::string value(dnPart, pos + 1, dnPart.length() - pos + 1); // +1s to skip the = itself
92                                 dnParts[key] = value;
93                         }
94
95                         // change host according to config key
96                         vhosts->set(user, SafeReplace(vhost, dnParts));
97                 }
98         }
99
100  public:
101         BindInterface(Module* c, const std::string& p, const std::string& u, const std::string& dn)
102                 : LDAPInterface(c)
103                 , provider(p), uid(u), DN(dn), checkingAttributes(false), passed(false), attrCount(0)
104         {
105         }
106
107         void OnResult(const LDAPResult& r) CXX11_OVERRIDE
108         {
109                 User* user = ServerInstance->FindUUID(uid);
110                 dynamic_reference<LDAPProvider> LDAP(me, provider);
111
112                 if (!user || !LDAP)
113                 {
114                         if (!checkingAttributes || !--attrCount)
115                                 delete this;
116                         return;
117                 }
118
119                 if (!checkingAttributes && requiredattributes.empty())
120                 {
121                         if (verbose)
122                                 ServerInstance->SNO->WriteToSnoMask('c', "Successful connection from %s (dn=%s)", user->GetFullRealHost().c_str(), DN.c_str());
123
124                         // We're done, there are no attributes to check
125                         SetVHost(user, DN);
126                         authed->set(user, 1);
127
128                         delete this;
129                         return;
130                 }
131
132                 // Already checked attributes?
133                 if (checkingAttributes)
134                 {
135                         if (!passed)
136                         {
137                                 // Only one has to pass
138                                 passed = true;
139
140                                 if (verbose)
141                                         ServerInstance->SNO->WriteToSnoMask('c', "Successful connection from %s (dn=%s)", user->GetFullRealHost().c_str(), DN.c_str());
142
143                                 SetVHost(user, DN);
144                                 authed->set(user, 1);
145                         }
146
147                         // Delete this if this is the last ref
148                         if (!--attrCount)
149                                 delete this;
150
151                         return;
152                 }
153
154                 // check required attributes
155                 checkingAttributes = true;
156
157                 for (std::vector<std::pair<std::string, std::string> >::const_iterator it = requiredattributes.begin(); it != requiredattributes.end(); ++it)
158                 {
159                         // Note that only one of these has to match for it to be success
160                         const std::string& attr = it->first;
161                         const std::string& val = it->second;
162
163                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "LDAP compare: %s=%s", attr.c_str(), val.c_str());
164                         try
165                         {
166                                 LDAP->Compare(this, DN, attr, val);
167                                 ++attrCount;
168                         }
169                         catch (LDAPException &ex)
170                         {
171                                 if (verbose)
172                                         ServerInstance->SNO->WriteToSnoMask('c', "Unable to compare attributes %s=%s: %s", attr.c_str(), val.c_str(), ex.GetReason().c_str());
173                         }
174                 }
175
176                 // Nothing done
177                 if (!attrCount)
178                 {
179                         if (verbose)
180                                 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (dn=%s) (unable to validate attributes)", user->GetFullRealHost().c_str(), DN.c_str());
181                         ServerInstance->Users->QuitUser(user, killreason);
182                         delete this;
183                 }
184         }
185
186         void OnError(const LDAPResult& err) CXX11_OVERRIDE
187         {
188                 if (checkingAttributes && --attrCount)
189                         return;
190
191                 if (passed)
192                 {
193                         delete this;
194                         return;
195                 }
196
197                 User* user = ServerInstance->FindUUID(uid);
198                 if (user)
199                 {
200                         if (verbose)
201                                 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (%s)", user->GetFullRealHost().c_str(), err.getError().c_str());
202                         ServerInstance->Users->QuitUser(user, killreason);
203                 }
204
205                 delete this;
206         }
207 };
208
209 class SearchInterface : public LDAPInterface
210 {
211         const std::string provider;
212         const std::string uid;
213
214  public:
215         SearchInterface(Module* c, const std::string& p, const std::string& u)
216                 : LDAPInterface(c), provider(p), uid(u)
217         {
218         }
219
220         void OnResult(const LDAPResult& r) CXX11_OVERRIDE
221         {
222                 LocalUser* user = IS_LOCAL(ServerInstance->FindUUID(uid));
223                 dynamic_reference<LDAPProvider> LDAP(me, provider);
224                 if (!LDAP || r.empty() || !user)
225                 {
226                         if (user)
227                                 ServerInstance->Users->QuitUser(user, killreason);
228                         delete this;
229                         return;
230                 }
231
232                 try
233                 {
234                         const LDAPAttributes& a = r.get(0);
235                         std::string bindDn = a.get("dn");
236                         if (bindDn.empty())
237                         {
238                                 ServerInstance->Users->QuitUser(user, killreason);
239                                 delete this;
240                                 return;
241                         }
242
243                         LDAP->Bind(new BindInterface(this->creator, provider, uid, bindDn), bindDn, user->password);
244                 }
245                 catch (LDAPException& ex)
246                 {
247                         ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: " + ex.GetReason());
248                 }
249                 delete this;
250         }
251
252         void OnError(const LDAPResult& err) CXX11_OVERRIDE
253         {
254                 ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: %s", err.getError().c_str());
255                 User* user = ServerInstance->FindUUID(uid);
256                 if (user)
257                         ServerInstance->Users->QuitUser(user, killreason);
258                 delete this;
259         }
260 };
261
262 class AdminBindInterface : public LDAPInterface
263 {
264         const std::string provider;
265         const std::string uuid;
266         const std::string base;
267         const std::string what;
268
269  public:
270         AdminBindInterface(Module* c, const std::string& p, const std::string& u, const std::string& b, const std::string& w)
271                 : LDAPInterface(c), provider(p), uuid(u), base(b), what(w)
272         {
273         }
274
275         void OnResult(const LDAPResult& r) CXX11_OVERRIDE
276         {
277                 dynamic_reference<LDAPProvider> LDAP(me, provider);
278                 if (LDAP)
279                 {
280                         try
281                         {
282                                 LDAP->Search(new SearchInterface(this->creator, provider, uuid), base, what);
283                         }
284                         catch (LDAPException& ex)
285                         {
286                                 ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: " + ex.GetReason());
287                         }
288                 }
289                 delete this;
290         }
291
292         void OnError(const LDAPResult& err) CXX11_OVERRIDE
293         {
294                 ServerInstance->SNO->WriteToSnoMask('a', "Error binding as manager to LDAP server: " + err.getError());
295                 delete this;
296         }
297 };
298
299 class ModuleLDAPAuth : public Module
300 {
301         dynamic_reference<LDAPProvider> LDAP;
302         LocalIntExt ldapAuthed;
303         LocalStringExt ldapVhost;
304         std::string base;
305         std::string attribute;
306         std::vector<std::string> allowpatterns;
307         std::vector<std::string> whitelistedcidrs;
308         bool useusername;
309
310 public:
311         ModuleLDAPAuth()
312                 : LDAP(this, "LDAP")
313                 , ldapAuthed("ldapauth", ExtensionItem::EXT_USER, this)
314                 , ldapVhost("ldapauth_vhost", ExtensionItem::EXT_USER, this)
315         {
316                 me = this;
317                 authed = &ldapAuthed;
318                 vhosts = &ldapVhost;
319         }
320
321         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
322         {
323                 ConfigTag* tag = ServerInstance->Config->ConfValue("ldapauth");
324                 whitelistedcidrs.clear();
325                 requiredattributes.clear();
326
327                 base                    = tag->getString("baserdn");
328                 attribute               = tag->getString("attribute");
329                 killreason              = tag->getString("killreason");
330                 vhost                   = tag->getString("host");
331                 // Set to true if failed connects should be reported to operators
332                 verbose                 = tag->getBool("verbose");
333                 useusername             = tag->getBool("userfield");
334
335                 LDAP.SetProvider("LDAP/" + tag->getString("dbid"));
336
337                 ConfigTagList whitelisttags = ServerInstance->Config->ConfTags("ldapwhitelist");
338
339                 for (ConfigIter i = whitelisttags.first; i != whitelisttags.second; ++i)
340                 {
341                         std::string cidr = i->second->getString("cidr");
342                         if (!cidr.empty()) {
343                                 whitelistedcidrs.push_back(cidr);
344                         }
345                 }
346
347                 ConfigTagList attributetags = ServerInstance->Config->ConfTags("ldaprequire");
348
349                 for (ConfigIter i = attributetags.first; i != attributetags.second; ++i)
350                 {
351                         const std::string attr = i->second->getString("attribute");
352                         const std::string val = i->second->getString("value");
353
354                         if (!attr.empty() && !val.empty())
355                                 requiredattributes.push_back(make_pair(attr, val));
356                 }
357
358                 std::string allowpattern = tag->getString("allowpattern");
359                 irc::spacesepstream ss(allowpattern);
360                 for (std::string more; ss.GetToken(more); )
361                 {
362                         allowpatterns.push_back(more);
363                 }
364         }
365
366         void OnUserConnect(LocalUser *user) CXX11_OVERRIDE
367         {
368                 std::string* cc = ldapVhost.get(user);
369                 if (cc)
370                 {
371                         user->ChangeDisplayedHost(*cc);
372                         ldapVhost.unset(user);
373                 }
374         }
375
376         ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE
377         {
378                 for (std::vector<std::string>::const_iterator i = allowpatterns.begin(); i != allowpatterns.end(); ++i)
379                 {
380                         if (InspIRCd::Match(user->nick, *i))
381                         {
382                                 ldapAuthed.set(user,1);
383                                 return MOD_RES_PASSTHRU;
384                         }
385                 }
386
387                 for (std::vector<std::string>::iterator i = whitelistedcidrs.begin(); i != whitelistedcidrs.end(); i++)
388                 {
389                         if (InspIRCd::MatchCIDR(user->GetIPString(), *i, ascii_case_insensitive_map))
390                         {
391                                 ldapAuthed.set(user,1);
392                                 return MOD_RES_PASSTHRU;
393                         }
394                 }
395
396                 if (user->password.empty())
397                 {
398                         if (verbose)
399                                 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (no password provided)", user->GetFullRealHost().c_str());
400                         ServerInstance->Users->QuitUser(user, killreason);
401                         return MOD_RES_DENY;
402                 }
403
404                 if (!LDAP)
405                 {
406                         if (verbose)
407                                 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (unable to find LDAP provider)", user->GetFullRealHost().c_str());
408                         ServerInstance->Users->QuitUser(user, killreason);
409                         return MOD_RES_DENY;
410                 }
411
412                 std::string what;
413                 std::string::size_type pos = user->password.find(':');
414                 if (pos != std::string::npos)
415                 {
416                         what = attribute + "=" + user->password.substr(0, pos);
417
418                         // Trim the user: prefix, leaving just 'pass' for later password check
419                         user->password = user->password.substr(pos + 1);
420                 }
421                 else
422                 {
423                         what = attribute + "=" + (useusername ? user->ident : user->nick);
424                 }
425
426                 try
427                 {
428                         LDAP->BindAsManager(new AdminBindInterface(this, LDAP.GetProvider(), user->uuid, base, what));
429                 }
430                 catch (LDAPException &ex)
431                 {
432                         ServerInstance->SNO->WriteToSnoMask('a', "LDAP exception: " + ex.GetReason());
433                         ServerInstance->Users->QuitUser(user, killreason);
434                 }
435
436                 return MOD_RES_DENY;
437         }
438
439         ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
440         {
441                 return ldapAuthed.get(user) ? MOD_RES_PASSTHRU : MOD_RES_DENY;
442         }
443
444         Version GetVersion() CXX11_OVERRIDE
445         {
446                 return Version("Allows connecting users to be authenticated against an LDAP database.", VF_VENDOR);
447         }
448 };
449
450 MODULE_INIT(ModuleLDAPAuth)