]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/modules/extra/m_sqloper.cpp
Remove InspIRCd* parameters and fields
[user/henk/code/inspircd.git] / src / modules / extra / m_sqloper.cpp
index 08ac72bcf564bceb14a686d708d5ab06200ecb05..47aa8d1fd2b32afca2c93f4b36b0f5b2b7068263 100644 (file)
  *       | Inspire Internet Relay Chat Daemon |
  *       +------------------------------------+
  *
- *  InspIRCd is copyright (C) 2002-2004 ChatSpike-Dev.
- *                       E-mail:
- *                <brain@chatspike.net>
- *               <Craig@chatspike.net>
- *     
- * Written by Craig Edwards, Craig McLure, and others.
+ *  InspIRCd: (C) 2002-2009 InspIRCd Development Team
+ * See: http://wiki.inspircd.org/Credits
+ *
  * This program is free but copyrighted software; see
- *            the file COPYING for details.
+ *         the file COPYING for details.
  *
  * ---------------------------------------------------
  */
 
-using namespace std;
-
-#include <stdio.h>
-#include <string>
-#include <stdlib.h>
-#include <time.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <string.h>
-#include <unistd.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <poll.h>
-#include "users.h"
-#include "channels.h"
-#include "modules.h"
 #include "inspircd.h"
-#include "configreader.h"
-#include "helperfuncs.h"
-#include "m_sql.h"
+#include "m_sqlv2.h"
+#include "m_sqlutils.h"
+#include "m_hash.h"
 #include "commands/cmd_oper.h"
 
 /* $ModDesc: Allows storage of oper credentials in an SQL table */
+/* $ModDep: m_sqlv2.h m_sqlutils.h m_hash.h */
 
-/* Required for the FOREACH_MOD alias (OnOper event) */
-extern int MODCOUNT;
-extern ServerConfig* Config;
-extern std::vector<Module*> modules;
-extern std::vector<ircd_module*> factory;
+typedef std::map<irc::string, Module*> hashymodules;
 
 class ModuleSQLOper : public Module
 {
-       Server* Srv;
-       ConfigReader* Conf;
-       unsigned long dbid;
-       Module* SQLModule;
+       Module* SQLutils;
+       std::string databaseid;
+       irc::string hashtype;
+       hashymodules hashers;
+       bool diduseiface;
+       parameterlist names;
+
+public:
+       ModuleSQLOper()
+               {
+               ServerInstance->Modules->UseInterface("SQLutils");
+               ServerInstance->Modules->UseInterface("SQL");
+               ServerInstance->Modules->UseInterface("HashRequest");
+
+               OnRehash(NULL);
+
+               diduseiface = false;
+
+               /* Find all modules which implement the interface 'HashRequest' */
+               modulelist* ml = ServerInstance->Modules->FindInterface("HashRequest");
+
+               /* Did we find any modules? */
+               if (ml)
+               {
+                       /* Yes, enumerate them all to find out the hashing algorithm name */
+                       for (modulelist::iterator m = ml->begin(); m != ml->end(); m++)
+                       {
+                               /* Make a request to it for its name, its implementing
+                                * HashRequest so we know its safe to do this
+                                */
+                               std::string name = HashNameRequest(this, *m).Send();
+                               /* Build a map of them */
+                               hashers[name.c_str()] = *m;
+                               names.push_back(name);
+                       }
+                       /* UseInterface doesn't do anything if there are no providers, so we'll have to call it later if a module gets loaded later on. */
+                       diduseiface = true;
+                       ServerInstance->Modules->UseInterface("HashRequest");
+               }
+
+               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.");
 
- public:
-       bool ReadConfig()
+               Implementation eventlist[] = { I_OnRequest, I_OnRehash, I_OnPreCommand, I_OnLoadModule };
+               ServerInstance->Modules->Attach(eventlist, this, 3);
+       }
+
+       bool OneOfMatches(const char* host, const char* ip, const char* hostlist)
        {
-               dbid = Conf->ReadInteger("sqloper","dbid",0,true);      // database id of a database configured in m_sql (see m_sql config)
-               SQLModule = Srv->FindModule("m_sql.so");
-               if (!SQLModule)
-                       Srv->Log(DEFAULT,"WARNING: m_sqloper.so could not initialize because m_sql.so is not loaded. Load the module and rehash your server.");
-               return (SQLModule);
+               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))
+                       {
+                               return true;
+                       }
+               }
+               return false;
        }
 
