mirror of
https://github.com/inspircd/inspircd.git
synced 2025-03-10 02:59:01 -04:00
SQL API v3, drop all the ugly complexity
git-svn-id: http://svn.inspircd.org/repository/trunk/inspircd@12602 e03df62e-2008-0410-955e-edbf42e46eb7
This commit is contained in:
parent
a1fab90c80
commit
410de52634
@ -12,50 +12,78 @@
|
||||
*/
|
||||
|
||||
#include "inspircd.h"
|
||||
#include "m_sqlv2.h"
|
||||
#include "m_sqlutils.h"
|
||||
#include "sql.h"
|
||||
#include "hash.h"
|
||||
|
||||
/* $ModDesc: Allow/Deny connections based upon an arbitary SQL table */
|
||||
|
||||
enum AuthState {
|
||||
AUTH_STATE_NONE = 0,
|
||||
AUTH_STATE_BUSY = 1,
|
||||
AUTH_STATE_FAIL = 2
|
||||
};
|
||||
|
||||
class AuthQuery : public SQLQuery
|
||||
{
|
||||
public:
|
||||
const std::string uid;
|
||||
LocalIntExt& pendingExt;
|
||||
bool verbose;
|
||||
AuthQuery(Module* me, const std::string& db, const std::string& q, const std::string& u, LocalIntExt& e, bool v)
|
||||
: SQLQuery(me, db, q), uid(u), pendingExt(e), verbose(v) {}
|
||||
|
||||
void OnResult(SQLResult& res)
|
||||
{
|
||||
User* user = ServerInstance->FindNick(uid);
|
||||
if (!user)
|
||||
return;
|
||||
if (res.Rows())
|
||||
{
|
||||
pendingExt.set(user, AUTH_STATE_NONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (verbose)
|
||||
ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s!%s@%s (SQL query returned no matches)", user->nick.c_str(), user->ident.c_str(), user->host.c_str());
|
||||
pendingExt.set(user, AUTH_STATE_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
void OnError(SQLerror& error)
|
||||
{
|
||||
User* user = ServerInstance->FindNick(uid);
|
||||
if (!user)
|
||||
return;
|
||||
pendingExt.set(user, AUTH_STATE_FAIL);
|
||||
if (verbose)
|
||||
ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s!%s@%s (SQL query failed: %s)", user->nick.c_str(), user->ident.c_str(), user->host.c_str(), error.Str());
|
||||
}
|
||||
};
|
||||
|
||||
class ModuleSQLAuth : public Module
|
||||
{
|
||||
LocalIntExt sqlAuthed;
|
||||
Module* SQLutils;
|
||||
Module* SQLprovider;
|
||||
LocalIntExt pendingExt;
|
||||
dynamic_reference<SQLProvider> SQL;
|
||||
|
||||
std::string freeformquery;
|
||||
std::string killreason;
|
||||
std::string allowpattern;
|
||||
std::string databaseid;
|
||||
|
||||
bool verbose;
|
||||
|
||||
public:
|
||||
ModuleSQLAuth() : sqlAuthed("sqlauth", this)
|
||||
public:
|
||||
ModuleSQLAuth() : pendingExt("sqlauth-wait", this), SQL(this, "SQL")
|
||||
{
|
||||
}
|
||||
|
||||
void init()
|
||||
{
|
||||
SQLutils = ServerInstance->Modules->Find("m_sqlutils.so");
|
||||
if (!SQLutils)
|
||||
throw ModuleException("Can't find m_sqlutils.so. Please load m_sqlutils.so before m_sqlauth.so.");
|
||||
|
||||
ServiceProvider* prov = ServerInstance->Modules->FindService(SERVICE_DATA, "SQL");
|
||||
if (!prov)
|
||||
throw ModuleException("Can't find an SQL provider module. Please load one before attempting to load m_sqlauth.");
|
||||
SQLprovider = prov->creator;
|
||||
|
||||
ServerInstance->Modules->AddService(pendingExt);
|
||||
OnRehash(NULL);
|
||||
Implementation eventlist[] = { I_OnUserDisconnect, I_OnCheckReady, I_OnRehash, I_OnUserRegister };
|
||||
ServerInstance->Modules->Attach(eventlist, this, 4);
|
||||
}
|
||||
|
||||
virtual ~ModuleSQLAuth()
|
||||
{
|
||||
}
|
||||
|
||||
void OnRehash(User* user)
|
||||
{
|
||||
ConfigReader Conf;
|
||||
@ -69,115 +97,62 @@ public:
|
||||
|
||||
ModResult OnUserRegister(LocalUser* user)
|
||||
{
|
||||
if ((!allowpattern.empty()) && (InspIRCd::Match(user->nick,allowpattern)))
|
||||
{
|
||||
sqlAuthed.set(user, 1);
|
||||
// Note this is their initial (unresolved) connect block
|
||||
ConfigTag* tag = user->MyClass->config;
|
||||
if (!tag->getBool("usesqlauth", true))
|
||||
return MOD_RES_PASSTHRU;
|
||||
}
|
||||
|
||||
if (!CheckCredentials(user))
|
||||
{
|
||||
ServerInstance->Users->QuitUser(user, killreason);
|
||||
return MOD_RES_DENY;
|
||||
}
|
||||
return MOD_RES_PASSTHRU;
|
||||
}
|
||||
if (!allowpattern.empty() && InspIRCd::Match(user->nick,allowpattern))
|
||||
return MOD_RES_PASSTHRU;
|
||||
|
||||
if (pendingExt.get(user))
|
||||
return MOD_RES_PASSTHRU;
|
||||
|
||||
pendingExt.set(user, AUTH_STATE_BUSY);
|
||||
|
||||
bool CheckCredentials(LocalUser* user)
|
||||
{
|
||||
std::string thisquery = freeformquery;
|
||||
std::string safepass = user->password;
|
||||
std::string safegecos = user->fullname;
|
||||
|
||||
/* Search and replace the escaped nick and escaped pass into the query */
|
||||
|
||||
SearchAndReplace(safepass, std::string("\""), std::string("\\\""));
|
||||
SearchAndReplace(safegecos, std::string("\""), std::string("\\\""));
|
||||
|
||||
SearchAndReplace(thisquery, std::string("$nick"), user->nick);
|
||||
SearchAndReplace(thisquery, std::string("$pass"), safepass);
|
||||
SearchAndReplace(thisquery, std::string("$host"), user->host);
|
||||
SearchAndReplace(thisquery, std::string("$ip"), std::string(user->GetIPString()));
|
||||
SearchAndReplace(thisquery, std::string("$gecos"), safegecos);
|
||||
SearchAndReplace(thisquery, std::string("$ident"), user->ident);
|
||||
SearchAndReplace(thisquery, std::string("$server"), std::string(user->server));
|
||||
SearchAndReplace(thisquery, std::string("$uuid"), user->uuid);
|
||||
ParamM userinfo;
|
||||
userinfo["nick"] = user->nick;
|
||||
userinfo["pass"] = user->password;
|
||||
userinfo["host"] = user->host;
|
||||
userinfo["ip"] = user->GetIPString();
|
||||
userinfo["gecos"] = user->fullname;
|
||||
userinfo["ident"] = user->ident;
|
||||
userinfo["server"] = user->server;
|
||||
userinfo["uuid"] = user->uuid;
|
||||
|
||||
HashProvider* md5 = ServerInstance->Modules->FindDataService<HashProvider>("hash/md5");
|
||||
if (md5)
|
||||
SearchAndReplace(thisquery, std::string("$md5pass"), md5->hexsum(user->password));
|
||||
userinfo["md5pass"] = md5->hexsum(user->password);
|
||||
|
||||
HashProvider* sha256 = ServerInstance->Modules->FindDataService<HashProvider>("hash/sha256");
|
||||
if (sha256)
|
||||
SearchAndReplace(thisquery, std::string("$sha256pass"), sha256->hexsum(user->password));
|
||||
userinfo["$sha256pass"] = sha256->hexsum(user->password);
|
||||
|
||||
/* Build the query */
|
||||
SQLrequest req = SQLrequest(this, SQLprovider, databaseid, SQLquery(thisquery));
|
||||
SQL->submit(new AuthQuery(this, databaseid, SQL->FormatQuery(freeformquery, userinfo), user->uuid, pendingExt, verbose));
|
||||
|
||||
req.Send();
|
||||
/* When we get the query response from the service provider we will be given an ID to play with,
|
||||
* just an ID number which is unique to this query. We need a way of associating that ID with a User
|
||||
* so we insert it into a map mapping the IDs to users.
|
||||
* Thankfully m_sqlutils provides this, it will associate a ID with a user or channel, and if the user quits it removes the
|
||||
* association. This means that if the user quits during a query we will just get a failed lookup from m_sqlutils - telling
|
||||
* us to discard the query.
|
||||
*/
|
||||
AssociateUser(this, SQLutils, req.id, user).Send();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnRequest(Request& request)
|
||||
{
|
||||
if(strcmp(SQLRESID, request.id) == 0)
|
||||
{
|
||||
SQLresult* res = static_cast<SQLresult*>(&request);
|
||||
|
||||
User* user = GetAssocUser(this, SQLutils, res->id).S().user;
|
||||
UnAssociate(this, SQLutils, res->id).S();
|
||||
|
||||
if(user)
|
||||
{
|
||||
if(res->error.Id() == SQL_NO_ERROR)
|
||||
{
|
||||
if(res->Rows())
|
||||
{
|
||||
/* We got a row in the result, this is enough really */
|
||||
sqlAuthed.set(user, 1);
|
||||
}
|
||||
else if (verbose)
|
||||
{
|
||||
/* No rows in result, this means there was no record matching the user */
|
||||
ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s!%s@%s (SQL query returned no matches)", user->nick.c_str(), user->ident.c_str(), user->host.c_str());
|
||||
}
|
||||
}
|
||||
else if (verbose)
|
||||
{
|
||||
ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s!%s@%s (SQL query failed: %s)", user->nick.c_str(), user->ident.c_str(), user->host.c_str(), res->error.Str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sqlAuthed.get(user))
|
||||
{
|
||||
ServerInstance->Users->QuitUser(user, killreason);
|
||||
}
|
||||
}
|
||||
return MOD_RES_PASSTHRU;
|
||||
}
|
||||
|
||||
ModResult OnCheckReady(LocalUser* user)
|
||||
{
|
||||
return sqlAuthed.get(user) ? MOD_RES_PASSTHRU : MOD_RES_DENY;
|
||||
switch (pendingExt.get(user))
|
||||
{
|
||||
case AUTH_STATE_NONE:
|
||||
return MOD_RES_PASSTHRU;
|
||||
case AUTH_STATE_BUSY:
|
||||
return MOD_RES_DENY;
|
||||
case AUTH_STATE_FAIL:
|
||||
ServerInstance->Users->QuitUser(user, killreason);
|
||||
return MOD_RES_DENY;
|
||||
}
|
||||
return MOD_RES_PASSTHRU;
|
||||
}
|
||||
|
||||
Version GetVersion()
|
||||
{
|
||||
return Version("Allow/Deny connections based upon an arbitary SQL table", VF_VENDOR);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
MODULE_INIT(ModuleSQLAuth)
|
||||
|
@ -12,211 +12,72 @@
|
||||
*/
|
||||
|
||||
#include "inspircd.h"
|
||||
#include "m_sqlv2.h"
|
||||
#include "m_sqlutils.h"
|
||||
#include "sql.h"
|
||||
#include "hash.h"
|
||||
|
||||
/* $ModDesc: Allows storage of oper credentials in an SQL table */
|
||||
|
||||
typedef std::map<irc::string, Module*> hashymodules;
|
||||
|
||||
class ModuleSQLOper : public Module
|
||||
static bool OneOfMatches(const char* host, const char* ip, const std::string& hostlist)
|
||||
{
|
||||
LocalStringExt saved_user;
|
||||
LocalStringExt saved_pass;
|
||||
Module* SQLutils;
|
||||
std::string databaseid;
|
||||
std::string hashtype;
|
||||
parameterlist names;
|
||||
|
||||
public:
|
||||
ModuleSQLOper() : saved_user("sqloper_user", this), saved_pass("sqloper_pass", this)
|
||||
std::stringstream hl(hostlist);
|
||||
std::string xhost;
|
||||
while (hl >> xhost)
|
||||
{
|
||||
}
|
||||
|
||||
void init()
|
||||
{
|
||||
OnRehash(NULL);
|
||||
|
||||
SQLutils = ServerInstance->Modules->Find("m_sqlutils.so");
|
||||
if (!SQLutils)
|
||||
throw ModuleException("Can't find m_sqlutils.so. Please load m_sqlutils.so before m_sqloper.so.");
|
||||
|
||||
Implementation eventlist[] = { I_OnRehash, I_OnPreCommand, I_OnLoadModule };
|
||||
ServerInstance->Modules->Attach(eventlist, this, 3);
|
||||
ServerInstance->Modules->AddService(saved_user);
|
||||
ServerInstance->Modules->AddService(saved_pass);
|
||||
}
|
||||
|
||||
bool OneOfMatches(const char* host, const char* ip, const char* hostlist)
|
||||
{
|
||||
std::stringstream hl(hostlist);
|
||||
std::string xhost;
|
||||
while (hl >> xhost)
|
||||
if (InspIRCd::Match(host, xhost, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(ip, xhost, ascii_case_insensitive_map))
|
||||
{
|
||||
if (InspIRCd::Match(host, xhost, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(ip, xhost, ascii_case_insensitive_map))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void OnRehash(User* user)
|
||||
{
|
||||
ConfigReader Conf;
|
||||
|
||||
databaseid = Conf.ReadValue("sqloper", "dbid", 0); /* Database ID of a database configured for the service provider module */
|
||||
hashtype = Conf.ReadValue("sqloper", "hash", 0);
|
||||
}
|
||||
|
||||
virtual ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line)
|
||||
{
|
||||
if ((validated) && (command == "OPER"))
|
||||
{
|
||||
if (LookupOper(user, parameters[0], parameters[1]))
|
||||
{
|
||||
/* Returning true here just means the query is in progress, or on it's way to being
|
||||
* in progress. Nothing about the /oper actually being successful..
|
||||
* If the oper lookup fails later, we pass the command to the original handler
|
||||
* for /oper by calling its Handle method directly.
|
||||
*/
|
||||
return MOD_RES_DENY;
|
||||
}
|
||||
}
|
||||
return MOD_RES_PASSTHRU;
|
||||
}
|
||||
|
||||
bool LookupOper(User* user, const std::string &username, const std::string &password)
|
||||
{
|
||||
ServiceProvider* prov = ServerInstance->Modules->FindService(SERVICE_DATA, "SQL");
|
||||
if (prov)
|
||||
{
|
||||
Module* target = prov->creator;
|
||||
HashProvider* hash = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + hashtype);
|
||||
|
||||
/* Make an MD5 hash of the password for using in the query */
|
||||
std::string md5_pass_hash = hash ? hash->hexsum(password) : password;
|
||||
|
||||
/* We generate our own sum here because some database providers (e.g. SQLite) dont have a builtin md5/sha256 function,
|
||||
* also hashing it in the module and only passing a remote query containing a hash is more secure.
|
||||
*/
|
||||
SQLrequest req = SQLrequest(this, target, databaseid,
|
||||
SQLquery("SELECT username, password, hostname, type FROM ircd_opers WHERE username = '?' AND password='?'") % username % md5_pass_hash);
|
||||
|
||||
/* When we get the query response from the service provider we will be given an ID to play with,
|
||||
* just an ID number which is unique to this query. We need a way of associating that ID with a User
|
||||
* so we insert it into a map mapping the IDs to users.
|
||||
* Thankfully m_sqlutils provides this, it will associate a ID with a user or channel, and if the user quits it removes the
|
||||
* association. This means that if the user quits during a query we will just get a failed lookup from m_sqlutils - telling
|
||||
* us to discard the query.
|
||||
*/
|
||||
AssociateUser(this, SQLutils, req.id, user).Send();
|
||||
|
||||
saved_user.set(user, username);
|
||||
saved_pass.set(user, password);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class OpMeQuery : public SQLQuery
|
||||
{
|
||||
public:
|
||||
const std::string uid, username, password;
|
||||
OpMeQuery(Module* me, const std::string& db, const std::string& q, const std::string& u, const std::string& un, const std::string& pw)
|
||||
: SQLQuery(me, db, q), uid(u), username(un), password(pw) {}
|
||||
|
||||
void OnResult(SQLResult& res)
|
||||
{
|
||||
User* user = ServerInstance->FindNick(uid);
|
||||
if (!user)
|
||||
return;
|
||||
|
||||
// multiple rows may exist for multiple hosts
|
||||
parameterlist row;
|
||||
while (res.GetRow(row))
|
||||
{
|
||||
ServerInstance->Logs->Log("m_sqloper",SPARSE, "WARNING: Couldn't find SQL provider module. NOBODY will be able to oper up unless their o:line is statically configured");
|
||||
return false;
|
||||
if (OperUser(user, row[2], row[3]))
|
||||
return;
|
||||
}
|
||||
// nobody succeeded... fall back to OPER
|
||||
fallback();
|
||||
}
|
||||
|
||||
void OnRequest(Request& request)
|
||||
void OnError(SQLerror& error)
|
||||
{
|
||||
if (strcmp(SQLRESID, request.id) == 0)
|
||||
{
|
||||
SQLresult* res = static_cast<SQLresult*>(&request);
|
||||
|
||||
User* user = GetAssocUser(this, SQLutils, res->id).S().user;
|
||||
UnAssociate(this, SQLutils, res->id).S();
|
||||
|
||||
if (user)
|
||||
{
|
||||
std::string* tried_user = saved_user.get(user);
|
||||
std::string* tried_pass = saved_pass.get(user);
|
||||
if (res->error.Id() == SQL_NO_ERROR)
|
||||
{
|
||||
if (res->Rows())
|
||||
{
|
||||
/* We got a row in the result, this means there was a record for the oper..
|
||||
* now we just need to check if their host matches, and if it does then
|
||||
* oper them up.
|
||||
*
|
||||
* We now (previous versions of the module didn't) support multiple SQL
|
||||
* rows per-oper in the same way the config file does, all rows will be tried
|
||||
* until one is found which matches. This is useful to define several different
|
||||
* hosts for a single oper.
|
||||
*
|
||||
* The for() loop works as SQLresult::GetRowMap() returns an empty map when there
|
||||
* are no more rows to return.
|
||||
*/
|
||||
|
||||
for (SQLfieldMap& row = res->GetRowMap(); row.size(); row = res->GetRowMap())
|
||||
{
|
||||
if (OperUser(user, row["hostname"].d, row["type"].d))
|
||||
{
|
||||
/* If/when one of the rows matches, stop checking and return */
|
||||
saved_user.unset(user);
|
||||
saved_pass.unset(user);
|
||||
}
|
||||
if (tried_user && tried_pass)
|
||||
{
|
||||
LoginFail(user, *tried_user, *tried_pass);
|
||||
saved_user.unset(user);
|
||||
saved_pass.unset(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No rows in result, this means there was no oper line for the user,
|
||||
* we should have already checked the o:lines so now we need an
|
||||
* "insufficient awesomeness" (invalid credentials) error
|
||||
*/
|
||||
if (tried_user && tried_pass)
|
||||
{
|
||||
LoginFail(user, *tried_user, *tried_pass);
|
||||
saved_user.unset(user);
|
||||
saved_pass.unset(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* This one shouldn't happen, the query failed for some reason.
|
||||
* We have to fail the /oper request and give them the same error
|
||||
* as above.
|
||||
*/
|
||||
if (tried_user && tried_pass)
|
||||
{
|
||||
LoginFail(user, *tried_user, *tried_pass);
|
||||
saved_user.unset(user);
|
||||
saved_pass.unset(user);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
fallback();
|
||||
}
|
||||
|
||||
void LoginFail(User* user, const std::string &username, const std::string &pass)
|
||||
void fallback()
|
||||
{
|
||||
User* user = ServerInstance->FindNick(uid);
|
||||
if (!user)
|
||||
return;
|
||||
|
||||
Command* oper_command = ServerInstance->Parser->GetHandler("OPER");
|
||||
|
||||
if (oper_command)
|
||||
{
|
||||
std::vector<std::string> params;
|
||||
params.push_back(username);
|
||||
params.push_back(pass);
|
||||
params.push_back(password);
|
||||
oper_command->Handle(params, user);
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerInstance->Logs->Log("m_sqloper",DEBUG, "BUG: WHAT?! Why do we have no OPER command?!");
|
||||
ServerInstance->Logs->Log("m_sqloper",SPARSE, "BUG: WHAT?! Why do we have no OPER command?!");
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,6 +102,56 @@ public:
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class ModuleSQLOper : public Module
|
||||
{
|
||||
std::string databaseid;
|
||||
std::string hashtype;
|
||||
dynamic_reference<SQLProvider> SQL;
|
||||
|
||||
public:
|
||||
ModuleSQLOper() : SQL(this, "SQL") {}
|
||||
|
||||
void init()
|
||||
{
|
||||
OnRehash(NULL);
|
||||
|
||||
Implementation eventlist[] = { I_OnRehash, I_OnPreCommand };
|
||||
ServerInstance->Modules->Attach(eventlist, this, 2);
|
||||
}
|
||||
|
||||
void OnRehash(User* user)
|
||||
{
|
||||
ConfigReader Conf;
|
||||
|
||||
databaseid = Conf.ReadValue("sqloper", "dbid", 0); /* Database ID of a database configured for the service provider module */
|
||||
hashtype = Conf.ReadValue("sqloper", "hash", 0);
|
||||
}
|
||||
|
||||
ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line)
|
||||
{
|
||||
if (validated && command == "OPER" && parameters.size() == 2 && SQL)
|
||||
{
|
||||
LookupOper(user, parameters[0], parameters[1]);
|
||||
/* Query is in progress, it will re-invoke OPER if needed */
|
||||
return MOD_RES_DENY;
|
||||
}
|
||||
return MOD_RES_PASSTHRU;
|
||||
}
|
||||
|
||||
void LookupOper(User* user, const std::string &username, const std::string &password)
|
||||
{
|
||||
HashProvider* hash = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + hashtype);
|
||||
|
||||
parameterlist params;
|
||||
params.push_back(username);
|
||||
params.push_back(hash ? hash->hexsum(password) : password);
|
||||
|
||||
SQL->submit(new OpMeQuery(this, databaseid, SQL->FormatQuery(
|
||||
"SELECT username, password, hostname, type FROM ircd_opers WHERE username = '?' AND password='?'", params
|
||||
), user->uuid, username, password));
|
||||
}
|
||||
|
||||
Version GetVersion()
|
||||
{
|
||||
|
@ -1,223 +0,0 @@
|
||||
/* +------------------------------------+
|
||||
* | Inspire Internet Relay Chat Daemon |
|
||||
* +------------------------------------+
|
||||
*
|
||||
* InspIRCd: (C) 2002-2010 InspIRCd Development Team
|
||||
* See: http://wiki.inspircd.org/Credits
|
||||
*
|
||||
* This program is free but copyrighted software; see
|
||||
* the file COPYING for details.
|
||||
*
|
||||
* ---------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "inspircd.h"
|
||||
#include <sstream>
|
||||
#include <list>
|
||||
#include "m_sqlutils.h"
|
||||
|
||||
/* $ModDesc: Provides some utilities to SQL client modules, such as mapping queries to users and channels */
|
||||
/* $ModDep: m_sqlutils.h */
|
||||
|
||||
typedef std::map<unsigned long, User*> IdUserMap;
|
||||
typedef std::map<unsigned long, Channel*> IdChanMap;
|
||||
typedef std::list<unsigned long> AssocIdList;
|
||||
|
||||
class ModuleSQLutils : public Module
|
||||
{
|
||||
private:
|
||||
IdUserMap iduser;
|
||||
IdChanMap idchan;
|
||||
SimpleExtItem<AssocIdList> idExt;
|
||||
|
||||
public:
|
||||
ModuleSQLutils() : idExt("sqlutils_list", this)
|
||||
{
|
||||
}
|
||||
|
||||
void init()
|
||||
{
|
||||
Implementation eventlist[] = { I_OnChannelDelete, I_OnUnloadModule, I_OnUserDisconnect };
|
||||
ServerInstance->Modules->Attach(eventlist, this, 3);
|
||||
}
|
||||
|
||||
void OnRequest(Request& request)
|
||||
{
|
||||
if(strcmp(SQLUTILAU, request.id) == 0)
|
||||
{
|
||||
AssociateUser* req = (AssociateUser*)&request;
|
||||
|
||||
iduser.insert(std::make_pair(req->id, req->user));
|
||||
|
||||
AttachList(req->user, req->id);
|
||||
}
|
||||
else if(strcmp(SQLUTILAC, request.id) == 0)
|
||||
{
|
||||
AssociateChan* req = (AssociateChan*)&request;
|
||||
|
||||
idchan.insert(std::make_pair(req->id, req->chan));
|
||||
|
||||
AttachList(req->chan, req->id);
|
||||
}
|
||||
else if(strcmp(SQLUTILUA, request.id) == 0)
|
||||
{
|
||||
UnAssociate* req = (UnAssociate*)&request;
|
||||
|
||||
/* Unassociate a given query ID with all users and channels
|
||||
* it is associated with.
|
||||
*/
|
||||
|
||||
DoUnAssociate(iduser, req->id);
|
||||
DoUnAssociate(idchan, req->id);
|
||||
}
|
||||
else if(strcmp(SQLUTILGU, request.id) == 0)
|
||||
{
|
||||
GetAssocUser* req = (GetAssocUser*)&request;
|
||||
|
||||
IdUserMap::iterator iter = iduser.find(req->id);
|
||||
|
||||
if(iter != iduser.end())
|
||||
{
|
||||
req->user = iter->second;
|
||||
}
|
||||
}
|
||||
else if(strcmp(SQLUTILGC, request.id) == 0)
|
||||
{
|
||||
GetAssocChan* req = (GetAssocChan*)&request;
|
||||
|
||||
IdChanMap::iterator iter = idchan.find(req->id);
|
||||
|
||||
if(iter != idchan.end())
|
||||
{
|
||||
req->chan = iter->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnUserDisconnect(LocalUser* user)
|
||||
{
|
||||
/* A user is disconnecting, first we need to check if they have a list of queries associated with them.
|
||||
* Then, if they do, we need to erase each of them from our IdUserMap (iduser) so when the module that
|
||||
* associated them asks to look them up then it gets a NULL result and knows to discard the query.
|
||||
*/
|
||||
AssocIdList* il = idExt.get(user);
|
||||
|
||||
if(il)
|
||||
{
|
||||
for(AssocIdList::iterator listiter = il->begin(); listiter != il->end(); listiter++)
|
||||
{
|
||||
IdUserMap::iterator iter;
|
||||
|
||||
iter = iduser.find(*listiter);
|
||||
|
||||
if(iter != iduser.end())
|
||||
{
|
||||
if(iter->second != user)
|
||||
{
|
||||
ServerInstance->Logs->Log("m_sqlutils",DEBUG, "BUG: ID associated with user %s doesn't have the same User* associated with it in the map (erasing anyway)", user->nick.c_str());
|
||||
}
|
||||
|
||||
iduser.erase(iter);
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerInstance->Logs->Log("m_sqlutils",DEBUG, "BUG: user %s was extended with sqlutils_queryids but there was nothing matching in the map", user->nick.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
idExt.unset(user);
|
||||
}
|
||||
}
|
||||
|
||||
void AttachList(Extensible* obj, unsigned long id)
|
||||
{
|
||||
AssocIdList* il = idExt.get(obj);
|
||||
|
||||
if (!il)
|
||||
{
|
||||
/* Doesn't already exist, create a new list and attach it. */
|
||||
il = new AssocIdList;
|
||||
idExt.set(obj, il);
|
||||
}
|
||||
|
||||
/* Now either way we have a valid list in il, attached. */
|
||||
il->push_back(id);
|
||||
}
|
||||
|
||||
void RemoveFromList(Extensible* obj, unsigned long id)
|
||||
{
|
||||
AssocIdList* il = idExt.get(obj);
|
||||
|
||||
if (il)
|
||||
{
|
||||
/* Only do anything if the list exists... (which it ought to) */
|
||||
il->remove(id);
|
||||
|
||||
if(il->empty())
|
||||
{
|
||||
/* If we just emptied it.. */
|
||||
idExt.unset(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> void DoUnAssociate(T &map, unsigned long id)
|
||||
{
|
||||
/* For each occurence of 'id' (well, only one..it's not a multimap) in 'map'
|
||||
* remove it from the map, take an Extensible* value from the map and remove
|
||||
* 'id' from the list of query IDs attached to it.
|
||||
*/
|
||||
typename T::iterator iter = map.find(id);
|
||||
|
||||
if(iter != map.end())
|
||||
{
|
||||
/* Found a value indexed by 'id', call RemoveFromList()
|
||||
* on it with 'id' to remove 'id' from the list attached
|
||||
* to the value.
|
||||
*/
|
||||
RemoveFromList(iter->second, id);
|
||||
}
|
||||
}
|
||||
|
||||
void OnChannelDelete(Channel* chan)
|
||||
{
|
||||
/* A channel is being destroyed, first we need to check if it has a list of queries associated with it.
|
||||
* Then, if it does, we need to erase each of them from our IdChanMap (idchan) so when the module that
|
||||
* associated them asks to look them up then it gets a NULL result and knows to discard the query.
|
||||
*/
|
||||
AssocIdList* il = idExt.get(chan);
|
||||
|
||||
if (il)
|
||||
{
|
||||
for(AssocIdList::iterator listiter = il->begin(); listiter != il->end(); listiter++)
|
||||
{
|
||||
IdChanMap::iterator iter;
|
||||
|
||||
iter = idchan.find(*listiter);
|
||||
|
||||
if(iter != idchan.end())
|
||||
{
|
||||
if(iter->second != chan)
|
||||
{
|
||||
ServerInstance->Logs->Log("m_sqlutils",DEBUG, "BUG: ID associated with channel %s doesn't have the same Channel* associated with it in the map (erasing anyway)", chan->name.c_str());
|
||||
}
|
||||
idchan.erase(iter);
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerInstance->Logs->Log("m_sqlutils",DEBUG, "BUG: channel %s was extended with sqlutils_queryids but there was nothing matching in the map", chan->name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
idExt.unset(chan);
|
||||
}
|
||||
}
|
||||
|
||||
Version GetVersion()
|
||||
{
|
||||
return Version("Provides some utilities to SQL client modules, such as mapping queries to users and channels", VF_VENDOR);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
MODULE_INIT(ModuleSQLutils)
|
@ -1,143 +0,0 @@
|
||||
/* +------------------------------------+
|
||||
* | Inspire Internet Relay Chat Daemon |
|
||||
* +------------------------------------+
|
||||
*
|
||||
* InspIRCd: (C) 2002-2010 InspIRCd Development Team
|
||||
* See: http://wiki.inspircd.org/Credits
|
||||
*
|
||||
* This program is free but copyrighted software; see
|
||||
* the file COPYING for details.
|
||||
*
|
||||
* ---------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef INSPIRCD_SQLUTILS
|
||||
#define INSPIRCD_SQLUTILS
|
||||
|
||||
#include "modules.h"
|
||||
|
||||
#define SQLUTILAU "SQLutil AssociateUser"
|
||||
#define SQLUTILAC "SQLutil AssociateChan"
|
||||
#define SQLUTILUA "SQLutil UnAssociate"
|
||||
#define SQLUTILGU "SQLutil GetAssocUser"
|
||||
#define SQLUTILGC "SQLutil GetAssocChan"
|
||||
#define SQLUTILSUCCESS "You shouldn't be reading this (success)"
|
||||
|
||||
/** Used to associate an SQL query with a user
|
||||
*/
|
||||
class AssociateUser : public Request
|
||||
{
|
||||
public:
|
||||
/** Query ID
|
||||
*/
|
||||
unsigned long id;
|
||||
/** User
|
||||
*/
|
||||
User* user;
|
||||
|
||||
AssociateUser(Module* s, Module* d, unsigned long i, User* u)
|
||||
: Request(s, d, SQLUTILAU), id(i), user(u)
|
||||
{
|
||||
}
|
||||
|
||||
AssociateUser& S()
|
||||
{
|
||||
Send();
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/** Used to associate an SQL query with a channel
|
||||
*/
|
||||
class AssociateChan : public Request
|
||||
{
|
||||
public:
|
||||
/** Query ID
|
||||
*/
|
||||
unsigned long id;
|
||||
/** Channel
|
||||
*/
|
||||
Channel* chan;
|
||||
|
||||
AssociateChan(Module* s, Module* d, unsigned long i, Channel* u)
|
||||
: Request(s, d, SQLUTILAC), id(i), chan(u)
|
||||
{
|
||||
}
|
||||
|
||||
AssociateChan& S()
|
||||
{
|
||||
Send();
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/** Unassociate a user or class from an SQL query
|
||||
*/
|
||||
class UnAssociate : public Request
|
||||
{
|
||||
public:
|
||||
/** The query ID
|
||||
*/
|
||||
unsigned long id;
|
||||
|
||||
UnAssociate(Module* s, Module* d, unsigned long i)
|
||||
: Request(s, d, SQLUTILUA), id(i)
|
||||
{
|
||||
}
|
||||
|
||||
UnAssociate& S()
|
||||
{
|
||||
Send();
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/** Get the user associated with an SQL query ID
|
||||
*/
|
||||
class GetAssocUser : public Request
|
||||
{
|
||||
public:
|
||||
/** The query id
|
||||
*/
|
||||
unsigned long id;
|
||||
/** The user
|
||||
*/
|
||||
User* user;
|
||||
|
||||
GetAssocUser(Module* s, Module* d, unsigned long i)
|
||||
: Request(s, d, SQLUTILGU), id(i), user(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
GetAssocUser& S()
|
||||
{
|
||||
Send();
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/** Get the channel associated with an SQL query ID
|
||||
*/
|
||||
class GetAssocChan : public Request
|
||||
{
|
||||
public:
|
||||
/** The query id
|
||||
*/
|
||||
unsigned long id;
|
||||
/** The channel
|
||||
*/
|
||||
Channel* chan;
|
||||
|
||||
GetAssocChan(Module* s, Module* d, unsigned long i)
|
||||
: Request(s, d, SQLUTILGC), id(i), chan(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
GetAssocChan& S()
|
||||
{
|
||||
Send();
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
@ -1,604 +0,0 @@
|
||||
/* +------------------------------------+
|
||||
* | Inspire Internet Relay Chat Daemon |
|
||||
* +------------------------------------+
|
||||
*
|
||||
* InspIRCd: (C) 2002-2010 InspIRCd Development Team
|
||||
* See: http://wiki.inspircd.org/Credits
|
||||
*
|
||||
* This program is free but copyrighted software; see
|
||||
* the file COPYING for details.
|
||||
*
|
||||
* ---------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef INSPIRCD_SQLAPI_2
|
||||
#define INSPIRCD_SQLAPI_2
|
||||
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include "modules.h"
|
||||
|
||||
/** Identifiers used to identify Request types
|
||||
*/
|
||||
#define SQLREQID "SQLv2 Request"
|
||||
#define SQLRESID "SQLv2 Result"
|
||||
#define SQLSUCCESS "You shouldn't be reading this (success)"
|
||||
|
||||
/** Defines the error types which SQLerror may be set to
|
||||
*/
|
||||
enum SQLerrorNum { SQL_NO_ERROR, SQL_BAD_DBID, SQL_BAD_CONN, SQL_QSEND_FAIL, SQL_QREPLY_FAIL };
|
||||
|
||||
/** A list of format parameters for an SQLquery object.
|
||||
*/
|
||||
typedef std::deque<std::string> ParamL;
|
||||
|
||||
/** The base class of SQL exceptions
|
||||
*/
|
||||
class SQLexception : public ModuleException
|
||||
{
|
||||
public:
|
||||
SQLexception(const std::string &reason) : ModuleException(reason)
|
||||
{
|
||||
}
|
||||
|
||||
SQLexception() : ModuleException("SQLv2: Undefined exception")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/** An exception thrown when a bad column or row name or id is requested
|
||||
*/
|
||||
class SQLbadColName : public SQLexception
|
||||
{
|
||||
public:
|
||||
SQLbadColName() : SQLexception("SQLv2: Bad column name")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/** SQLerror holds the error state of any SQLrequest or SQLresult.
|
||||
* The error string varies from database software to database software
|
||||
* and should be used to display informational error messages to users.
|
||||
*/
|
||||
class SQLerror
|
||||
{
|
||||
/** The error id
|
||||
*/
|
||||
SQLerrorNum id;
|
||||
/** The error string
|
||||
*/
|
||||
std::string str;
|
||||
public:
|
||||
/** Initialize an SQLerror
|
||||
* @param i The error ID to set
|
||||
* @param s The (optional) error string to set
|
||||
*/
|
||||
SQLerror(SQLerrorNum i = SQL_NO_ERROR, const std::string &s = "")
|
||||
: id(i), str(s)
|
||||
{
|
||||
}
|
||||
|
||||
/** Return the ID of the error
|
||||
*/
|
||||
SQLerrorNum Id()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
/** Set the ID of an error
|
||||
* @param i The new error ID to set
|
||||
* @return the ID which was set
|
||||
*/
|
||||
SQLerrorNum Id(SQLerrorNum i)
|
||||
{
|
||||
id = i;
|
||||
return id;
|
||||
}
|
||||
|
||||
/** Set the error string for an error
|
||||
* @param s The new error string to set
|
||||
*/
|
||||
void Str(const std::string &s)
|
||||
{
|
||||
str = s;
|
||||
}
|
||||
|
||||
/** Return the error string for an error
|
||||
*/
|
||||
const char* Str()
|
||||
{
|
||||
if(str.length())
|
||||
return str.c_str();
|
||||
|
||||
switch(id)
|
||||
{
|
||||
case SQL_NO_ERROR:
|
||||
return "No error";
|
||||
case SQL_BAD_DBID:
|
||||
return "Invalid database ID";
|
||||
case SQL_BAD_CONN:
|
||||
return "Invalid connection";
|
||||
case SQL_QSEND_FAIL:
|
||||
return "Sending query failed";
|
||||
case SQL_QREPLY_FAIL:
|
||||
return "Getting query result failed";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** SQLquery provides a way to represent a query string, and its parameters in a type-safe way.
|
||||
* C++ has no native type-safe way of having a variable number of arguments to a function,
|
||||
* the workaround for this isn't easy to describe simply, but in a nutshell what's really
|
||||
* happening when - from the above example - you do this:
|
||||
*
|
||||
* SQLrequest foo = SQLrequest(this, target, "databaseid", SQLquery("SELECT (foo, bar) FROM rawr WHERE foo = '?' AND bar = ?", "Hello", "42"));
|
||||
*
|
||||
* what's actually happening is functionally this:
|
||||
*
|
||||
* SQLrequest foo = SQLrequest(this, target, "databaseid", query("SELECT (foo, bar) FROM rawr WHERE foo = '?' AND bar = ?").addparam("Hello").addparam("42"));
|
||||
*
|
||||
* with 'query()' returning a reference to an object with a 'addparam()' member function which
|
||||
* in turn returns a reference to that object. There are actually four ways you can create a
|
||||
* SQLrequest..all have their disadvantages and advantages. In the real implementations the
|
||||
* 'query()' function is replaced by the constructor of another class 'SQLquery' which holds
|
||||
* the query string and a ParamL (std::deque<std::string>) of query parameters.
|
||||
* This is essentially the same as the above example except 'addparam()' is replaced by operator,(). The full syntax for this method is:
|
||||
*
|
||||
* SQLrequest foo = SQLrequest(this, target, "databaseid", (SQLquery("SELECT.. ?"), parameter, parameter));
|
||||
*/
|
||||
class SQLquery
|
||||
{
|
||||
public:
|
||||
/** The query 'format string'
|
||||
*/
|
||||
std::string q;
|
||||
/** The query parameter list
|
||||
* There should be one parameter for every ? character
|
||||
* within the format string shown above.
|
||||
*/
|
||||
ParamL p;
|
||||
|
||||
/** Initialize an SQLquery with a given format string only
|
||||
*/
|
||||
SQLquery(const std::string &query)
|
||||
: q(query)
|
||||
{
|
||||
}
|
||||
|
||||
/** Initialize an SQLquery with a format string and parameters.
|
||||
* If you provide parameters, you must initialize the list yourself
|
||||
* if you choose to do it via this method, using std::deque::push_back().
|
||||
*/
|
||||
SQLquery(const std::string &query, const ParamL ¶ms)
|
||||
: q(query), p(params)
|
||||
{
|
||||
}
|
||||
|
||||
/** An overloaded operator for pushing parameters onto the parameter list
|
||||
*/
|
||||
template<typename T> SQLquery& operator,(const T &foo)
|
||||
{
|
||||
p.push_back(ConvToStr(foo));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** An overloaded operator for pushing parameters onto the parameter list.
|
||||
* This has higher precedence than 'operator,' and can save on parenthesis.
|
||||
*/
|
||||
template<typename T> SQLquery& operator%(const T &foo)
|
||||
{
|
||||
p.push_back(ConvToStr(foo));
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/** SQLrequest is sent to the SQL API to command it to run a query and return the result.
|
||||
* You must instantiate this object with a valid SQLquery object and its parameters, then
|
||||
* send it using its Send() method to the module providing the 'SQL' feature. To find this
|
||||
* module, use Server::FindFeature().
|
||||
*/
|
||||
class SQLrequest : public Request
|
||||
{
|
||||
public:
|
||||
/** The fully parsed and expanded query string
|
||||
* This is initialized from the SQLquery parameter given in the constructor.
|
||||
*/
|
||||
SQLquery query;
|
||||
/** The database ID to apply the request to
|
||||
*/
|
||||
std::string dbid;
|
||||
/** True if this is a priority query.
|
||||
* Priority queries may 'queue jump' in the request queue.
|
||||
*/
|
||||
bool pri;
|
||||
/** True if this query has been cancelled; send no response */
|
||||
bool cancel;
|
||||
/** The query ID, assigned by the SQL api.
|
||||
* After your request is processed, this will
|
||||
* be initialized for you by the API to a valid request ID,
|
||||
* except in the case of an error.
|
||||
*/
|
||||
unsigned long id;
|
||||
/** If an error occured, error.id will be any other value than SQL_NO_ERROR.
|
||||
*/
|
||||
SQLerror error;
|
||||
|
||||
/** Initialize an SQLrequest.
|
||||
* For example:
|
||||
*
|
||||
* SQLrequest req = SQLrequest(MyMod, SQLModule, dbid, SQLquery("INSERT INTO ircd_log_actors VALUES('','?')" % nick));
|
||||
*
|
||||
* @param s A pointer to the sending module, where the result should be routed
|
||||
* @param d A pointer to the receiving module, identified as implementing the 'SQL' feature
|
||||
* @param databaseid The database ID to perform the query on. This must match a valid
|
||||
* database ID from the configuration of the SQL module.
|
||||
* @param q A properly initialized SQLquery object.
|
||||
*/
|
||||
SQLrequest(Module* s, Module* d, const std::string &databaseid, const SQLquery &q)
|
||||
: Request(s, d, SQLREQID), query(q), dbid(databaseid), pri(false), id(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Copy constructor - XXX probably shouldn't be needed
|
||||
SQLrequest(const SQLrequest& o)
|
||||
: Request(o.source, o.dest, SQLREQID), query(o.query), dbid(o.dbid), pri(o.pri), cancel(o.cancel),
|
||||
id(o.id), error(o.error) {}
|
||||
|
||||
/** Set the priority of a request.
|
||||
*/
|
||||
void Priority(bool p = true)
|
||||
{
|
||||
pri = p;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This class contains a field's data plus a way to determine if the field
|
||||
* is NULL or not without having to mess around with NULL pointers.
|
||||
*/
|
||||
class SQLfield
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* The data itself
|
||||
*/
|
||||
std::string d;
|
||||
|
||||
/**
|
||||
* If the field was null
|
||||
*/
|
||||
bool null;
|
||||
|
||||
/** Initialize an SQLfield
|
||||
*/
|
||||
SQLfield(const std::string &data = "", bool n = false)
|
||||
: d(data), null(n)
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/** A list of items which make up a row of a result or table (tuple)
|
||||
* This does not include field names.
|
||||
*/
|
||||
typedef std::vector<SQLfield> SQLfieldList;
|
||||
/** A list of items which make up a row of a result or table (tuple)
|
||||
* This also includes the field names.
|
||||
*/
|
||||
typedef std::map<std::string, SQLfield> SQLfieldMap;
|
||||
|
||||
/** SQLresult is a reply to a previous query.
|
||||
* If you send a query to the SQL api, the response will arrive at your
|
||||
* OnRequest method of your module at some later time, depending on the
|
||||
* congestion of the SQL server and complexity of the query. The ID of
|
||||
* this result will match the ID assigned to your original request.
|
||||
* SQLresult contains its own internal cursor (row counter) which is
|
||||
* incremented with each method call which retrieves a single row.
|
||||
*/
|
||||
class SQLresult : public Request
|
||||
{
|
||||
public:
|
||||
/** The original query string passed initially to the SQL API
|
||||
*/
|
||||
std::string query;
|
||||
/** The database ID the query was executed on
|
||||
*/
|
||||
std::string dbid;
|
||||
/**
|
||||
* The error (if any) which occured.
|
||||
* If an error occured the value of error.id will be any
|
||||
* other value than SQL_NO_ERROR.
|
||||
*/
|
||||
SQLerror error;
|
||||
/**
|
||||
* This will match query ID you were given when sending
|
||||
* the request at an earlier time.
|
||||
*/
|
||||
unsigned long id;
|
||||
|
||||
/** Used by the SQL API to instantiate an SQLrequest
|
||||
*/
|
||||
SQLresult(Module* s, Module* d, unsigned long i)
|
||||
: Request(s, d, SQLRESID), id(i)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of rows in the result
|
||||
* Note that if you have perfomed an INSERT
|
||||
* or UPDATE query or other query which will
|
||||
* not return rows, this will return the
|
||||
* number of affected rows, and SQLresult::Cols()
|
||||
* will contain 0. In this case you SHOULD NEVER
|
||||
* access any of the result set rows, as there arent any!
|
||||
* @returns Number of rows in the result set.
|
||||
*/
|
||||
virtual int Rows() = 0;
|
||||
|
||||
/**
|
||||
* Return the number of columns in the result.
|
||||
* If you performed an UPDATE or INSERT which
|
||||
* does not return a dataset, this value will
|
||||
* be 0.
|
||||
* @returns Number of columns in the result set.
|
||||
*/
|
||||
virtual int Cols() = 0;
|
||||
|
||||
/**
|
||||
* Get a string name of the column by an index number
|
||||
* @param column The id number of a column
|
||||
* @returns The column name associated with the given ID
|
||||
*/
|
||||
virtual std::string ColName(int column) = 0;
|
||||
|
||||
/**
|
||||
* Get an index number for a column from a string name.
|
||||
* An exception of type SQLbadColName will be thrown if
|
||||
* the name given is invalid.
|
||||
* @param column The column name to get the ID of
|
||||
* @returns The ID number of the column provided
|
||||
*/
|
||||
virtual int ColNum(const std::string &column) = 0;
|
||||
|
||||
/**
|
||||
* Get a string value in a given row and column
|
||||
* This does not effect the internal cursor.
|
||||
* @returns The value stored at [row,column] in the table
|
||||
*/
|
||||
virtual SQLfield GetValue(int row, int column) = 0;
|
||||
|
||||
/**
|
||||
* Return a list of values in a row, this should
|
||||
* increment an internal counter so you can repeatedly
|
||||
* call it until it returns an empty vector.
|
||||
* This returns a reference to an internal object,
|
||||
* the same object is used for all calls to this function
|
||||
* and therefore the return value is only valid until
|
||||
* you call this function again. It is also invalid if
|
||||
* the SQLresult object is destroyed.
|
||||
* The internal cursor (row counter) is incremented by one.
|
||||
* @returns A reference to the current row's SQLfieldList
|
||||
*/
|
||||
virtual SQLfieldList& GetRow() = 0;
|
||||
|
||||
/**
|
||||
* As above, but return a map indexed by key name.
|
||||
* The internal cursor (row counter) is incremented by one.
|
||||
* @returns A reference to the current row's SQLfieldMap
|
||||
*/
|
||||
virtual SQLfieldMap& GetRowMap() = 0;
|
||||
|
||||
/**
|
||||
* Like GetRow(), but returns a pointer to a dynamically
|
||||
* allocated object which must be explicitly freed. For
|
||||
* portability reasons this must be freed with SQLresult::Free()
|
||||
* The internal cursor (row counter) is incremented by one.
|
||||
* @returns A newly-allocated SQLfieldList
|
||||
*/
|
||||
virtual SQLfieldList* GetRowPtr() = 0;
|
||||
|
||||
/**
|
||||
* As above, but return a map indexed by key name
|
||||
* The internal cursor (row counter) is incremented by one.
|
||||
* @returns A newly-allocated SQLfieldMap
|
||||
*/
|
||||
virtual SQLfieldMap* GetRowMapPtr() = 0;
|
||||
|
||||
/**
|
||||
* Overloaded function for freeing the lists and maps
|
||||
* returned by GetRowPtr or GetRowMapPtr.
|
||||
* @param fm The SQLfieldMap to free
|
||||
*/
|
||||
virtual void Free(SQLfieldMap* fm) = 0;
|
||||
|
||||
/**
|
||||
* Overloaded function for freeing the lists and maps
|
||||
* returned by GetRowPtr or GetRowMapPtr.
|
||||
* @param fl The SQLfieldList to free
|
||||
*/
|
||||
virtual void Free(SQLfieldList* fl) = 0;
|
||||
};
|
||||
|
||||
|
||||
/** SQLHost represents a <database> config line and is useful
|
||||
* for storing in a map and iterating on rehash to see which
|
||||
* <database> tags was added/removed/unchanged.
|
||||
*/
|
||||
class SQLhost
|
||||
{
|
||||
public:
|
||||
std::string id; /* Database handle id */
|
||||
std::string host; /* Database server hostname */
|
||||
std::string ip; /* resolved IP, needed for at least pgsql.so */
|
||||
unsigned int port; /* Database server port */
|
||||
std::string name; /* Database name */
|
||||
std::string user; /* Database username */
|
||||
std::string pass; /* Database password */
|
||||
bool ssl; /* If we should require SSL */
|
||||
|
||||
SQLhost()
|
||||
: id(""), host(""), ip(""), port(0), name(""), user(""), pass(""), ssl(0)
|
||||
{
|
||||
}
|
||||
|
||||
SQLhost(const std::string& i, const std::string& h, unsigned int p, const std::string& n, const std::string& u, const std::string& pa, bool s)
|
||||
: id(i), host(h), ip(""), port(p), name(n), user(u), pass(pa), ssl(s)
|
||||
{
|
||||
}
|
||||
|
||||
/** Overload this to return a correct Data source Name (DSN) for
|
||||
* the current SQL module.
|
||||
*/
|
||||
std::string GetDSN();
|
||||
};
|
||||
|
||||
/** Overload operator== for two SQLhost objects for easy comparison.
|
||||
*/
|
||||
inline bool operator== (const SQLhost& l, const SQLhost& r)
|
||||
{
|
||||
return (l.id == r.id && l.host == r.host && l.port == r.port && l.name == r.name && l.user == r.user && l.pass == r.pass && l.ssl == r.ssl);
|
||||
}
|
||||
/** Overload operator!= for two SQLhost objects for easy comparison.
|
||||
*/
|
||||
inline bool operator!= (const SQLhost& l, const SQLhost& r)
|
||||
{
|
||||
return (l.id != r.id || l.host != r.host || l.port != r.port || l.name != r.name || l.user != r.user || l.pass != r.pass || l.ssl != r.ssl);
|
||||
}
|
||||
|
||||
|
||||
/** QueryQueue, a queue of queries waiting to be executed.
|
||||
* This maintains two queues internally, one for 'priority'
|
||||
* queries and one for less important ones. Each queue has
|
||||
* new queries appended to it and ones to execute are popped
|
||||
* off the front. This keeps them flowing round nicely and no
|
||||
* query should ever get 'stuck' for too long. If there are
|
||||
* queries in the priority queue they will be executed first,
|
||||
* 'unimportant' queries will only be executed when the
|
||||
* priority queue is empty.
|
||||
*
|
||||
* We store lists of SQLrequest's here, by value as we want to avoid storing
|
||||
* any data allocated inside the client module (in case that module is unloaded
|
||||
* while the query is in progress).
|
||||
*
|
||||
* Because we want to work on the current SQLrequest in-situ, we need a way
|
||||
* of accessing the request we are currently processing, QueryQueue::front(),
|
||||
* but that call needs to always return the same request until that request
|
||||
* is removed from the queue, this is what the 'which' variable is. New queries are
|
||||
* always added to the back of one of the two queues, but if when front()
|
||||
* is first called then the priority queue is empty then front() will return
|
||||
* a query from the normal queue, but if a query is then added to the priority
|
||||
* queue then front() must continue to return the front of the *normal* queue
|
||||
* until pop() is called.
|
||||
*/
|
||||
|
||||
class QueryQueue
|
||||
{
|
||||
private:
|
||||
typedef std::deque<SQLrequest*> ReqDeque;
|
||||
|
||||
ReqDeque priority; /* The priority queue */
|
||||
ReqDeque normal; /* The 'normal' queue */
|
||||
enum { PRI, NOR, NON } which; /* Which queue the currently active element is at the front of */
|
||||
|
||||
public:
|
||||
QueryQueue()
|
||||
: which(NON)
|
||||
{
|
||||
}
|
||||
|
||||
void push(SQLrequest *q)
|
||||
{
|
||||
if(q->pri)
|
||||
priority.push_back(q);
|
||||
else
|
||||
normal.push_back(q);
|
||||
}
|
||||
|
||||
void pop()
|
||||
{
|
||||
if((which == PRI) && priority.size())
|
||||
{
|
||||
priority.pop_front();
|
||||
}
|
||||
else if((which == NOR) && normal.size())
|
||||
{
|
||||
normal.pop_front();
|
||||
}
|
||||
|
||||
/* Reset this */
|
||||
which = NON;
|
||||
|
||||
/* Silently do nothing if there was no element to pop() */
|
||||
}
|
||||
|
||||
SQLrequest* front()
|
||||
{
|
||||
switch(which)
|
||||
{
|
||||
case PRI:
|
||||
return priority.front();
|
||||
case NOR:
|
||||
return normal.front();
|
||||
default:
|
||||
if(priority.size())
|
||||
{
|
||||
which = PRI;
|
||||
return priority.front();
|
||||
}
|
||||
|
||||
if(normal.size())
|
||||
{
|
||||
which = NOR;
|
||||
return normal.front();
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::pair<int, int> size()
|
||||
{
|
||||
return std::make_pair(priority.size(), normal.size());
|
||||
}
|
||||
|
||||
int totalsize()
|
||||
{
|
||||
return priority.size() + normal.size();
|
||||
}
|
||||
|
||||
void PurgeModule(Module* mod)
|
||||
{
|
||||
DoPurgeModule(mod, priority);
|
||||
DoPurgeModule(mod, normal);
|
||||
}
|
||||
|
||||
private:
|
||||
void DoPurgeModule(Module* mod, ReqDeque& q)
|
||||
{
|
||||
ReqDeque::iterator iter = q.begin();
|
||||
while (iter != q.end())
|
||||
{
|
||||
if((**iter).source == mod)
|
||||
{
|
||||
if (*iter == front())
|
||||
{
|
||||
/* It's the currently active query.. :x */
|
||||
(**iter).cancel = true;
|
||||
iter++;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* It hasn't been executed yet..just remove it */
|
||||
iter = q.erase(iter);
|
||||
}
|
||||
}
|
||||
else
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif
|
@ -1,103 +0,0 @@
|
||||
/* +------------------------------------+
|
||||
* | Inspire Internet Relay Chat Daemon |
|
||||
* +------------------------------------+
|
||||
*
|
||||
* InspIRCd: (C) 2002-2010 InspIRCd Development Team
|
||||
* See: http://wiki.inspircd.org/Credits
|
||||
*
|
||||
* This program is free but copyrighted software; see
|
||||
* the file COPYING for details.
|
||||
*
|
||||
* ---------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "inspircd.h"
|
||||
#include "m_sqlv2.h"
|
||||
|
||||
class ModuleTestClient : public Module
|
||||
{
|
||||
private:
|
||||
|
||||
|
||||
public:
|
||||
ModuleTestClient()
|
||||
{
|
||||
Implementation eventlist[] = { I_OnBackgroundTimer };
|
||||
ServerInstance->Modules->Attach(eventlist, this, 1);
|
||||
}
|
||||
|
||||
|
||||
virtual Version GetVersion()
|
||||
{
|
||||
return Version("SQL test module", VF_VENDOR);
|
||||
}
|
||||
|
||||
virtual void OnBackgroundTimer(time_t)
|
||||
{
|
||||
ServiceProvider* prov = ServerInstance->Modules->FindService(SERVICE_DATA, "SQL");
|
||||
if (!prov)
|
||||
return;
|
||||
Module* target = prov->creator;
|
||||
|
||||
if(target)
|
||||
{
|
||||
SQLrequest foo = SQLrequest(this, target, "foo",
|
||||
SQLquery("UPDATE rawr SET foo = '?' WHERE bar = 42") % ServerInstance->Time());
|
||||
|
||||
foo.Send();
|
||||
if (foo.cancel)
|
||||
{
|
||||
ServerInstance->Logs->Log("m_testclient.so", DEBUG, "SQLrequest failed: %s", foo.error.Str());
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerInstance->Logs->Log("m_testclient.so", DEBUG, "Sent query, got given ID %lu", foo.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnRequest(Request& request)
|
||||
{
|
||||
if(strcmp(SQLRESID, request.id) == 0)
|
||||
{
|
||||
ServerInstance->Logs->Log("m_testclient.so", DEBUG, "Got SQL result (%s)", request.id);
|
||||
|
||||
SQLresult* res = (SQLresult*)&request;
|
||||
|
||||
if (res->error.Id() == SQL_NO_ERROR)
|
||||
{
|
||||
if(res->Cols())
|
||||
{
|
||||
ServerInstance->Logs->Log("m_testclient.so", DEBUG, "Got result with %d rows and %d columns", res->Rows(), res->Cols());
|
||||
|
||||
for (int r = 0; r < res->Rows(); r++)
|
||||
{
|
||||
ServerInstance->Logs->Log("m_testclient.so", DEBUG, "Row %d:", r);
|
||||
|
||||
for(int i = 0; i < res->Cols(); i++)
|
||||
{
|
||||
ServerInstance->Logs->Log("m_testclient.so", DEBUG, "\t[%s]: %s", res->ColName(i).c_str(), res->GetValue(r, i).d.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerInstance->Logs->Log("m_testclient.so", DEBUG, "%d rows affected in query", res->Rows());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerInstance->Logs->Log("m_testclient.so", DEBUG, "SQLrequest failed: %s", res->error.Str());
|
||||
}
|
||||
}
|
||||
|
||||
ServerInstance->Logs->Log("m_testclient.so", DEBUG, "Got unsupported API version string: %s", request.id);
|
||||
}
|
||||
|
||||
virtual ~ModuleTestClient()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
MODULE_INIT(ModuleTestClient)
|
||||
|
154
src/modules/sql.h
Normal file
154
src/modules/sql.h
Normal file
@ -0,0 +1,154 @@
|
||||
/* +------------------------------------+
|
||||
* | Inspire Internet Relay Chat Daemon |
|
||||
* +------------------------------------+
|
||||
*
|
||||
* InspIRCd: (C) 2002-2010 InspIRCd Development Team
|
||||
* See: http://wiki.inspircd.org/Credits
|
||||
*
|
||||
* This program is free but copyrighted software; see
|
||||
* the file COPYING for details.
|
||||
*
|
||||
* ---------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef INSPIRCD_SQLAPI_3
|
||||
#define INSPIRCD_SQLAPI_3
|
||||
|
||||
/** Defines the error types which SQLerror may be set to
|
||||
*/
|
||||
enum SQLerrorNum { SQL_BAD_DBID, SQL_BAD_CONN, SQL_QSEND_FAIL, SQL_QREPLY_FAIL };
|
||||
|
||||
/** A list of format parameters for an SQLquery object.
|
||||
*/
|
||||
typedef std::vector<std::string> ParamL;
|
||||
|
||||
typedef std::map<std::string, std::string> ParamM;
|
||||
|
||||
/**
|
||||
* Result of an SQL query. Only valid inside OnResult
|
||||
*/
|
||||
class SQLResult : public interfacebase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Return the number of rows in the result.
|
||||
*
|
||||
* Note that if you have perfomed an INSERT or UPDATE query or other
|
||||
* query which will not return rows, this will return the number of
|
||||
* affected rows. In this case you SHOULD NEVER access any of the result
|
||||
* set rows, as there aren't any!
|
||||
* @returns Number of rows in the result set.
|
||||
*/
|
||||
virtual int Rows() = 0;
|
||||
|
||||
/**
|
||||
* Return a single row (result of the query). The internal row counter
|
||||
* is incremented by one.
|
||||
*
|
||||
* @param result Storage for the result data.
|
||||
* @returns true if there was a row, false if no row exists (end of
|
||||
* iteration)
|
||||
*/
|
||||
virtual bool GetRow(std::vector<std::string>& result) = 0;
|
||||
};
|
||||
|
||||
/** SQLerror holds the error state of a request.
|
||||
* The error string varies from database software to database software
|
||||
* and should be used to display informational error messages to users.
|
||||
*/
|
||||
class SQLerror
|
||||
{
|
||||
public:
|
||||
/** The error id
|
||||
*/
|
||||
SQLerrorNum id;
|
||||
|
||||
/** The error string
|
||||
*/
|
||||
std::string str;
|
||||
|
||||
/** Initialize an SQLerror
|
||||
* @param i The error ID to set
|
||||
* @param s The (optional) error string to set
|
||||
*/
|
||||
SQLerror(SQLerrorNum i, const std::string &s = "")
|
||||
: id(i), str(s)
|
||||
{
|
||||
}
|
||||
|
||||
/** Return the error string for an error
|
||||
*/
|
||||
const char* Str()
|
||||
{
|
||||
if(str.length())
|
||||
return str.c_str();
|
||||
|
||||
switch(id)
|
||||
{
|
||||
case SQL_BAD_DBID:
|
||||
return "Invalid database ID";
|
||||
case SQL_BAD_CONN:
|
||||
return "Invalid connection";
|
||||
case SQL_QSEND_FAIL:
|
||||
return "Sending query failed";
|
||||
case SQL_QREPLY_FAIL:
|
||||
return "Getting query result failed";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Object representing an SQL query. This should be allocated on the heap and
|
||||
* passed to an SQLProvider, which will free it when the query is complete or
|
||||
* when the querying module is unloaded.
|
||||
*
|
||||
* You should store whatever information is needed to have the callbacks work in
|
||||
* this object (UID of user, channel name, etc).
|
||||
*/
|
||||
class SQLQuery : public classbase
|
||||
{
|
||||
public:
|
||||
ModuleRef creator;
|
||||
const std::string dbid;
|
||||
const std::string query;
|
||||
|
||||
SQLQuery(Module* Creator, const std::string& db, const std::string& q)
|
||||
: creator(Creator), dbid(db), query(q) {}
|
||||
virtual ~SQLQuery() {}
|
||||
|
||||
virtual void OnResult(SQLResult& result) = 0;
|
||||
/**
|
||||
* Called when the query fails
|
||||
*/
|
||||
virtual void OnError(SQLerror& error) { }
|
||||
};
|
||||
|
||||
/**
|
||||
* Provider object for SQL servers
|
||||
*/
|
||||
class SQLProvider : public DataProvider
|
||||
{
|
||||
public:
|
||||
/** Submit an asynchronous SQL request
|
||||
* @param dbid The database ID to apply the request to
|
||||
* @param query The query string
|
||||
* @param callback The callback that the result is sent to
|
||||
*/
|
||||
virtual void submit(SQLQuery* query) = 0;
|
||||
|
||||
/** Format a parameterized query string using proper SQL escaping.
|
||||
* @param q The query string, with '?' parameters
|
||||
* @param p The parameters to fill in in the '?' slots
|
||||
*/
|
||||
virtual std::string FormatQuery(std::string q, ParamL p);
|
||||
|
||||
/** Format a parameterized query string using proper SQL escaping.
|
||||
* @param q The query string, with '$foo' parameters
|
||||
* @param p The map to look up parameters in
|
||||
*/
|
||||
virtual std::string FormatQuery(std::string q, ParamM p);
|
||||
};
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user