]> 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) 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                         // We're done, there are no attributes to check
122                         SetVHost(user, DN);
123                         authed->set(user, 1);
124
125                         delete this;
126                         return;
127                 }
128
129                 // Already checked attributes?
130                 if (checkingAttributes)
131                 {
132                         if (!passed)
133                         {
134                                 // Only one has to pass
135                                 passed = true;
136
137                                 SetVHost(user, DN);
138                                 authed->set(user, 1);
139                         }
140
141                         // Delete this if this is the last ref
142                         if (!--attrCount)
143                                 delete this;
144
145                         return;
146                 }
147
148                 // check required attributes
149                 checkingAttributes = true;
150
151                 for (std::vector<std::pair<std::string, std::string> >::const_iterator it = requiredattributes.begin(); it != requiredattributes.end(); ++it)
152                 {
153                         // Note that only one of these has to match for it to be success
154                         const std::string& attr = it->first;
155                         const std::string& val = it->second;
156
157                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "LDAP compare: %s=%s", attr.c_str(), val.c_str());
158                         try
159                         {
160                                 LDAP->Compare(this, DN, attr, val);
161                                 ++attrCount;
162                         }
163                         catch (LDAPException &ex)
164                         {
165                                 if (verbose)
166                                         ServerInstance->SNO->WriteToSnoMask('c', "Unable to compare attributes %s=%s: %s", attr.c_str(), val.c_str(), ex.GetReason().c_str());
167                         }
168                 }
169
170                 // Nothing done
171                 if (!attrCount)
172                 {
173                         if (verbose)
174                                 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (unable to validate attributes)", user->GetFullRealHost().c_str());
175                         ServerInstance->Users->QuitUser(user, killreason);
176                         delete this;
177                 }
178         }
179
180         void OnError(const LDAPResult& err) CXX11_OVERRIDE
181         {
182                 if (checkingAttributes && --attrCount)
183                         return;
184
185                 if (passed)
186                 {
187                         delete this;
188                         return;
189                 }
190
191                 User* user = ServerInstance->FindUUID(uid);
192                 if (user)
193                 {
194                         if (verbose)
195                                 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (%s)", user->GetFullRealHost().c_str(), err.getError().c_str());
196                         ServerInstance->Users->QuitUser(user, killreason);
197                 }
198
199                 delete this;
200         }
201 };
202
203 class SearchInterface : public LDAPInterface
204 {
205         const std::string provider;
206         const std::string uid;
207
208  public:
209         SearchInterface(Module* c, const std::string& p, const std::string& u)
210                 : LDAPInterface(c), provider(p), uid(u)
211         {
212         }
213
214         void OnResult(const LDAPResult& r) CXX11_OVERRIDE
215         {
216                 LocalUser* user = IS_LOCAL(ServerInstance->FindUUID(uid));
217                 dynamic_reference<LDAPProvider> LDAP(me, provider);
218                 if (!LDAP || r.empty() || !user)
219                 {
220                         if (user)
221                                 ServerInstance->Users->QuitUser(user, killreason);
222                         delete this;
223                         return;
224                 }
225
226                 try
227                 {
228                         const LDAPAttributes& a = r.get(0);
229                         std::string bindDn = a.get("dn");
230                         if (bindDn.empty())
231                         {
232                                 ServerInstance->Users->QuitUser(user, killreason);
233                                 delete this;
234                                 return;
235                         }
236
237                         LDAP->Bind(new BindInterface(this->creator, provider, uid, bindDn), bindDn, user->password);
238                 }
239                 catch (LDAPException& ex)
240                 {
241                         ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: " + ex.GetReason());
242                 }
243                 delete this;
244         }
245
246         void OnError(const LDAPResult& err) CXX11_OVERRIDE
247         {
248                 ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: %s", err.getError().c_str());
249                 User* user = ServerInstance->FindUUID(uid);
250                 if (user)
251                         ServerInstance->Users->QuitUser(user, killreason);
252                 delete this;
253         }
254 };
255
256 class AdminBindInterface : public LDAPInterface
257 {
258         const std::string provider;
259         const std::string uuid;
260         const std::string base;
261         const std::string what;
262
263  public:
264         AdminBindInterface(Module* c, const std::string& p, const std::string& u, const std::string& b, const std::string& w)
265                 : LDAPInterface(c), provider(p), uuid(u), base(b), what(w)
266         {
267         }
268
269         void OnResult(const LDAPResult& r) CXX11_OVERRIDE
270         {
271                 dynamic_reference<LDAPProvider> LDAP(me, provider);
272                 if (LDAP)
273                 {
274                         try
275                         {
276                                 LDAP->Search(new SearchInterface(this->creator, provider, uuid), base, what);
277                         }
278                         catch (LDAPException& ex)
279                         {
280                                 ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: " + ex.GetReason());
281                         }
282                 }
283                 delete this;
284         }
285
286         void OnError(const LDAPResult& err) CXX11_OVERRIDE
287         {
288                 ServerInstance->SNO->WriteToSnoMask('a', "Error binding as manager to LDAP server: " + err.getError());
289                 delete this;
290         }
291 };
292
293 class ModuleLDAPAuth : public Module
294 {
295         dynamic_reference<LDAPProvider> LDAP;
296         LocalIntExt ldapAuthed;
297         LocalStringExt ldapVhost;
298         std::string base;
299         std::string attribute;
300         std::vector<std::string> allowpatterns;
301         std::vector<std::string> whitelistedcidrs;
302         bool useusername;
303
304 public:
305         ModuleLDAPAuth()
306                 : LDAP(this, "LDAP")
307                 , ldapAuthed("ldapauth", ExtensionItem::EXT_USER, this)
308                 , ldapVhost("ldapauth_vhost", ExtensionItem::EXT_USER, this)
309         {
310                 me = this;
311                 authed = &ldapAuthed;
312                 vhosts = &ldapVhost;
313         }
314
315         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
316         {
317                 ConfigTag* tag = ServerInstance->Config->ConfValue("ldapauth");
318                 whitelistedcidrs.clear();
319                 requiredattributes.clear();
320
321                 base                    = tag->getString("baserdn");
322                 attribute               = tag->getString("attribute");
323                 killreason              = tag->getString("killreason");
324                 vhost                   = tag->getString("host");
325                 // Set to true if failed connects should be reported to operators
326                 verbose                 = tag->getBool("verbose");
327                 useusername             = tag->getBool("userfield");
328
329                 LDAP.SetProvider("LDAP/" + tag->getString("dbid"));
330
331                 ConfigTagList whitelisttags = ServerInstance->Config->ConfTags("ldapwhitelist");
332
333                 for (ConfigIter i = whitelisttags.first; i != whitelisttags.second; ++i)
334                 {
335                         std::string cidr = i->second->getString("cidr");
336                         if (!cidr.empty()) {
337                                 whitelistedcidrs.push_back(cidr);
338                         }
339                 }
340
341                 ConfigTagList attributetags = ServerInstance->Config->ConfTags("ldaprequire");
342
343                 for (ConfigIter i = attributetags.first; i != attributetags.second; ++i)
344                 {
345                         const std::string attr = i->second->getString("attribute");
346                         const std::string val = i->second->getString("value");
347
348                         if (!attr.empty() && !val.empty())
349                                 requiredattributes.push_back(make_pair(attr, val));
350                 }
351
352                 std::string allowpattern = tag->getString("allowpattern");
353                 irc::spacesepstream ss(allowpattern);
354                 for (std::string more; ss.GetToken(more); )
355                 {
356                         allowpatterns.push_back(more);
357                 }
358         }
359
360         void OnUserConnect(LocalUser *user) CXX11_OVERRIDE
361         {
362                 std::string* cc = ldapVhost.get(user);
363                 if (cc)
364                 {
365                         user->ChangeDisplayedHost(*cc);
366                         ldapVhost.unset(user);
367                 }
368         }
369
370         ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE
371         {
372                 for (std::vector<std::string>::const_iterator i = allowpatterns.begin(); i != allowpatterns.end(); ++i)
373                 {
374                         if (InspIRCd::Match(user->nick, *i))
375                         {
376                                 ldapAuthed.set(user,1);
377                                 return MOD_RES_PASSTHRU;
378                         }
379                 }
380
381                 for (std::vector<std::string>::iterator i = whitelistedcidrs.begin(); i != whitelistedcidrs.end(); i++)
382                 {
383                         if (InspIRCd::MatchCIDR(user->GetIPString(), *i, ascii_case_insensitive_map))
384                         {
385                                 ldapAuthed.set(user,1);
386                                 return MOD_RES_PASSTHRU;
387                         }
388                 }
389
390                 if (user->password.empty())
391                 {
392                         if (verbose)
393                                 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (no password provided)", user->GetFullRealHost().c_str());
394                         ServerInstance->Users->QuitUser(user, killreason);
395                         return MOD_RES_DENY;
396                 }
397
398                 if (!LDAP)
399                 {
400                         if (verbose)
401                                 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (unable to find LDAP provider)", user->GetFullRealHost().c_str());
402                         ServerInstance->Users->QuitUser(user, killreason);
403                         return MOD_RES_DENY;
404                 }
405
406                 std::string what;
407                 std::string::size_type pos = user->password.find(':');
408                 if (pos != std::string::npos)
409                 {
410                         what = attribute + "=" + user->password.substr(0, pos);
411
412                         // Trim the user: prefix, leaving just 'pass' for later password check
413                         user->password = user->password.substr(pos + 1);
414                 }
415                 else
416                 {
417                         what = attribute + "=" + (useusername ? user->ident : user->nick);
418                 }
419
420                 try
421                 {
422                         LDAP->BindAsManager(new AdminBindInterface(this, LDAP.GetProvider(), user->uuid, base, what));
423                 }
424                 catch (LDAPException &ex)
425                 {
426                         ServerInstance->SNO->WriteToSnoMask('a', "LDAP exception: " + ex.GetReason());
427                         ServerInstance->Users->QuitUser(user, killreason);
428                 }
429
430                 return MOD_RES_DENY;
431         }
432
433         ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
434         {
435                 return ldapAuthed.get(user) ? MOD_RES_PASSTHRU : MOD_RES_DENY;
436         }
437
438         Version GetVersion() CXX11_OVERRIDE
439         {
440                 return Version("Allow/deny connections based upon answers from an LDAP server", VF_VENDOR);
441         }
442 };
443
444 MODULE_INIT(ModuleLDAPAuth)