-       ModuleSQLOper(Server* Me)
-               : Module::Module(Me)
+       virtual void OnLoadModule(Module* mod, const std::string& name)
        {
-               Srv = Me;
-               Conf = new ConfigReader();
-               ReadConfig();
+               if (ServerInstance->Modules->ModuleHasInterface(mod, "HashRequest"))
+               {
+                       ServerInstance->Logs->Log("m_sqloper",DEBUG, "Post-load registering hasher: %s", name.c_str());
+                       std::string sname = HashNameRequest(this, mod).Send();
+                       hashers[sname.c_str()] = mod;
+                       names.push_back(sname);
+                       if (!diduseiface)
+                       {
+                               ServerInstance->Modules->UseInterface("HashRequest");
+                               diduseiface = true;
+                       }
+               }
        }
 
-       virtual void OnRehash(const std::string &parameter)
+       virtual ~ModuleSQLOper()
        {
-               DELETE(Conf);
-               Conf = new ConfigReader();
-               ReadConfig();
+               ServerInstance->Modules->DoneWithInterface("SQL");
+               ServerInstance->Modules->DoneWithInterface("SQLutils");
+               if (diduseiface)
+                       ServerInstance->Modules->DoneWithInterface("HashRequest");
        }
 
-       void Implements(char* List)
+
+       virtual void OnRehash(User* user)
        {
-               List[I_OnRehash] = List[I_OnPreCommand] = 1;
+               ConfigReader Conf;
+
+               databaseid = Conf.ReadValue("sqloper", "dbid", 0); /* Database ID of a database configured for the service provider module */
+               hashtype = assign(Conf.ReadValue("sqloper", "hash", 0));
        }
 
