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