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:
danieldg 2010-03-06 16:58:13 +00:00
parent a1fab90c80
commit 410de52634
7 changed files with 326 additions and 1359 deletions

View File

@ -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)

View File

@ -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> &parameters, 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> &parameters, 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()
{

View File

@ -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)

View File

@ -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

View File

@ -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 &params)
: 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

View File

@ -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
View 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