-       virtual int OnPreCommand(const std::string &command, char **parameters, int pcnt, userrec *user, bool validated)
+       virtual ModResult OnPreCommand(std::string &command, std::vector<std::string> &parameters, User *user, bool validated, const std::string &original_line)
        {
-               if ((command == "OPER") && (validated))
+               if ((validated) && (command == "OPER"))
                {
-                       if (LookupOper(parameters[0],parameters[1],user))
-                               return 1;
+                       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 0;
+               return MOD_RES_PASSTHRU;
        }
 
-       bool LookupOper(const std::string &s_username, const std::string &s_password, userrec* user)
+       bool LookupOper(User* user, const std::string &username, const std::string &password)
        {
-               bool found = false;
+               Module* target;
 
-               // is the sql module loaded? If not, we don't attempt to do anything.
-               if (!SQLModule)
-                       return false;
+               target = ServerInstance->Modules->FindFeature("SQL");
+
+               if (target)
+               {
+                       hashymodules::iterator x = hashers.find(hashtype);
+                       if (x == hashers.end())
+                               return false;
 
-               // sanitize the password (we dont want any mysql insertion exploits!)
-               std::string username = SQLQuery::Sanitise(s_username);
-               std::string password = SQLQuery::Sanitise(s_password);
+                       /* Reset hash module first back to MD5 standard state */
+                       HashResetRequest(this, x->second).Send();
+                       /* Make an MD5 hash of the password for using in the query */
+                       std::string md5_pass_hash = HashSumRequest(this, x->second, password.c_str()).Send();
 
-               // Create a request containing the SQL query and send it to m_sql.so
-               SQLRequest* query = new SQLRequest(SQL_RESULT,dbid,"SELECT username,password,hostname,type FROM ircd_opers WHERE username='"+username+"' AND password=md5('"+password+"')");
-               Request queryrequest((char*)query, this, SQLModule);
-               SQLResult* result = (SQLResult*)queryrequest.Send();
+                       /* 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);
 
-               // Did we get "OK" as a result?
-               if (result->GetType() == SQL_OK)
+                       if (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();
+
+                               user->Extend("oper_user", strdup(username.c_str()));
+                               user->Extend("oper_pass", strdup(password.c_str()));
+
+                               return true;
+                       }
+                       else
+                       {
+                               return false;
+                       }
+               }
+               else
                {
-                       Srv->Log(DEBUG,"An SQL based oper exists");
-                       // if we did, this means we may now request a row... there should be only one row for each user, so,
-                       // we don't need to loop to fetch multiple rows.
-                       SQLRequest* rowrequest = new SQLRequest(SQL_ROW,dbid,"");
-                       Request rowquery((char*)rowrequest, this, SQLModule);
-                       SQLResult* rowresult = (SQLResult*)rowquery.Send();
-
-                       // did we get a row? If we did, we can now do something with the fields
-                       if (rowresult->GetType() == SQL_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;
+               }
+       }
+
+       virtual const char* OnRequest(Request* request)
+       {
+               if (strcmp(SQLRESID, request->GetId()) == 0)
+               {
+                       SQLresult* res = static_cast<SQLresult*>(request);
+
+                       User* user = GetAssocUser(this, SQLutils, res->id).S().user;
+                       UnAssociate(this, SQLutils, res->id).S();
+
+                       char* tried_user = NULL;
+                       char* tried_pass = NULL;
+
+                       user->GetExt("oper_user", tried_user);
+                       user->GetExt("oper_pass", tried_pass);
+
+                       if (user)
                        {
-                               if (rowresult->GetField("username") == username)
+                               if (res->error.Id() == SQL_NO_ERROR)
                                {
-                                       found = true;
-                                       // oper up the user.
-                                       
-                                       for (int j =0; j < Conf->Enumerate("type"); j++)
+                                       if (res->Rows())
                                        {
-                                               std::string TypeName = Conf->ReadValue("type","name",j);
-                                               Srv->Log(DEBUG,"Scanning opertype: "+TypeName);
-                                               std::string pattern = std::string(user->ident) + "@" + std::string(user->host);
-                                                       
-                                               if((TypeName == rowresult->GetField("type")) && OneOfMatches(pattern.c_str(), rowresult->GetField("hostname").c_str()))
+                                               /* 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())
                                                {
-                                                       /* found this oper's opertype */
-                                                       Srv->Log(DEBUG,"Host and type match: "+TypeName+" "+rowresult->GetField("type"));
-                                                       std::string HostName = Conf->ReadValue("type","host",j);
-                                                       
-                                                       if(HostName != "")
-                                                               Srv->ChangeHost(user,HostName);
-                                                               
-                                                       strlcpy(user->oper,rowresult->GetField("type").c_str(),NICKMAX-1);
-                                                       WriteOpers("*** %s (%s@%s) is now an IRC operator of type %s",user->nick,user->ident,user->host,rowresult->GetField("type").c_str());
-                                                       WriteServ(user->fd,"381 %s :You are now an IRC operator of type %s",user->nick,rowresult->GetField("type").c_str());
-                                                       if(!strchr(user->modes,'o'))
+                                                       if (OperUser(user, row["hostname"].d, row["type"].d))
                                                        {
-                                                               strcat(user->modes,"o");
-                                                               WriteServ(user->fd,"MODE %s :+o",user->nick);
-                                                               FOREACH_MOD(I_OnOper,OnOper(user,rowresult->GetField("type")));
-                                                               AddOper(user);
-                                                               FOREACH_MOD(I_OnPostOper,OnPostOper(user,rowresult->GetField("type")));
-                                                               log(DEFAULT,"OPER: %s!%s@%s opered as type: %s",user->nick,user->ident,user->host,rowresult->GetField("type").c_str());
+                                                               /* If/when one of the rows matches, stop checking and return */
+                                                               return SQLSUCCESS;
                                                        }
-                                                               
-                                                       break;
+                                                       if (tried_user && tried_pass)
+                                                       {
+                                                               LoginFail(user, tried_user, tried_pass);
+                                                               free(tried_user);
+                                                               free(tried_pass);
+                                                               user->Shrink("oper_user");
+                                                               user->Shrink("oper_pass");
+                                                       }
+                                               }
+                                       }
+                                       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);
+                                                       free(tried_user);
+                                                       free(tried_pass);
+                                                       user->Shrink("oper_user");
+                                                       user->Shrink("oper_pass");
                                                }
                                        }
                                }
