]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_sqlauth.cpp
Merge tag 'v2.0.26' into master.
[user/henk/code/inspircd.git] / src / modules / m_sqlauth.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
5  *
6  * This file is part of InspIRCd.  InspIRCd is free software: you can
7  * redistribute it and/or modify it under the terms of the GNU General Public
8  * License as published by the Free Software Foundation, version 2.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
13  * details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19
20 #include "inspircd.h"
21 #include "modules/sql.h"
22 #include "modules/hash.h"
23 #include "modules/ssl.h"
24
25 enum AuthState {
26         AUTH_STATE_NONE = 0,
27         AUTH_STATE_BUSY = 1,
28         AUTH_STATE_FAIL = 2
29 };
30
31 class AuthQuery : public SQL::Query
32 {
33  public:
34         const std::string uid;
35         LocalIntExt& pendingExt;
36         bool verbose;
37         const std::string& kdf;
38         const std::string& pwcolumn;
39
40         AuthQuery(Module* me, const std::string& u, LocalIntExt& e, bool v, const std::string& kd, const std::string& pwcol)
41                 : SQL::Query(me)
42                 , uid(u)
43                 , pendingExt(e)
44                 , verbose(v)
45                 , kdf(kd)
46                 , pwcolumn(pwcol)
47         {
48         }
49
50         void OnResult(SQL::Result& res) CXX11_OVERRIDE
51         {
52                 LocalUser* user = static_cast<LocalUser*>(ServerInstance->FindUUID(uid));
53                 if (!user)
54                         return;
55
56                 if (res.Rows())
57                 {
58                         if (!kdf.empty())
59                         {
60                                 HashProvider* hashprov = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + kdf);
61                                 if (!hashprov)
62                                 {
63                                         if (verbose)
64                                                 ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s (a provider for %s was not loaded)", user->GetFullRealHost().c_str(), kdf.c_str());
65                                         pendingExt.set(user, AUTH_STATE_FAIL);
66                                         return;
67                                 }
68
69                                 size_t colindex = 0;
70                                 if (!pwcolumn.empty() && !res.HasColumn(pwcolumn, colindex))
71                                 {
72                                         if (verbose)
73                                                 ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s (the column specified (%s) was not returned)", user->GetFullRealHost().c_str(), pwcolumn.c_str());
74                                         pendingExt.set(user, AUTH_STATE_FAIL);
75                                         return;
76                                 }
77
78                                 SQL::Row row;
79                                 while (res.GetRow(row))
80                                 {
81                                         if (hashprov->Compare(user->password, row[colindex]))
82                                         {
83                                                 pendingExt.set(user, AUTH_STATE_NONE);
84                                                 return;
85                                         }
86                                 }
87
88                                 if (verbose)
89                                         ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s (Password from the SQL query did not match the user provided password)", user->GetFullRealHost().c_str());
90                                 pendingExt.set(user, AUTH_STATE_FAIL);
91                                 return;
92                         }
93
94                         pendingExt.set(user, AUTH_STATE_NONE);
95                 }
96                 else
97                 {
98                         if (verbose)
99                                 ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s (SQL query returned no matches)", user->GetFullRealHost().c_str());
100                         pendingExt.set(user, AUTH_STATE_FAIL);
101                 }
102         }
103
104         void OnError(SQL::Error& error) CXX11_OVERRIDE
105         {
106                 User* user = ServerInstance->FindNick(uid);
107                 if (!user)
108                         return;
109                 pendingExt.set(user, AUTH_STATE_FAIL);
110                 if (verbose)
111                         ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s (SQL query failed: %s)", user->GetFullRealHost().c_str(), error.ToString());
112         }
113 };
114
115 class ModuleSQLAuth : public Module
116 {
117         LocalIntExt pendingExt;
118         dynamic_reference<SQL::Provider> SQL;
119
120         std::string freeformquery;
121         std::string killreason;
122         std::string allowpattern;
123         bool verbose;
124         std::vector<std::string> hash_algos;
125         std::string kdf;
126         std::string pwcolumn;
127
128  public:
129         ModuleSQLAuth()
130                 : pendingExt("sqlauth-wait", ExtensionItem::EXT_USER, this)
131                 , SQL(this, "SQL")
132         {
133         }
134
135         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
136         {
137                 ConfigTag* conf = ServerInstance->Config->ConfValue("sqlauth");
138                 std::string dbid = conf->getString("dbid");
139                 if (dbid.empty())
140                         SQL.SetProvider("SQL");
141                 else
142                         SQL.SetProvider("SQL/" + dbid);
143                 freeformquery = conf->getString("query");
144                 killreason = conf->getString("killreason");
145                 allowpattern = conf->getString("allowpattern");
146                 verbose = conf->getBool("verbose");
147                 kdf = conf->getString("kdf");
148                 pwcolumn = conf->getString("column");
149
150                 hash_algos.clear();
151                 irc::commasepstream algos(conf->getString("hash", "md5,sha256"));
152                 std::string algo;
153                 while (algos.GetToken(algo))
154                         hash_algos.push_back(algo);
155         }
156
157         ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE
158         {
159                 // Note this is their initial (unresolved) connect block
160                 ConfigTag* tag = user->MyClass->config;
161                 if (!tag->getBool("usesqlauth", true))
162                         return MOD_RES_PASSTHRU;
163
164                 if (!allowpattern.empty() && InspIRCd::Match(user->nick,allowpattern))
165                         return MOD_RES_PASSTHRU;
166
167                 if (pendingExt.get(user))
168                         return MOD_RES_PASSTHRU;
169
170                 if (!SQL)
171                 {
172                         ServerInstance->SNO->WriteGlobalSno('a', "Forbiding connection from %s (SQL database not present)", user->GetFullRealHost().c_str());
173                         ServerInstance->Users->QuitUser(user, killreason);
174                         return MOD_RES_PASSTHRU;
175                 }
176
177                 pendingExt.set(user, AUTH_STATE_BUSY);
178
179                 SQL::ParamMap userinfo;
180                 SQL::PopulateUserInfo(user, userinfo);
181                 userinfo["pass"] = user->password;
182
183                 for (std::vector<std::string>::const_iterator it = hash_algos.begin(); it != hash_algos.end(); ++it)
184                 {
185                         HashProvider* hashprov = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + *it);
186                         if (hashprov && !hashprov->IsKDF())
187                                 userinfo[*it + "pass"] = hashprov->Generate(user->password);
188                 }
189
190                 const std::string certfp = SSLClientCert::GetFingerprint(&user->eh);
191                 userinfo["certfp"] = certfp;
192
193                 SQL->Submit(new AuthQuery(this, user->uuid, pendingExt, verbose, kdf, pwcolumn), freeformquery, userinfo);
194
195                 return MOD_RES_PASSTHRU;
196         }
197
198         ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
199         {
200                 switch (pendingExt.get(user))
201                 {
202                         case AUTH_STATE_NONE:
203                                 return MOD_RES_PASSTHRU;
204                         case AUTH_STATE_BUSY:
205                                 return MOD_RES_DENY;
206                         case AUTH_STATE_FAIL:
207                                 ServerInstance->Users->QuitUser(user, killreason);
208                                 return MOD_RES_DENY;
209                 }
210                 return MOD_RES_PASSTHRU;
211         }
212
213         Version GetVersion() CXX11_OVERRIDE
214         {
215                 return Version("Allow/Deny connections based upon an arbitrary SQL table", VF_VENDOR);
216         }
217 };
218
219 MODULE_INIT(ModuleSQLAuth)