]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_ldapauth.cpp
85a0181c405b639603dd396f4311467a1b9e079f
[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 /* $ModDesc: Allow/Deny connections based upon answer from LDAP server */
32 /* $LinkerFlags: -lldap */
33
34 class ModuleLDAPAuth : public Module
35 {
36         LocalIntExt ldapAuthed;
37         std::string base;
38         std::string attribute;
39         std::string ldapserver;
40         std::string allowpattern;
41         std::string killreason;
42         std::string username;
43         std::string password;
44         int searchscope;
45         bool verbose;
46         bool useusername;
47         LDAP *conn;
48
49 public:
50         ModuleLDAPAuth() : ldapAuthed("ldapauth", this)
51         {
52                 conn = NULL;
53         }
54
55         void init()
56         {
57                 Implementation eventlist[] = { I_OnCheckReady, I_OnRehash, I_OnUserRegister };
58                 ServerInstance->Modules->Attach(eventlist, this, 3);
59                 OnRehash(NULL);
60         }
61
62         ~ModuleLDAPAuth()
63         {
64                 if (conn)
65                         ldap_unbind_ext(conn, NULL, NULL);
66         }
67
68         void OnRehash(User* user)
69         {
70                 ConfigReader Conf;
71
72                 base                    = Conf.ReadValue("ldapauth", "baserdn", 0);
73                 attribute               = Conf.ReadValue("ldapauth", "attribute", 0);
74                 ldapserver              = Conf.ReadValue("ldapauth", "server", 0);
75                 allowpattern            = Conf.ReadValue("ldapauth", "allowpattern", 0);
76                 killreason              = Conf.ReadValue("ldapauth", "killreason", 0);
77                 std::string scope       = Conf.ReadValue("ldapauth", "searchscope", 0);
78                 username                = Conf.ReadValue("ldapauth", "binddn", 0);
79                 password                = Conf.ReadValue("ldapauth", "bindauth", 0);
80                 verbose                 = Conf.ReadFlag("ldapauth", "verbose", 0);              /* Set to true if failed connects should be reported to operators */
81                 useusername             = Conf.ReadFlag("ldapauth", "userfield", 0);
82
83                 if (scope == "base")
84                         searchscope = LDAP_SCOPE_BASE;
85                 else if (scope == "onelevel")
86                         searchscope = LDAP_SCOPE_ONELEVEL;
87                 else searchscope = LDAP_SCOPE_SUBTREE;
88
89                 Connect();
90         }
91
92         bool Connect()
93         {
94                 if (conn != NULL)
95                         ldap_unbind_ext(conn, NULL, NULL);
96                 int res, v = LDAP_VERSION3;
97                 res = ldap_initialize(&conn, ldapserver.c_str());
98                 if (res != LDAP_SUCCESS)
99                 {
100                         if (verbose)
101                                 ServerInstance->SNO->WriteToSnoMask('c', "LDAP connection failed: %s", ldap_err2string(res));
102                         conn = NULL;
103                         return false;
104                 }
105
106                 res = ldap_set_option(conn, LDAP_OPT_PROTOCOL_VERSION, (void *)&v);
107                 if (res != LDAP_SUCCESS)
108                 {
109                         if (verbose)
110                                 ServerInstance->SNO->WriteToSnoMask('c', "LDAP set protocol to v3 failed: %s", ldap_err2string(res));
111                         ldap_unbind_ext(conn, NULL, NULL);
112                         conn = NULL;
113                         return false;
114                 }
115                 return true;
116         }
117
118         ModResult OnUserRegister(LocalUser* user)
119         {
120                 if ((!allowpattern.empty()) && (InspIRCd::Match(user->nick,allowpattern)))
121                 {
122                         ldapAuthed.set(user,1);
123                         return MOD_RES_PASSTHRU;
124                 }
125
126                 if (!CheckCredentials(user))
127                 {
128                         ServerInstance->Users->QuitUser(user, killreason);
129                         return MOD_RES_DENY;
130                 }
131                 return MOD_RES_PASSTHRU;
132         }
133
134         bool CheckCredentials(LocalUser* user)
135         {
136                 if (conn == NULL)
137                         if (!Connect())
138                                 return false;
139
140                 if (user->password.empty())
141                 {
142                         if (verbose)
143                                 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());
144                         return false;
145                 }
146
147                 int res;
148                 // bind anonymously if no bind DN and authentication are given in the config
149                 struct berval cred;
150                 cred.bv_val = const_cast<char*>(password.c_str());
151                 cred.bv_len = password.length();
152
153                 if ((res = ldap_sasl_bind_s(conn, username.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL)) != LDAP_SUCCESS)
154                 {
155                         if (res == LDAP_SERVER_DOWN)
156                         {
157                                 // Attempt to reconnect if the connection dropped
158                                 if (verbose)
159                                         ServerInstance->SNO->WriteToSnoMask('a', "LDAP server has gone away - reconnecting...");
160                                 Connect();
161                                 res = ldap_sasl_bind_s(conn, username.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL);
162                         }
163
164                         if (res != LDAP_SUCCESS)
165                         {
166                                 if (verbose)
167                                         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));
168                                 ldap_unbind_ext(conn, NULL, NULL);
169                                 conn = NULL;
170                                 return false;
171                         }
172                 }
173
174                 LDAPMessage *msg, *entry;
175                 std::string what = (attribute + "=" + (useusername ? user->ident : user->nick));
176                 if ((res = ldap_search_ext_s(conn, base.c_str(), searchscope, what.c_str(), NULL, 0, NULL, NULL, NULL, 0, &msg)) != LDAP_SUCCESS)
177                 {
178                         // Do a second search, based on password, if it contains a :
179                         // That is, PASS <user>:<password> will work.
180                         size_t pos = user->password.find(":");
181                         if (pos != std::string::npos)
182                         {
183                                 res = ldap_search_ext_s(conn, base.c_str(), searchscope, user->password.substr(0, pos).c_str(), NULL, 0, NULL, NULL, NULL, 0, &msg);
184
185                                 if (res)
186                                 {
187                                         // Trim the user: prefix, leaving just 'pass' for later password check
188                                         user->password = user->password.substr(pos + 1);
189                                 }
190                         }
191
192                         // It may have found based on user:pass check above.
193                         if (!res)
194                         {
195                                 if (verbose)
196                                         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));
197                                 return false;
198                         }
199                 }
200                 if (ldap_count_entries(conn, msg) > 1)
201                 {
202                         if (verbose)
203                                 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));
204                         ldap_msgfree(msg);
205                         return false;
206                 }
207                 if ((entry = ldap_first_entry(conn, msg)) == NULL)
208                 {
209                         if (verbose)
210                                 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));
211                         ldap_msgfree(msg);
212                         return false;
213                 }
214                 cred.bv_val = (char*)user->password.data();
215                 cred.bv_len = user->password.length();
216                 if ((res = ldap_sasl_bind_s(conn, ldap_get_dn(conn, entry), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL)) == LDAP_SUCCESS)
217                 {
218                         ldap_msgfree(msg);
219                         ldapAuthed.set(user,1);
220                         return true;
221                 }
222                 else
223                 {
224                         if (verbose)
225                                 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));
226                         ldap_msgfree(msg);
227                         return false;
228                 }
229         }
230
231         ModResult OnCheckReady(LocalUser* user)
232         {
233                 return ldapAuthed.get(user) ? MOD_RES_PASSTHRU : MOD_RES_DENY;
234         }
235
236         Version GetVersion()
237         {
238                 return Version("Allow/Deny connections based upon answer from LDAP server", VF_VENDOR);
239         }
240
241 };
242
243 MODULE_INIT(ModuleLDAPAuth)