]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/modules/extra/m_sqloper.cpp
A few more I missed.
[user/henk/code/inspircd.git] / src / modules / extra / m_sqloper.cpp
index 4b09ac26eb377607593acf530424192b348dda1f..520869e210b3074d1fa02ffe3f6f0a34a7a19d24 100644 (file)
@@ -1 +1,283 @@
-/*       +------------------------------------+\r *       | Inspire Internet Relay Chat Daemon |\r *       +------------------------------------+\r *\r *  InspIRCd: (C) 2002-2007 InspIRCd Development Team\r * See: http://www.inspircd.org/wiki/index.php/Credits\r *\r * This program is free but copyrighted software; see\r *            the file COPYING for details.\r *\r * ---------------------------------------------------\r */\r\r#include "inspircd.h"\r#include "users.h"\r#include "channels.h"\r#include "modules.h"\r#include "configreader.h"\r\r#include "m_sqlv2.h"\r#include "m_sqlutils.h"\r#include "m_hash.h"\r#include "commands/cmd_oper.h"\r\r/* $ModDesc: Allows storage of oper credentials in an SQL table */\r/* $ModDep: m_sqlv2.h m_sqlutils.h */\r\rclass ModuleSQLOper : public Module\r{\r   Module* SQLutils;\r      Module* HashModule;\r    std::string databaseid;\r\rpublic:\r       ModuleSQLOper(InspIRCd* Me)\r    : Module::Module(Me)\r   {\r              ServerInstance->UseInterface("SQLutils");\r              ServerInstance->UseInterface("SQL");\r           ServerInstance->UseInterface("HashRequest");\r\r          /* Attempt to locate the md5 service provider, bail if we can't find it */\r             HashModule = ServerInstance->FindModule("m_md5.so");\r           if (!HashModule)\r                       throw ModuleException("Can't find m_md5.so. Please load m_md5.so before m_sqloper.so.");\r\r              SQLutils = ServerInstance->FindModule("m_sqlutils.so");\r                if (!SQLutils)\r                 throw ModuleException("Can't find m_sqlutils.so. Please load m_sqlutils.so before m_sqloper.so.");\r\r            OnRehash(NULL,"");\r     }\r\r     virtual ~ModuleSQLOper()\r       {\r              ServerInstance->DoneWithInterface("SQL");\r              ServerInstance->DoneWithInterface("SQLutils");\r         ServerInstance->DoneWithInterface("HashRequest");\r      }\r\r     void Implements(char* List)\r    {\r              List[I_OnRequest] = List[I_OnRehash] = List[I_OnPreCommand] = 1;\r       }\r\r     virtual void OnRehash(userrec* user, const std::string &parameter)\r     {\r              ConfigReader Conf(ServerInstance);\r             \r               databaseid = Conf.ReadValue("sqloper", "dbid", 0); /* Database ID of a database configured for the service provider module */\r  }\r\r     virtual int OnPreCommand(const std::string &command, const char** parameters, int pcnt, userrec *user, bool validated, const std::string &original_line)\r       {\r              if ((validated) && (command == "OPER"))\r                {\r                      if (LookupOper(user, parameters[0], parameters[1]))\r                    {       \r                               /* Returning true here just means the query is in progress, or on it's way to being\r                             * in progress. Nothing about the /oper actually being successful..\r                             * If the oper lookup fails later, we pass the command to the original handler\r                          * for /oper by calling its Handle method directly.\r                             */\r                            return 1;\r                      }\r              }\r              return 0;\r      }\r\r     bool LookupOper(userrec* user, const std::string &username, const std::string &password)\r       {\r              Module* target;\r                \r               target = ServerInstance->FindFeature("SQL");\r\r          if (target)\r            {\r                      /* Reset hash module first back to MD5 standard state */\r                       HashResetRequest(this, HashModule).Send();\r                     /* Make an MD5 hash of the password for using in the query */\r                  std::string md5_pass_hash = HashSumRequest(this, HashModule, password.c_str()).Send();\r\r                        /* We generate our own MD5 sum here because some database providers (e.g. SQLite) dont have a builtin md5 function,\r                     * also hashing it in the module and only passing a remote query containing a hash is more secure.\r                      */\r\r                   SQLrequest req = SQLreq(this, target, databaseid, "SELECT username, password, hostname, type FROM ircd_opers WHERE username = '?' AND password='?'", username, md5_pass_hash);\r                 \r                       if (req.Send())\r                        {\r                              /* When we get the query response from the service provider we will be given an ID to play with,\r                                * just an ID number which is unique to this query. We need a way of associating that ID with a userrec\r                                 * so we insert it into a map mapping the IDs to users.\r                                 * Thankfully m_sqlutils provides this, it will associate a ID with a user or channel, and if the user quits it removes the\r                             * association. This means that if the user quits during a query we will just get a failed lookup from m_sqlutils - telling\r                             * us to discard the query.\r                             */\r                            AssociateUser(this, SQLutils, req.id, user).Send();\r\r                           user->Extend("oper_user", strdup(username.c_str()));\r                           user->Extend("oper_pass", strdup(password.c_str()));\r                                   \r                               return true;\r                   }\r                      else\r                   {\r                              return false;\r                  }\r              }\r              else\r           {\r                      ServerInstance->Log(SPARSE, "WARNING: Couldn't find SQL provider module. NOBODY will be able to oper up unless their o:line is statically configured");\r                        return false;\r          }\r      }\r      \r       virtual char* OnRequest(Request* request)\r      {\r              if (strcmp(SQLRESID, request->GetId()) == 0)\r           {\r                      SQLresult* res = static_cast<SQLresult*>(request);\r\r                    userrec* user = GetAssocUser(this, SQLutils, res->id).S().user;\r                        UnAssociate(this, SQLutils, res->id).S();\r\r                     char* tried_user = NULL;\r                       char* tried_pass = NULL;\r\r                      user->GetExt("oper_user", tried_user);\r                 user->GetExt("oper_pass", tried_pass);\r                 \r                       if (user)\r                      {\r                              if (res->error.Id() == NO_ERROR)\r                               {\r                                      if (res->Rows())\r                                       {\r                                              /* We got a row in the result, this means there was a record for the oper..\r                                             * now we just need to check if their host matches, and if it does then\r                                                 * oper them up.\r                                                * \r                                             * We now (previous versions of the module didn't) support multiple SQL\r                                                 * rows per-oper in the same way the config file does, all rows will be tried\r                                           * until one is found which matches. This is useful to define several different\r                                                 * hosts for a single oper.\r                                             * \r                                             * The for() loop works as SQLresult::GetRowMap() returns an empty map when there\r                                               * are no more rows to return.\r                                          */\r                                            \r                                               for (SQLfieldMap& row = res->GetRowMap(); row.size(); row = res->GetRowMap())\r                                          {                                                       \r                                                       if (OperUser(user, row["username"].d, row["password"].d, row["hostname"].d, row["type"].d))\r                                                    {\r                                                              /* If/when one of the rows matches, stop checking and return */\r                                                                return SQLSUCCESS;\r                                                     }\r                                                      if (tried_user && tried_pass)\r                                                  {\r                                                              LoginFail(user, tried_user, tried_pass);\r                                                               free(tried_user);\r                                                              free(tried_pass);\r                                                              user->Shrink("oper_user");\r                                                             user->Shrink("oper_pass");\r                                                     }\r                                              }\r                                      }\r                                      else\r                                   {\r                                              /* No rows in result, this means there was no oper line for the user,\r                                           * we should have already checked the o:lines so now we need an\r                                                 * "insufficient awesomeness" (invalid credentials) error\r                                               */\r                                            if (tried_user && tried_pass)\r                                          {\r                                                      LoginFail(user, tried_user, tried_pass);\r                                                       free(tried_user);\r                                                      free(tried_pass);\r                                                      user->Shrink("oper_user");\r                                                     user->Shrink("oper_pass");\r                                             }\r                                      }\r                              }\r                              else\r                           {\r                                      /* This one shouldn't happen, the query failed for some reason.\r                                         * We have to fail the /oper request and give them the same error\r                                       * as above.\r                                    */\r                                    if (tried_user && tried_pass)\r                                  {\r                                              LoginFail(user, tried_user, tried_pass);\r                                               free(tried_user);\r                                              free(tried_pass);\r                                              user->Shrink("oper_user");\r                                             user->Shrink("oper_pass");\r                                     }\r\r                             }\r                      }\r              \r                       return SQLSUCCESS;\r             }\r\r             return NULL;\r   }\r\r     void LoginFail(userrec* user, const std::string &username, const std::string &pass)\r    {\r              command_t* oper_command = ServerInstance->Parser->GetHandler("OPER");\r\r         if (oper_command)\r              {\r                      const char* params[] = { username.c_str(), pass.c_str() };\r                     oper_command->Handle(params, 2, user);\r         }\r              else\r           {\r                      ServerInstance->Log(DEBUG, "BUG: WHAT?! Why do we have no OPER command?!");\r            }\r      }\r\r     bool OperUser(userrec* user, const std::string &username, const std::string &password, const std::string &pattern, const std::string &type)\r    {\r              ConfigReader Conf(ServerInstance);\r             \r               for (int j = 0; j < Conf.Enumerate("type"); j++)\r               {\r                      std::string tname = Conf.ReadValue("type","name",j);\r                   std::string hostname(user->ident);\r\r                    hostname.append("@").append(user->host);\r                                                       \r                       if ((tname == type) && OneOfMatches(hostname.c_str(), user->GetIPString(), pattern.c_str()))\r                   {\r                              /* Opertype and host match, looks like this is it. */\r                          std::string operhost = Conf.ReadValue("type", "host", j);\r\r                             if (operhost.size())\r                                   user->ChangeDisplayedHost(operhost.c_str());\r\r                          ServerInstance->SNO->WriteToSnoMask('o',"%s (%s@%s) is now an IRC operator of type %s", user->nick, user->ident, user->host, type.c_str());\r                            user->WriteServ("381 %s :You are now an IRC operator of type %s", user->nick, type.c_str());\r\r                          if (!user->modes[UM_OPERATOR])\r                                 user->Oper(type);\r\r                             return true;\r                   }\r              }\r              \r               return false;\r  }\r\r     virtual Version GetVersion()\r   {\r              return Version(1,1,1,0,VF_VENDOR,API_VERSION);\r }\r      \r};\r\rMODULE_INIT(ModuleSQLOper);\r\r
\ No newline at end of file
+/*       +------------------------------------+
+ *       | Inspire Internet Relay Chat Daemon |
+ *       +------------------------------------+
+ *
+ *  InspIRCd: (C) 2002-2007 InspIRCd Development Team
+ * See: http://www.inspircd.org/wiki/index.php/Credits
+ *
+ * This program is free but copyrighted software; see
+ *            the file COPYING for details.
+ *
+ * ---------------------------------------------------
+ */
+
+#include "inspircd.h"
+#include "users.h"
+#include "channels.h"
+#include "modules.h"
+#include "configreader.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 */
+
+class ModuleSQLOper : public Module
+{
+       Module* SQLutils;
+       Module* HashModule;
+       std::string databaseid;
+
+public:
+       ModuleSQLOper(InspIRCd* Me)
+       : Module::Module(Me)
+       {
+               ServerInstance->UseInterface("SQLutils");
+               ServerInstance->UseInterface("SQL");
+               ServerInstance->UseInterface("HashRequest");
+
+               /* Attempt to locate the md5 service provider, bail if we can't find it */
+               HashModule = ServerInstance->FindModule("m_md5.so");
+               if (!HashModule)
+                       throw ModuleException("Can't find m_md5.so. Please load m_md5.so before m_sqloper.so.");
+
+               SQLutils = ServerInstance->FindModule("m_sqlutils.so");
+               if (!SQLutils)
+                       throw ModuleException("Can't find m_sqlutils.so. Please load m_sqlutils.so before m_sqloper.so.");
+
+               OnRehash(NULL,"");
+       }
+
+       virtual ~ModuleSQLOper()
+       {
+               ServerInstance->DoneWithInterface("SQL");
+               ServerInstance->DoneWithInterface("SQLutils");
+               ServerInstance->DoneWithInterface("HashRequest");
+       }
+
+       void Implements(char* List)
+       {
+               List[I_OnRequest] = List[I_OnRehash] = List[I_OnPreCommand] = 1;
+       }
+
+       virtual void OnRehash(userrec* user, const std::string &parameter)
+       {
+               ConfigReader Conf(ServerInstance);
+               
+               databaseid = Conf.ReadValue("sqloper", "dbid", 0); /* Database ID of a database configured for the service provider module */
+       }
+
+       virtual int OnPreCommand(const std::string &command, const char** parameters, int pcnt, userrec *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 1;
+                       }
+               }
+               return 0;
+       }
+
+       bool LookupOper(userrec* user, const std::string &username, const std::string &password)
+       {
+               Module* target;
+               
+               target = ServerInstance->FindFeature("SQL");
+
+               if (target)
+               {
+                       /* Reset hash module first back to MD5 standard state */
+                       HashResetRequest(this, HashModule).Send();
+                       /* Make an MD5 hash of the password for using in the query */
+                       std::string md5_pass_hash = HashSumRequest(this, HashModule, password.c_str()).Send();
+
+                       /* We generate our own MD5 sum here because some database providers (e.g. SQLite) dont have a builtin md5 function,
+                        * also hashing it in the module and only passing a remote query containing a hash is more secure.
+                        */
+
+                       SQLrequest req = SQLreq(this, target, databaseid, "SELECT username, password, hostname, type FROM ircd_opers WHERE username = '?' AND password='?'", username, md5_pass_hash);
+                       
+                       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 userrec
+                                * 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
+               {
+                       ServerInstance->Log(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 char* OnRequest(Request* request)
+       {
+               if (strcmp(SQLRESID, request->GetId()) == 0)
+               {
+                       SQLresult* res = static_cast<SQLresult*>(request);
+
+                       userrec* 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 (res->error.Id() == 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["username"].d, row["password"].d, row["hostname"].d, row["type"].d))
+                                                       {
+                                                               /* If/when one of the rows matches, stop checking and return */
+                                                               return SQLSUCCESS;
+                                                       }
+                                                       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");
+                                               }
+                                       }
+                               }
+                               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");
+                                       }
+
+                               }
+                       }
+               
+                       return SQLSUCCESS;
+               }
+
+               return NULL;
+       }
+
+       void LoginFail(userrec* user, const std::string &username, const std::string &pass)
+       {
+               command_t* oper_command = ServerInstance->Parser->GetHandler("OPER");
+
+               if (oper_command)
+               {
+                       const char* params[] = { username.c_str(), pass.c_str() };
+                       oper_command->Handle(params, 2, user);
+               }
+               else
+               {
+                       ServerInstance->Log(DEBUG, "BUG: WHAT?! Why do we have no OPER command?!");
+               }
+       }
+
+       bool OperUser(userrec* user, const std::string &username, const std::string &password, const std::string &pattern, const std::string &type)
+       {
+               ConfigReader Conf(ServerInstance);
+               
+               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());
+
+                               ServerInstance->SNO->WriteToSnoMask('o',"%s (%s@%s) is now an IRC operator of type %s", user->nick, user->ident, user->host, type.c_str());
+                               user->WriteServ("381 %s :You are now an IRC operator of type %s", user->nick, type.c_str());
+
+                               if (!user->modes[UM_OPERATOR])
+                                       user->Oper(type);
+
+                               return true;
+                       }
+               }
+               
+               return false;
+       }
+
+       virtual Version GetVersion()
+       {
+               return Version(1,1,1,0,VF_VENDOR,API_VERSION);
+       }
+       
+};
+
+MODULE_INIT(ModuleSQLOper);
+