]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_ldapauth.cpp
Merge pull request #28 from DjSlash/classinconnectmsg
[user/henk/code/inspircd.git] / src / modules / extra / m_ldapauth.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2010 InspIRCd Development Team
6  * See: http://wiki.inspircd.org/Credits
7  *
8  * This program is free but copyrighted software; see
9  *            the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  *
13  * Taken from the UnrealIRCd 4.0 SVN version, based on
14  * InspIRCd 1.1.x.
15  *
16  * UnrealIRCd 4.0 (C) 2007 Carsten Valdemar Munk
17  * This program is free but copyrighted software; see
18  *          the file COPYING for details.
19  *
20  * ---------------------------------------------------
21  * Heavily based on SQLauth
22  */
23
24 #include "inspircd.h"
25 #include "users.h"
26 #include "channels.h"
27 #include "modules.h"
28
29 #include <ldap.h>
30
31 #ifdef WINDOWS
32 # pragma comment(lib, "ldap.lib")
33 # pragma comment(lib, "lber.lib")
34 #endif
35
36 /* $ModDesc: Allow/Deny connections based upon answer from LDAP server */
37 /* $LinkerFlags: -lldap */
38
39 class ModuleLDAPAuth : public Module
40 {
41         LocalIntExt ldapAuthed;
42         std::string base;
43         std::string attribute;
44         std::string ldapserver;
45         std::string allowpattern;
46         std::string killreason;
47         std::string username;
48         std::string password;
49         std::vector<std::string> whitelistedcidrs;
50         int searchscope;
51         bool verbose;
52         bool useusername;
53         LDAP *conn;
54
55 public:
56         ModuleLDAPAuth() : ldapAuthed("ldapauth", this)
57         {
58                 conn = NULL;
59         }
60
61         void init()
62         {
63                 Implementation eventlist[] = { I_OnCheckReady, I_OnRehash, I_OnUserRegister };
64                 ServerInstance->Modules->Attach(eventlist, this, 3);
65                 OnRehash(NULL);
66         }
67
68         ~ModuleLDAPAuth()
69         {
70                 if (conn)
71                         ldap_unbind_ext(conn, NULL, NULL);
72         }
73
74         void OnRehash(User* user)
75         {
76                 ConfigReader Conf;
77                 whitelistedcidrs.clear();
78
79                 base                    = Conf.ReadValue("ldapauth", "baserdn", 0);
80                 attribute               = Conf.ReadValue("ldapauth", "attribute", 0);
81                 ldapserver              = Conf.ReadValue("ldapauth", "server", 0);
82                 allowpattern            = Conf.ReadValue("ldapauth", "allowpattern", 0);
83                 killreason              = Conf.ReadValue("ldapauth", "killreason", 0);
84                 std::string scope       = Conf.ReadValue("ldapauth", "searchscope", 0);
85                 username                = Conf.ReadValue("ldapauth", "binddn", 0);
86                 password                = Conf.ReadValue("ldapauth", "bindauth", 0);
87                 verbose                 = Conf.ReadFlag("ldapauth", "verbose", 0);              /* Set to true if failed connects should be reported to operators */
88                 useusername             = Conf.ReadFlag("ldapauth", "userfield", 0);
89
90                 ConfigTagList whitelisttags = ServerInstance->Config->ConfTags("ldapwhitelist");
91
92                 for (ConfigIter i = whitelisttags.first; i != whitelisttags.second; ++i)
93                 {
94                         std::string cidr = i->second->getString("cidr");
95                         if (!cidr.empty()) {
96                                 whitelistedcidrs.push_back(cidr);
97                         }
98                 }
99
100                 if (scope == "base")
101                         searchscope = LDAP_SCOPE_BASE;
102                 else if (scope == "onelevel")
103                         searchscope = LDAP_SCOPE_ONELEVEL;
104                 else searchscope = LDAP_SCOPE_SUBTREE;
105
106                 Connect();
107         }
108
109         bool Connect()
110         {
111                 if (conn != NULL)
112                         ldap_unbind_ext(conn, NULL, NULL);
113                 int res, v = LDAP_VERSION3;
114                 res = ldap_initialize(&conn, ldapserver.c_str());
115                 if (res != LDAP_SUCCESS)
116                 {
117                         if (verbose)
118                                 ServerInstance->SNO->WriteToSnoMask('c', "LDAP connection failed: %s", ldap_err2string(res));
119                         conn = NULL;
120                         return false;
121                 }
122
123                 res = ldap_set_option(conn, LDAP_OPT_PROTOCOL_VERSION, (void *)&v);
124                 if (res != LDAP_SUCCESS)
125                 {
126                         if (verbose)
127                                 ServerInstance->SNO->WriteToSnoMask('c', "LDAP set protocol to v3 failed: %s", ldap_err2string(res));
128                         ldap_unbind_ext(conn, NULL, NULL);
129                         conn = NULL;
130                         return false;
131                 }
132                 return true;
133         }
134
135         ModResult OnUserRegister(LocalUser* user)
136         {
137                 if ((!allowpattern.empty()) && (InspIRCd::Match(user->nick,allowpattern)))
138                 {
139                         ldapAuthed.set(user,1);
140                         return MOD_RES_PASSTHRU;
141                 }
142
143                 for (std::vector<std::string>::iterator i = whitelistedcidrs.begin(); i != whitelistedcidrs.end(); i++)
144                 {
145                         if (InspIRCd::MatchCIDR(user->GetIPString(), *i, ascii_case_insensitive_map))
146                         {
147                                 ldapAuthed.set(user,1);
148                                 return MOD_RES_PASSTHRU;
149                         }
150                 }
151
152                 if (!CheckCredentials(user))
153                 {
154                         ServerInstance->Users->QuitUser(user, killreason);
155                         return MOD_RES_DENY;
156                 }
157                 return MOD_RES_PASSTHRU;
158         }
159
160         bool CheckCredentials(LocalUser* user)
161         {
162                 if (conn == NULL)
163                         if (!Connect())
164                                 return false;
165
166                 if (user->password.empty())
167                 {
168                         if (verbose)
169                                 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());
170                         return false;
171                 }
172
173                 int res;
174                 // bind anonymously if no bind DN and authentication are given in the config
175                 struct berval cred;
176                 cred.bv_val = const_cast<char*>(password.c_str());
177                 cred.bv_len = password.length();
178
179                 if ((res = ldap_sasl_bind_s(conn, username.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL)) != LDAP_SUCCESS)
180                 {
181                         if (res == LDAP_SERVER_DOWN)
182                         {
183                                 // Attempt to reconnect if the connection dropped
184                                 if (verbose)
185                                         ServerInstance->SNO->WriteToSnoMask('a', "LDAP server has gone away - reconnecting...");
186                                 Connect();
187                                 res = ldap_sasl_bind_s(conn, username.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL);
188                         }
189
190                         if (res != LDAP_SUCCESS)
191                         {
192                                 if (verbose)
193                                         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));
194                                 ldap_unbind_ext(conn, NULL, NULL);
195                                 conn = NULL;
196                                 return false;
197                         }
198                 }
199
200                 LDAPMessage *msg, *entry;
201                 std::string what = (attribute + "=" + (useusername ? user->ident : user->nick));
202                 if ((res = ldap_search_ext_s(conn, base.c_str(), searchscope, what.c_str(), NULL, 0, NULL, NULL, NULL, 0, &msg)) != LDAP_SUCCESS)
203                 {
204                         // Do a second search, based on password, if it contains a :
205                         // That is, PASS <user>:<password> will work.
206                         size_t pos = user->password.find(":");
207                         if (pos != std::string::npos)
208                         {
209                                 res = ldap_search_ext_s(conn, base.c_str(), searchscope, user->password.substr(0, pos).c_str(), NULL, 0, NULL, NULL, NULL, 0, &msg);
210
211                                 if (res)
212                                 {
213                                         // Trim the user: prefix, leaving just 'pass' for later password check
214                                         user->password = user->password.substr(pos + 1);
215                                 }
216                         }
217
218                         // It may have found based on user:pass check above.
219                         if (!res)
220                         {
221                                 if (verbose)
222                                         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));
223                                 return false;
224                         }
225                 }
226                 if (ldap_count_entries(conn, msg) > 1)
227                 {
228                         if (verbose)
229                                 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));
230                         ldap_msgfree(msg);
231                         return false;
232                 }
233                 if ((entry = ldap_first_entry(conn, msg)) == NULL)
234                 {
235                         if (verbose)
236                                 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));
237                         ldap_msgfree(msg);
238                         return false;
239                 }
240                 cred.bv_val = (char*)user->password.data();
241                 cred.bv_len = user->password.length();
242                 if ((res = ldap_sasl_bind_s(conn, ldap_get_dn(conn, entry), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL)) == LDAP_SUCCESS)
243                 {
244                         ldap_msgfree(msg);
245                         ldapAuthed.set(user,1);
246                         return true;
247                 }
248                 else
249                 {
250                         if (verbose)
251                                 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));
252                         ldap_msgfree(msg);
253                         return false;
254                 }
255         }
256
257         ModResult OnCheckReady(LocalUser* user)
258         {
259                 return ldapAuthed.get(user) ? MOD_RES_PASSTHRU : MOD_RES_DENY;
260         }
261
262         Version GetVersion()
263         {
264                 return Version("Allow/Deny connections based upon answer from LDAP server", VF_VENDOR);
265         }
266
267 };
268
269 MODULE_INIT(ModuleLDAPAuth)