]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/modules/extra/m_sqloper.cpp
Get rid of Server::GetUsers(chanrec) - a throwback to before chanrec could do this...
[user/henk/code/inspircd.git] / src / modules / extra / m_sqloper.cpp
index 73422e49e851968c9a1c09a52c02a262728551b5..a67ec46e266b18531b7d10ef1383b2e572a03ca2 100644 (file)
  * ---------------------------------------------------
  */
 
-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 "cmd_oper.h"
+#include "m_sqlv2.h"
+#include "m_sqlutils.h"
+#include "commands/cmd_oper.h"
 
 /* $ModDesc: Allows storage of oper credentials in an SQL table */
 
@@ -44,174 +34,227 @@ extern ServerConfig* Config;
 extern std::vector<Module*> modules;
 extern std::vector<ircd_module*> factory;
 
-Server *Srv;
-
 class ModuleSQLOper : public Module
 {
-       ConfigReader* Conf;
-       unsigned long dbid;
-       Module* SQLModule;
-
- public:
-       bool ReadConfig()
-       {
-               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);
-       }
+       Server* Srv;
+       Module* SQLutils;
+       std::string databaseid;
 
+public:
        ModuleSQLOper(Server* Me)
-               : Module::Module(Me)
+       : Module::Module(Me), Srv(Me)
        {
-               Srv = Me;
-               Conf = new ConfigReader();
-               ReadConfig();
+               SQLutils = Srv->FindFeature("SQLutils");
+               
+               if (SQLutils)
+               {
+                       log(DEBUG, "Successfully got SQLutils pointer");
+               }
+               else
+               {
+                       log(DEFAULT, "ERROR: This module requires a module offering the 'SQLutils' feature (usually m_sqlutils.so). Please load it and try again.");
+                       throw ModuleException("This module requires a module offering the 'SQLutils' feature (usually m_sqlutils.so). Please load it and try again.");
+               }
+               
+               OnRehash("");
        }
 
-       virtual void OnRehash(std::string parameter)
+       virtual void OnRehash(const std::string &parameter)
        {
-               delete Conf;
-               Conf = new ConfigReader();
-               ReadConfig();
+               ConfigReader Conf;
+               
+               databaseid = Conf.ReadValue("sqloper", "dbid", 0); /* Database ID of a database configured for the service provider module */
        }
 
        void Implements(char* List)
        {
-               List[I_OnRehash] = List[I_OnPreCommand] = 1;
+               List[I_OnRequest] = List[I_OnRehash] = List[I_OnPreCommand] = 1;
        }
 
-       virtual int OnPreCommand(std::string command, char **parameters, int pcnt, userrec *user, bool validated)
+       virtual int OnPreCommand(const std::string &command, const char** parameters, int pcnt, userrec *user, bool validated)
        {
-               if ((command == "OPER") && (validated))
+               if (validated && (command == "OPER"))
                {
-                       if (LookupOper(parameters[0],parameters[1],user))
+                       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..
+                                */
                                return 1;
+                       }
                }
+
                return 0;
        }
 
