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