-                               
-                               DELETE(rowresult);
-                       }
-                       else
-                       {
-                               // we didn't have a row.
-                               found = false;
+                               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);
+                                               free(tried_user);
+                                               free(tried_pass);
+                                               user->Shrink("oper_user");
+                                               user->Shrink("oper_pass");
+                                       }
+
+                               }
                        }
-                       
-                       DELETE(rowrequest);
-                       DELETE(result);
+
+                       return SQLSUCCESS;
+               }
+
+               return NULL;
+       }
+
+       void LoginFail(User* user, const std::string &username, const std::string &pass)
+       {
+               Command* oper_command = ServerInstance->Parser->GetHandler("OPER");
+
+               if (oper_command)
+               {
+                       std::vector<std::string> params;
+                       params.push_back(username);
+                       params.push_back(pass);
+                       oper_command->Handle(params, user);
                }
                else
                {
-                       // the query was bad
-                       found = false;
+                       ServerInstance->Logs->Log("m_sqloper",DEBUG, "BUG: WHAT?! Why do we have no OPER command?!");
                }
-               query->SetQueryType(SQL_DONE);
-               query->SetConnID(dbid);
-               Request donerequest((char*)query, this, SQLModule);
-               donerequest.Send();
-               DELETE(query);
-               return found;
        }
 
-       virtual ~ModuleSQLOper()
+       bool OperUser(User* user, const std::string &pattern, const std::string &type)
        {
-               DELETE(Conf);
+               ConfigReader Conf;
+
+               for (int j = 0; j < Conf.Enumerate("type"); j++)
+               {
+                       std::string tname = Conf.ReadValue("type","name",j);
+                       std::string hostname(user->ident);
+
+                       hostname.append("@").append(user->host);
+
+                       if ((tname == type) && OneOfMatches(hostname.c_str(), user->GetIPString(), pattern.c_str()))
+                       {
+                               /* Opertype and host match, looks like this is it. */
+                               std::string operhost = Conf.ReadValue("type", "host", j);
+
+                               if (operhost.size())
+                                       user->ChangeDisplayedHost(operhost.c_str());
+
+                               user->Oper(type, tname);
+                               return true;
+                       }
+               }
+
+               return false;
        }
-       
+
        virtual Version GetVersion()
        {
-               return Version(1,0,0,1,VF_VENDOR);
+               return Version("Allows storage of oper credentials in an SQL table", VF_VENDOR, API_VERSION);
        }
-       
-};
 
-class ModuleSQLOperFactory : public ModuleFactory
-{
- public:
-       ModuleSQLOperFactory()
-       {
-       }
-       
-       ~ModuleSQLOperFactory()
-       {
-       }
-       
-       virtual Module * CreateModule(Server* Me)
-       {
-               return new ModuleSQLOper(Me);
-       }
-       
 };
 
-
-extern "C" void * init_module( void )
-{
-       return new ModuleSQLOperFactory;
-}
+MODULE_INIT(ModuleSQLOper)