-       bool LookupOper(std::string username, std::string password, userrec* user)
+       bool LookupOper(userrec* user, const std::string &username, const std::string &password)
        {
-               bool found = false;
-
-               // is the sql module loaded? If not, we don't attempt to do anything.
-               if (!SQLModule)
-                       return false;
-
-               // sanitize the password (we dont want any mysql insertion exploits!)
-               std::string temp = "";
-               for (unsigned int q = 0; q < password.length(); q++)
+               Module* target;
+               
+               target = Srv->FindFeature("SQL");
+               
+               if (target)
                {
-                       if (password[q] == '\'')
+                       SQLrequest req = SQLreq(this, target, databaseid, "SELECT username, password, hostname, type FROM ircd_opers WHERE username = '?' AND password=md5('?')", username, password);
+                       
+                       if (req.Send())
                        {
-                               temp = temp + "\'";
+                               /* 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.
+                                */
+                               log(DEBUG, "Sent query, got given ID %lu", req.id);
+                               
+                               AssociateUser(this, SQLutils, req.id, user).Send();
+                                       
+                               return true;
                        }
-                       else if (password[q] == '"')
+                       else
                        {
-                               temp = temp + "\\\"";
+                               log(DEBUG, "SQLrequest failed: %s", req.error.Str());
+                       
+                               return false;
                        }
-                       else temp = temp + password[q];
                }
-               password = temp;
-               temp = "";
-               for (unsigned int v = 0; v < username.length(); v++)
+               else
                {
-                       if (username[v] == '\'')
-                       {
-                               temp = temp + "\'";
-                       }
-                       if (username[v] == '"')
-                       {
-                               temp = temp + "\\\"";
-                       }
-                       else temp = temp + username[v];
+                       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;
                }
-               username = temp;
-
-               // 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();
-
-               // Did we get "OK" as a result?
-               if (result->GetType() == SQL_OK)
+       }
+       
+       virtual char* OnRequest(Request* request)
+       {
+               if (strcmp(SQLRESID, request->GetId()) == 0)
                {
-                       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)
+                       SQLresult* res;
+               
+                       res = static_cast<SQLresult*>(request);
+                       
+                       log(DEBUG, "Got SQL result (%s) with ID %lu", res->GetId(), res->id);
+                       
+                       userrec* user = GetAssocUser(this, SQLutils, res->id).S().user;
+                       UnAssociate(this, SQLutils, res->id).S();
+                       
+                       if (user)
                        {
-                               if (rowresult->GetField("username") == username)
+                               if (res->error.Id() == NO_ERROR)
+                               {                               
+                                       log(DEBUG, "Associated query ID %lu with user %s", res->id, user->nick);                        
+                                       log(DEBUG, "Got result with %d rows and %d columns", res->Rows(), res->Cols());
+                       
+                                       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())
+                                               {
+                                                       log(DEBUG, "Trying to oper user %s with username = '%s', passhash = '%s', hostname = '%s', type = '%s'", user->nick, row["username"].d.c_str(), row["password"].d.c_str(), row["hostname"].d.c_str(), row["type"].d.c_str());
+                                                       
+                                                       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;
+                                                       }
+                                               }
+                                       }
+                                       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
+                                                */
+                                               
+                                               user->WriteServ( "491 %s :Invalid oper credentials", user->nick);
+                                               WriteOpers("*** WARNING! Failed oper attempt by %s!%s@%s!", user->nick, user->ident, user->host);
+                                               log(DEFAULT,"OPER: Failed oper attempt by %s!%s@%s: user, host or password did not match.", user->nick, user->ident, user->host);
+                                       }
+                               }
+                               else
                                {
-                                       found = true;
-                                       // oper up the user.
-                                       for (int j =0; j < Conf->Enumerate("type"); j++)
-                                       {
-                                               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,rowresult->GetField("hostname"))))
-                                               {
-                                                       Srv->Log(DEBUG,"Host and type match: "+TypeName+" "+rowresult->GetField("type"));
-                                                       /* found this oper's opertype */
-                                                       std::string HostName = Conf->ReadValue("type","host",j);
-                                                       if (HostName != "")
-                                                               Srv->ChangeHost(user,HostName);
-                                                       strlcpy(user->oper,rowresult->GetField("type").c_str(),NICKMAX);
-                                                       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'))
-                                                       {
-                                                               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());
-                                                       }
-                                                       break;
-                                               }
-                                       }
+                                       /* 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.
+                                        */
+                                       log(DEBUG, "Query failed: %s", res->error.Str());
 
+                                       user->WriteServ( "491 %s :Invalid oper credentials", user->nick);
+                                       WriteOpers("*** WARNING! Failed oper attempt by %s!%s@%s! (SQL query failed: %s)", user->nick, user->ident, user->host, res->error.Str());
+                                       log(DEFAULT,"OPER: Failed oper attempt by %s!%s@%s: user, host or password did not match.", user->nick, user->ident, user->host);
                                }
-                               delete rowresult;
                        }
                        else
                        {
-                               // we didn't have a row.
-                               found = false;
+                               log(DEBUG, "Got query with unknown ID, this probably means the user quit while the query was in progress");
                        }
-                       delete rowrequest;
-                       delete result;
+               
+                       return SQLSUCCESS;
                }
-               else
+               
+               log(DEBUG, "Got unsupported API version string: %s", request->GetId());
+               
+               return NULL;
+       }       
+
+       bool OperUser(userrec* user, const std::string &username, const std::string &password, const std::string &pattern, const std::string &type)
+       {
+               ConfigReader Conf;
+               
+               for (int j = 0; j < Conf.Enumerate("type"); j++)
                {
-                       // the query was bad
-                       found = false;
+                       std::string tname = Conf.ReadValue("type","name",j);
+                       
+                       log(DEBUG, "Scanning opertype: %s", tname.c_str());
+                       
+                       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. */
+                               log(DEBUG, "Host (%s matched %s OR %s) and type (%s)", pattern.c_str(), hostname.c_str(), user->GetIPString(), type.c_str());
+                               
+                               std::string operhost = Conf.ReadValue("type", "host", j);
+                                                       
+                               if (operhost.size())
+                                       user->ChangeDisplayedHost(operhost);
+                                                               
+                               strlcpy(user->oper, type.c_str(), NICKMAX-1);
+                               
+                               WriteOpers("*** %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();
+                                                               
+                               return true;
+                       }
                }
-               query->SetQueryType(SQL_DONE);
-               query->SetConnID(dbid);
-               Request donerequest((char*)query, this, SQLModule);
-               donerequest.Send();
-               delete query;
-               return found;
+               
+               return false;
        }
 
        virtual ~ModuleSQLOper()
        {
-               delete Conf;
        }
        
        virtual Version GetVersion()
        {
-               return Version(1,0,0,1,VF_VENDOR);
+               return Version(1,0,1,0,VF_VENDOR);
        }
        
 };
@@ -239,4 +282,3 @@ extern "C" void * init_module( void )
 {
        return new ModuleSQLOperFactory;
 }
-