]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_ldapauth.cpp
f404471ea07b4816d904b36616deda6d9841b4a5
[user/henk/code/inspircd.git] / src / modules / extra / m_ldapauth.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2011 Pierre Carrier <pierre@spotify.com>
5  *   Copyright (C) 2009-2010 Robin Burchell <robin+git@viroteck.net>
6  *   Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
7  *   Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com>
8  *   Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc>
9  *   Copyright (C) 2008 Dennis Friis <peavey@inspircd.org>
10  *   Copyright (C) 2007 Carsten Valdemar Munk <carsten.munk+inspircd@gmail.com>
11  *
12  * This file is part of InspIRCd.  InspIRCd is free software: you can
13  * redistribute it and/or modify it under the terms of the GNU General Public
14  * License as published by the Free Software Foundation, version 2.
15  *
16  * This program is distributed in the hope that it will be useful, but WITHOUT
17  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
19  * details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24
25
26 #include "inspircd.h"
27 #include "users.h"
28 #include "channels.h"
29 #include "modules.h"
30
31 #include <ldap.h>
32
33 #ifdef _WIN32
34 # pragma comment(lib, "ldap.lib")
35 # pragma comment(lib, "lber.lib")
36 #endif
37
38 /* $ModDesc: Allow/Deny connections based upon answer from LDAP server */
39 /* $LinkerFlags: -lldap */
40
41 class ModuleLDAPAuth : public Module
42 {
43         LocalIntExt ldapAuthed;
44         std::string base;
45         std::string attribute;
46         std::string ldapserver;
47         std::string allowpattern;
48         std::string killreason;
49         std::string username;
50         std::string password;
51         std::vector<std::string> whitelistedcidrs;
52         std::vector<std::pair<std::string, std::string> > requiredattributes;
53         int searchscope;
54         bool verbose;
55         bool useusername;
56         LDAP *conn;
57
58 public:
59         ModuleLDAPAuth() : ldapAuthed("ldapauth", this)
60         {
61                 conn = NULL;
62         }
63
64         void init()
65         {
66                 Implementation eventlist[] = { I_OnCheckReady, I_OnRehash, I_OnUserRegister };
67                 ServerInstance->Modules->Attach(eventlist, this, 3);
68                 OnRehash(NULL);
69         }
70
71         ~ModuleLDAPAuth()
72         {
73                 if (conn)
74                         ldap_unbind_ext(conn, NULL, NULL);
75         }
76
77         void OnRehash(User* user)
78         {
79                 ConfigReader Conf;
80                 whitelistedcidrs.clear();
81                 requiredattributes.clear();
82
83                 base                    = Conf.ReadValue("ldapauth", "baserdn", 0);
84                 attribute               = Conf.ReadValue("ldapauth", "attribute", 0);
85                 ldapserver              = Conf.ReadValue("ldapauth", "server", 0);
86                 allowpattern            = Conf.ReadValue("ldapauth", "allowpattern", 0);
87                 killreason              = Conf.ReadValue("ldapauth", "killreason", 0);
88                 std::string scope       = Conf.ReadValue("ldapauth", "searchscope", 0);
89                 username                = Conf.ReadValue("ldapauth", "binddn", 0);
90                 password                = Conf.ReadValue("ldapauth", "bindauth", 0);
91                 verbose                 = Conf.ReadFlag("ldapauth", "verbose", 0);              /* Set to true if failed connects should be reported to operators */
92                 useusername             = Conf.ReadFlag("ldapauth", "userfield", 0);
93
94                 ConfigTagList whitelisttags = ServerInstance->Config->ConfTags("ldapwhitelist");
95
96                 for (ConfigIter i = whitelisttags.first; i != whitelisttags.second; ++i)
97                 {
98                         std::string cidr = i->second->getString("cidr");
99                         if (!cidr.empty()) {
100                                 whitelistedcidrs.push_back(cidr);
101                         }
102                 }
103
104                 ConfigTagList attributetags = ServerInstance->Config->ConfTags("ldaprequire");
105
106                 for (ConfigIter i = attributetags.first; i != attributetags.second; ++i)
107                 {
108                         const std::string attr = i->second->getString("attribute");
109                         const std::string val = i->second->getString("value");
110
111                         if (!attr.empty() && !val.empty())
112                                 requiredattributes.push_back(make_pair(attr, val));
113                 }
114
115                 if (scope == "base")
116                         searchscope = LDAP_SCOPE_BASE;
117                 else if (scope == "onelevel")
118                         searchscope = LDAP_SCOPE_ONELEVEL;
119                 else searchscope = LDAP_SCOPE_SUBTREE;
120
121                 Connect();
122         }
123
124         bool Connect()
125         {
126                 if (conn != NULL)
127                         ldap_unbind_ext(conn, NULL, NULL);
128                 int res, v = LDAP_VERSION3;
129                 res = ldap_initialize(&conn, ldapserver.c_str());
130                 if (res != LDAP_SUCCESS)
131                 {
132                         if (verbose)
133                                 ServerInstance->SNO->WriteToSnoMask('c', "LDAP connection failed: %s", ldap_err2string(res));
134                         conn = NULL;
135                         return false;
136                 }
137
138                 res = ldap_set_option(conn, LDAP_OPT_PROTOCOL_VERSION, (void *)&v);
139                 if (res != LDAP_SUCCESS)
140                 {
141                         if (verbose)
142                                 ServerInstance->SNO->WriteToSnoMask('c', "LDAP set protocol to v3 failed: %s", ldap_err2string(res));
143                         ldap_unbind_ext(conn, NULL, NULL);
144                         conn = NULL;
145                         return false;
146                 }
147                 return true;
148         }
149
150         ModResult OnUserRegister(LocalUser* user)
151         {
152                 if ((!allowpattern.empty()) && (InspIRCd::Match(user->nick,allowpattern)))
153                 {
154                         ldapAuthed.set(user,1);
155                         return MOD_RES_PASSTHRU;
156                 }
157
158                 for (std::vector<std::string>::iterator i = whitelistedcidrs.begin(); i != whitelistedcidrs.end(); i++)
159                 {
160                         if (InspIRCd::MatchCIDR(user->GetIPString(), *i, ascii_case_insensitive_map))
161                         {
162                                 ldapAuthed.set(user,1);
163                                 return MOD_RES_PASSTHRU;
164                         }
165                 }
166
167                 if (!CheckCredentials(user))
168                 {
169                         ServerInstance->Users->QuitUser(user, killreason);
170                         return MOD_RES_DENY;
171                 }
172                 return MOD_RES_PASSTHRU;
173         }
174
175         bool CheckCredentials(LocalUser* user)
176         {
177                 if (conn == NULL)
178                         if (!Connect())
179                                 return false;
180
181                 if (user->password.empty())
182                 {
183                         if (verbose)
184                                 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s!%s@%s (No password provided)", user->nick.c_str(), user->ident.c_str(), user->host.c_str());
185                         return false;
186                 }
187
188                 int res;
189                 // bind anonymously if no bind DN and authentication are given in the config
190                 struct berval cred;
191                 cred.bv_val = const_cast<char*>(password.c_str());
192                 cred.bv_len = password.length();
193
194                 if ((res = ldap_sasl_bind_s(conn, username.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL)) != LDAP_SUCCESS)
195                 {
196                         if (res == LDAP_SERVER_DOWN)
197                         {
198                                 // Attempt to reconnect if the connection dropped
199                                 if (verbose)
200                                         ServerInstance->SNO->WriteToSnoMask('a', "LDAP server has gone away - reconnecting...");
201                                 Connect();
202                                 res = ldap_sasl_bind_s(conn, username.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL);
203                         }
204
205                         if (res != LDAP_SUCCESS)
206                         {
207                                 if (verbose)
208                                         ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s!%s@%s (LDAP bind failed: %s)", user->nick.c_str(), user->ident.c_str(), user->host.c_str(), ldap_err2string(res));
209                                 ldap_unbind_ext(conn, NULL, NULL);
210                                 conn = NULL;
211                                 return false;
212                         }
213                 }
214
215                 LDAPMessage *msg, *entry;
216                 std::string what = (attribute + "=" + (useusername ? user->ident : user->nick));
217                 if ((res = ldap_search_ext_s(conn, base.c_str(), searchscope, what.c_str(), NULL, 0, NULL, NULL, NULL, 0, &msg)) != LDAP_SUCCESS)
218                 {
219                         // Do a second search, based on password, if it contains a :
220                         // That is, PASS <user>:<password> will work.
221                         size_t pos = user->password.find(":");
222                         if (pos != std::string::npos)
223                         {
224                                 std::string cutpassword = user->password.substr(0, pos);
225                                 res = ldap_search_ext_s(conn, base.c_str(), searchscope, cutpassword.c_str(), NULL, 0, NULL, NULL, NULL, 0, &msg);
226
227                                 if (res == LDAP_SUCCESS)
228                                 {
229                                         // Trim the user: prefix, leaving just 'pass' for later password check
230                                         user->password = user->password.substr(pos + 1);
231                                 }
232                         }
233
234                         // It may have found based on user:pass check above.
235                         if (res != LDAP_SUCCESS)
236                         {
237                                 if (verbose)
238                                         ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s!%s@%s (LDAP search failed: %s)", user->nick.c_str(), user->ident.c_str(), user->host.c_str(), ldap_err2string(res));
239                                 return false;
240                         }
241                 }
242                 if (ldap_count_entries(conn, msg) > 1)
243                 {
244                         if (verbose)
245                                 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s!%s@%s (LDAP search returned more than one result: %s)", user->nick.c_str(), user->ident.c_str(), user->host.c_str(), ldap_err2string(res));
246                         ldap_msgfree(msg);
247                         return false;
248                 }
249                 if ((entry = ldap_first_entry(conn, msg)) == NULL)
250                 {
251                         if (verbose)
252                                 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s!%s@%s (LDAP search returned no results: %s)", user->nick.c_str(), user->ident.c_str(), user->host.c_str(), ldap_err2string(res));
253                         ldap_msgfree(msg);
254                         return false;
255                 }
256                 cred.bv_val = (char*)user->password.data();
257                 cred.bv_len = user->password.length();
258                 if ((res = ldap_sasl_bind_s(conn, ldap_get_dn(conn, entry), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL)) != LDAP_SUCCESS)
259                 {
260                         if (verbose)
261                                 ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s!%s@%s (%s)", user->nick.c_str(), user->ident.c_str(), user->host.c_str(), ldap_err2string(res));
262                         ldap_msgfree(msg);
263                         return false;
264                 }
265
266                 if (requiredattributes.empty())
267                 {
268                         ldap_msgfree(msg);
269                         ldapAuthed.set(user,1);
270                         return true;
271                 }
272
273                 bool authed = false;
274
275                 for (std::vector<std::pair<std::string, std::string> >::const_iterator it = requiredattributes.begin(); it != requiredattributes.end(); ++it)
276                 {
277                         const std::string &attr = it->first;
278                         const std::string &val = it->second;
279
280                         struct berval attr_value;
281                         attr_value.bv_val = const_cast<char*>(val.c_str());
282                         attr_value.bv_len = val.length();
283
284                         ServerInstance->Logs->Log("m_ldapauth", DEBUG, "LDAP compare: %s=%s", attr.c_str(), val.c_str());
285
286                         authed = (ldap_compare_ext_s(conn, ldap_get_dn(conn, entry), attr.c_str(), &attr_value, NULL, NULL) == LDAP_COMPARE_TRUE);
287
288                         if (authed)
289                                 break;
290                 }
291
292                 ldap_msgfree(msg);
293
294                 if (!authed)
295                         return false;
296
297                 ldapAuthed.set(user,1);
298                 return true;
299         }
300
301         ModResult OnCheckReady(LocalUser* user)
302         {
303                 return ldapAuthed.get(user) ? MOD_RES_PASSTHRU : MOD_RES_DENY;
304         }
305
306         Version GetVersion()
307         {
308                 return Version("Allow/Deny connections based upon answer from LDAP server", VF_VENDOR);
309         }
310
311 };
312
313 MODULE_INIT(ModuleLDAPAuth)