2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
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.
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
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/>.
21 #include "modules/sql.h"
22 #include "modules/hash.h"
23 #include "modules/ssl.h"
31 class AuthQuery : public SQL::Query
34 const std::string uid;
35 LocalIntExt& pendingExt;
37 const std::string& kdf;
38 const std::string& pwcolumn;
40 AuthQuery(Module* me, const std::string& u, LocalIntExt& e, bool v, const std::string& kd, const std::string& pwcol)
50 void OnResult(SQL::Result& res) CXX11_OVERRIDE
52 LocalUser* user = static_cast<LocalUser*>(ServerInstance->FindUUID(uid));
60 HashProvider* hashprov = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + kdf);
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);
70 if (!pwcolumn.empty() && !res.HasColumn(pwcolumn, colindex))
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);
79 while (res.GetRow(row))
81 if (hashprov->Compare(user->password, row[colindex]))
83 pendingExt.set(user, AUTH_STATE_NONE);
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);
94 pendingExt.set(user, AUTH_STATE_NONE);
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);
104 void OnError(SQL::Error& error) CXX11_OVERRIDE
106 User* user = ServerInstance->FindNick(uid);
109 pendingExt.set(user, AUTH_STATE_FAIL);
111 ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s (SQL query failed: %s)", user->GetFullRealHost().c_str(), error.ToString());
115 class ModuleSQLAuth : public Module
117 LocalIntExt pendingExt;
118 dynamic_reference<SQL::Provider> SQL;
119 UserCertificateAPI sslapi;
121 std::string freeformquery;
122 std::string killreason;
123 std::string allowpattern;
125 std::vector<std::string> hash_algos;
127 std::string pwcolumn;
131 : pendingExt("sqlauth-wait", ExtensionItem::EXT_USER, this)
137 void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
139 ConfigTag* conf = ServerInstance->Config->ConfValue("sqlauth");
140 std::string dbid = conf->getString("dbid");
142 SQL.SetProvider("SQL");
144 SQL.SetProvider("SQL/" + dbid);
145 freeformquery = conf->getString("query");
146 killreason = conf->getString("killreason");
147 allowpattern = conf->getString("allowpattern");
148 verbose = conf->getBool("verbose");
149 kdf = conf->getString("kdf");
150 pwcolumn = conf->getString("column");
153 irc::commasepstream algos(conf->getString("hash", "md5,sha256"));
155 while (algos.GetToken(algo))
156 hash_algos.push_back(algo);
159 ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE
161 // Note this is their initial (unresolved) connect block
162 ConfigTag* tag = user->MyClass->config;
163 if (!tag->getBool("usesqlauth", true))
164 return MOD_RES_PASSTHRU;
166 if (!allowpattern.empty() && InspIRCd::Match(user->nick,allowpattern))
167 return MOD_RES_PASSTHRU;
169 if (pendingExt.get(user))
170 return MOD_RES_PASSTHRU;
174 ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s (SQL database not present)", user->GetFullRealHost().c_str());
175 ServerInstance->Users->QuitUser(user, killreason);
176 return MOD_RES_PASSTHRU;
179 pendingExt.set(user, AUTH_STATE_BUSY);
181 SQL::ParamMap userinfo;
182 SQL::PopulateUserInfo(user, userinfo);
183 userinfo["pass"] = user->password;
184 userinfo["certfp"] = sslapi ? sslapi->GetFingerprint(user) : "";
186 for (std::vector<std::string>::const_iterator it = hash_algos.begin(); it != hash_algos.end(); ++it)
188 HashProvider* hashprov = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + *it);
189 if (hashprov && !hashprov->IsKDF())
190 userinfo[*it + "pass"] = hashprov->Generate(user->password);
193 SQL->Submit(new AuthQuery(this, user->uuid, pendingExt, verbose, kdf, pwcolumn), freeformquery, userinfo);
195 return MOD_RES_PASSTHRU;
198 ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
200 switch (pendingExt.get(user))
202 case AUTH_STATE_NONE:
203 return MOD_RES_PASSTHRU;
204 case AUTH_STATE_BUSY:
206 case AUTH_STATE_FAIL:
207 ServerInstance->Users->QuitUser(user, killreason);
210 return MOD_RES_PASSTHRU;
213 Version GetVersion() CXX11_OVERRIDE
215 return Version("Allow/deny connections based upon an arbitrary SQL table", VF_VENDOR);
219 MODULE_INIT(ModuleSQLAuth)