diff options
-rw-r--r-- | src/server/authserver/Authentication/AuthCodes.cpp | 37 | ||||
-rw-r--r-- | src/server/authserver/Authentication/AuthCodes.h | 97 | ||||
-rw-r--r-- | src/server/authserver/Realms/RealmList.cpp | 100 | ||||
-rw-r--r-- | src/server/authserver/Realms/RealmList.h | 79 | ||||
-rw-r--r-- | src/server/authserver/Server/AuthSocket.cpp | 1069 | ||||
-rw-r--r-- | src/server/authserver/Server/AuthSocket.h | 97 | ||||
-rw-r--r-- | src/server/authserver/Server/RealmAcceptor.h | 52 | ||||
-rw-r--r-- | src/server/authserver/Server/RealmSocket.cpp | 324 | ||||
-rw-r--r-- | src/server/authserver/Server/RealmSocket.h | 85 | ||||
-rw-r--r-- | src/server/worldserver/CommandLine/CliRunnable.cpp | 445 | ||||
-rw-r--r-- | src/server/worldserver/CommandLine/CliRunnable.h | 35 | ||||
-rw-r--r-- | src/server/worldserver/RemoteAccess/RASocket.cpp | 265 | ||||
-rw-r--r-- | src/server/worldserver/RemoteAccess/RASocket.h | 68 | ||||
-rw-r--r-- | src/server/worldserver/WorldThread/WorldRunnable.cpp | 97 | ||||
-rw-r--r-- | src/server/worldserver/WorldThread/WorldRunnable.h | 35 |
15 files changed, 2885 insertions, 0 deletions
diff --git a/src/server/authserver/Authentication/AuthCodes.cpp b/src/server/authserver/Authentication/AuthCodes.cpp new file mode 100644 index 00000000000..812949e0823 --- /dev/null +++ b/src/server/authserver/Authentication/AuthCodes.cpp @@ -0,0 +1,37 @@ +#include "AuthCodes.h" + +namespace AuthHelper +{ + +bool IsPreBCAcceptedClientBuild(int build) +{ + int accepted_versions[] = PRE_BC_ACCEPTED_CLIENT_BUILD; + for (int i = 0; accepted_versions[i]; ++i) + { + if (build == accepted_versions[i]) + { + return true; + } + } + return false; +} + +bool IsPostBCAcceptedClientBuild(int build) +{ + int accepted_versions[] = POST_BC_ACCEPTED_CLIENT_BUILD; + for (int i = 0; accepted_versions[i]; ++i) + { + if (build == accepted_versions[i]) + { + return true; + } + } + return false; +} + +bool IsAcceptedClientBuild(int build) +{ + return (IsPostBCAcceptedClientBuild(build) || IsPreBCAcceptedClientBuild(build)); +} + +}; diff --git a/src/server/authserver/Authentication/AuthCodes.h b/src/server/authserver/Authentication/AuthCodes.h new file mode 100644 index 00000000000..eb6e4abfb08 --- /dev/null +++ b/src/server/authserver/Authentication/AuthCodes.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** \file + \ingroup realmd +*/ + +#ifndef _AUTHCODES_H +#define _AUTHCODES_H + +enum AuthResult +{ + WOW_SUCCESS = 0x00, + WOW_FAIL_UNKNOWN0 = 0x01, ///< ? Unable to connect + WOW_FAIL_UNKNOWN1 = 0x02, ///< ? Unable to connect + WOW_FAIL_BANNED = 0x03, ///< This <game> account has been closed and is no longer available for use. Please go to <site>/banned.html for further information. + WOW_FAIL_UNKNOWN_ACCOUNT = 0x04, ///< The information you have entered is not valid. Please check the spelling of the account name and password. If you need help in retrieving a lost or stolen password, see <site> for more information + WOW_FAIL_INCORRECT_PASSWORD = 0x05, ///< The information you have entered is not valid. Please check the spelling of the account name and password. If you need help in retrieving a lost or stolen password, see <site> for more information + WOW_FAIL_ALREADY_ONLINE = 0x06, ///< This account is already logged into <game>. Please check the spelling and try again. + WOW_FAIL_NO_TIME = 0x07, ///< You have used up your prepaid time for this account. Please purchase more to continue playing + WOW_FAIL_DB_BUSY = 0x08, ///< Could not log in to <game> at this time. Please try again later. + WOW_FAIL_VERSION_INVALID = 0x09, ///< Unable to validate game version. This may be caused by file corruption or interference of another program. Please visit <site> for more information and possible solutions to this issue. + WOW_FAIL_VERSION_UPDATE = 0x0A, ///< Downloading + WOW_FAIL_INVALID_SERVER = 0x0B, ///< Unable to connect + WOW_FAIL_SUSPENDED = 0x0C, ///< This <game> account has been temporarily suspended. Please go to <site>/banned.html for further information + WOW_FAIL_FAIL_NOACCESS = 0x0D, ///< Unable to connect + WOW_SUCCESS_SURVEY = 0x0E, ///< Connected. + WOW_FAIL_PARENTCONTROL = 0x0F, ///< Access to this account has been blocked by parental controls. Your settings may be changed in your account preferences at <site> + WOW_FAIL_LOCKED_ENFORCED = 0x10, ///< You have applied a lock to your account. You can change your locked status by calling your account lock phone number. + WOW_FAIL_TRIAL_ENDED = 0x11, ///< Your trial subscription has expired. Please visit <site> to upgrade your account. + WOW_FAIL_USE_BATTLENET = 0x12, ///< WOW_FAIL_OTHER This account is now attached to a Battle.net account. Please login with your Battle.net account email address and password. +}; + +enum LoginResult +{ + LOGIN_OK = 0x00, + LOGIN_FAILED = 0x01, + LOGIN_FAILED2 = 0x02, + LOGIN_BANNED = 0x03, + LOGIN_UNKNOWN_ACCOUNT = 0x04, + LOGIN_UNKNOWN_ACCOUNT3 = 0x05, + LOGIN_ALREADYONLINE = 0x06, + LOGIN_NOTIME = 0x07, + LOGIN_DBBUSY = 0x08, + LOGIN_BADVERSION = 0x09, + LOGIN_DOWNLOAD_FILE = 0x0A, + LOGIN_FAILED3 = 0x0B, + LOGIN_SUSPENDED = 0x0C, + LOGIN_FAILED4 = 0x0D, + LOGIN_CONNECTED = 0x0E, + LOGIN_PARENTALCONTROL = 0x0F, + LOGIN_LOCKED_ENFORCED = 0x10, +}; + +//multirealm supported versions: +//1.12.1 build 5875 +//1.12.2 build 6005 +//2.4.3 build 8606 +//3.1.3 build 9947 +//3.1.3 build 10146 Chinese build +//3.2.2a build 10505 +//3.3.0a build 11159 +//3.3.2 build 11403 +//3.3.3a build 11723 + +#define POST_BC_ACCEPTED_CLIENT_BUILD {11723, 11403, 11159, 10571, 10505, 10146, 9947, 8606, 0} +#define PRE_BC_ACCEPTED_CLIENT_BUILD {5875, 6005, 0} + +#define POST_BC_EXP_FLAG 0x2 +#define PRE_BC_EXP_FLAG 0x1 +#define NO_VALID_EXP_FLAG 0x0 + +namespace AuthHelper +{ + bool IsAcceptedClientBuild(int build); + bool IsPostBCAcceptedClientBuild(int build); + bool IsPreBCAcceptedClientBuild(int build); +}; + +#endif diff --git a/src/server/authserver/Realms/RealmList.cpp b/src/server/authserver/Realms/RealmList.cpp new file mode 100644 index 00000000000..1e989c36c5d --- /dev/null +++ b/src/server/authserver/Realms/RealmList.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** \file + \ingroup realmd +*/ + +#include "Common.h" +#include "RealmList.h" +#include "Database/DatabaseEnv.h" + + +extern DatabaseType LoginDatabase; + +RealmList::RealmList() : m_UpdateInterval(0), m_NextUpdateTime(time(NULL)) +{ +} + +/// Load the realm list from the database +void RealmList::Initialize(uint32 updateInterval) +{ + m_UpdateInterval = updateInterval; + + ///- Get the content of the realmlist table in the database + UpdateRealms(true); +} + +void RealmList::UpdateRealm(uint32 ID, const std::string& name, const std::string& address, uint32 port, uint8 icon, uint8 color, uint8 timezone, AccountTypes allowedSecurityLevel, float popu, uint32 build) +{ + ///- Create new if not exist or update existed + Realm& realm = m_realms[name]; + + realm.m_ID = ID; + realm.name = name; + realm.icon = icon; + realm.color = color; + realm.timezone = timezone; + realm.allowedSecurityLevel = allowedSecurityLevel; + realm.populationLevel = popu; + + ///- Append port to IP address. + std::ostringstream ss; + ss << address << ":" << port; + realm.address = ss.str(); + realm.gamebuild = build; +} + +void RealmList::UpdateIfNeed() +{ + // maybe disabled or updated recently + if (!m_UpdateInterval || m_NextUpdateTime > time(NULL)) + return; + + m_NextUpdateTime = time(NULL) + m_UpdateInterval; + + // Clears Realm list + m_realms.clear(); + + // Get the content of the realmlist table in the database + UpdateRealms(false); +} + +void RealmList::UpdateRealms(bool init) +{ + sLog.outDetail("Updating Realm List..."); + + QueryResult_AutoPtr result = LoginDatabase.Query("SELECT id, name, address, port, icon, color, timezone, allowedSecurityLevel, population, gamebuild FROM realmlist WHERE color <> 3 ORDER BY name"); + + ///- Circle through results and add them to the realm map + if (result) + { + do + { + Field *fields = result->Fetch(); + + uint8 allowedSecurityLevel = fields[7].GetUInt8(); + + UpdateRealm(fields[0].GetUInt32(), fields[1].GetCppString(),fields[2].GetCppString(),fields[3].GetUInt32(),fields[4].GetUInt8(), fields[5].GetUInt8(), fields[6].GetUInt8(), (allowedSecurityLevel <= SEC_ADMINISTRATOR ? AccountTypes(allowedSecurityLevel) : SEC_ADMINISTRATOR), fields[8].GetFloat(), fields[9].GetUInt32()); + if (init) + sLog.outString("Added realm \"%s\".", fields[1].GetString()); + } while(result->NextRow()); + } +} diff --git a/src/server/authserver/Realms/RealmList.h b/src/server/authserver/Realms/RealmList.h new file mode 100644 index 00000000000..b29b561c797 --- /dev/null +++ b/src/server/authserver/Realms/RealmList.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/// \addtogroup realmd +/// @{ +/// \file + +#ifndef _REALMLIST_H +#define _REALMLIST_H + +#include <ace/Singleton.h> +#include <ace/Null_Mutex.h> +#include "Common.h" + +/// Storage object for a realm +struct Realm +{ + std::string address; + std::string name; + uint8 icon; + uint8 color; + uint8 timezone; + uint32 m_ID; + AccountTypes allowedSecurityLevel; + float populationLevel; + uint32 gamebuild; +}; + +/// Storage object for the list of realms on the server +class RealmList +{ + public: + // Null_Mutex is safe because the singleton initialized before the acceptor initialized(another place where the singleton called) + static RealmList* instance() { return ACE_Singleton<RealmList, ACE_Null_Mutex>::instance(); } + + typedef std::map<std::string, Realm> RealmMap; + + RealmList(); + ~RealmList() {} + + void Initialize(uint32 updateInterval); + + void UpdateIfNeed(); + + void AddRealm(Realm NewRealm) {m_realms[NewRealm.name] = NewRealm;} + + RealmMap::const_iterator begin() const { return m_realms.begin(); } + RealmMap::const_iterator end() const { return m_realms.end(); } + uint32 size() const { return m_realms.size(); } + private: + void UpdateRealms(bool init); + void UpdateRealm(uint32 ID, const std::string& name, const std::string& address, uint32 port, uint8 icon, uint8 color, uint8 timezone, AccountTypes allowedSecurityLevel, float popu, uint32 build); + private: + RealmMap m_realms; ///< Internal map of realms + uint32 m_UpdateInterval; + time_t m_NextUpdateTime; +}; + +#define sRealmList RealmList::instance() + +#endif +/// @} diff --git a/src/server/authserver/Server/AuthSocket.cpp b/src/server/authserver/Server/AuthSocket.cpp new file mode 100644 index 00000000000..cc293097977 --- /dev/null +++ b/src/server/authserver/Server/AuthSocket.cpp @@ -0,0 +1,1069 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** \file + \ingroup realmd +*/ + +#include "Common.h" +#include "Database/DatabaseEnv.h" +#include "ByteBuffer.h" +#include "Config/ConfigEnv.h" +#include "Log.h" +#include "RealmList.h" +#include "AuthSocket.h" +#include "AuthCodes.h" +#include <openssl/md5.h> +#include "Auth/Sha1.h" +//#include "Util.h" -- for commented utf8ToUpperOnlyLatin + +extern DatabaseType LoginDatabase; + +#define ChunkSize 2048 + +enum eAuthCmd +{ + //AUTH_NO_CMD = 0xFF, + AUTH_LOGON_CHALLENGE = 0x00, + AUTH_LOGON_PROOF = 0x01, + AUTH_RECONNECT_CHALLENGE = 0x02, + AUTH_RECONNECT_PROOF = 0x03, + //update srv =4 + REALM_LIST = 0x10, + XFER_INITIATE = 0x30, + XFER_DATA = 0x31, + XFER_ACCEPT = 0x32, + XFER_RESUME = 0x33, + XFER_CANCEL = 0x34 +}; + +enum eStatus +{ + STATUS_CONNECTED = 0, + STATUS_AUTHED +}; + +// GCC have alternative #pragma pack(N) syntax and old gcc version not support pack(push,N), also any gcc version not support it at some paltform +#if defined(__GNUC__) +#pragma pack(1) +#else +#pragma pack(push,1) +#endif + +typedef struct AUTH_LOGON_CHALLENGE_C +{ + uint8 cmd; + uint8 error; + uint16 size; + uint8 gamename[4]; + uint8 version1; + uint8 version2; + uint8 version3; + uint16 build; + uint8 platform[4]; + uint8 os[4]; + uint8 country[4]; + uint32 timezone_bias; + uint32 ip; + uint8 I_len; + uint8 I[1]; +} sAuthLogonChallenge_C; + +typedef struct AUTH_LOGON_PROOF_C +{ + uint8 cmd; + uint8 A[32]; + uint8 M1[20]; + uint8 crc_hash[20]; + uint8 number_of_keys; + uint8 securityFlags; // 0x00-0x04 +} sAuthLogonProof_C; + +typedef struct AUTH_LOGON_PROOF_S +{ + uint8 cmd; + uint8 error; + uint8 M2[20]; + uint32 unk1; + uint32 unk2; + uint16 unk3; +} sAuthLogonProof_S; + +typedef struct AUTH_LOGON_PROOF_S_OLD +{ + uint8 cmd; + uint8 error; + uint8 M2[20]; + //uint32 unk1; + uint32 unk2; + //uint16 unk3; +} sAuthLogonProof_S_Old; + +typedef struct AUTH_RECONNECT_PROOF_C +{ + uint8 cmd; + uint8 R1[16]; + uint8 R2[20]; + uint8 R3[20]; + uint8 number_of_keys; +} sAuthReconnectProof_C; + +typedef struct XFER_INIT +{ + uint8 cmd; // XFER_INITIATE + uint8 fileNameLen; // strlen(fileName); + uint8 fileName[5]; // fileName[fileNameLen] + uint64 file_size; // file size (bytes) + uint8 md5[MD5_DIGEST_LENGTH]; // MD5 +} XFER_INIT; + +typedef struct XFER_DATA +{ + uint8 opcode; + uint16 data_size; + uint8 data[ChunkSize]; +} XFER_DATA_STRUCT; + +typedef struct AuthHandler +{ + eAuthCmd cmd; + uint32 status; + bool (AuthSocket::*handler)(void); +} AuthHandler; + +// GCC have alternative #pragma pack() syntax and old gcc version not support pack(pop), also any gcc version not support it at some paltform +#if defined(__GNUC__) +#pragma pack() +#else +#pragma pack(pop) +#endif + +/// Launch a thread to transfer a patch to the client +class PatcherRunnable: public ACE_Based::Runnable +{ + public: + PatcherRunnable(class AuthSocket *); + void run(); + + private: + AuthSocket * mySocket; +}; + +typedef struct PATCH_INFO +{ + uint8 md5[MD5_DIGEST_LENGTH]; +} PATCH_INFO; + +/// Caches MD5 hash of client patches present on the server +class Patcher +{ + public: + typedef std::map<std::string, PATCH_INFO*> Patches; + ~Patcher(); + Patcher(); + Patches::const_iterator begin() const { return _patches.begin(); } + Patches::const_iterator end() const { return _patches.end(); } + void LoadPatchMD5(char*); + bool GetHash(char * pat,uint8 mymd5[16]); + + private: + void LoadPatchesInfo(); + Patches _patches; +}; + +const AuthHandler table[] = +{ + { AUTH_LOGON_CHALLENGE, STATUS_CONNECTED, &AuthSocket::_HandleLogonChallenge }, + { AUTH_LOGON_PROOF, STATUS_CONNECTED, &AuthSocket::_HandleLogonProof }, + { AUTH_RECONNECT_CHALLENGE, STATUS_CONNECTED, &AuthSocket::_HandleReconnectChallenge}, + { AUTH_RECONNECT_PROOF, STATUS_CONNECTED, &AuthSocket::_HandleReconnectProof }, + { REALM_LIST, STATUS_AUTHED, &AuthSocket::_HandleRealmList }, + { XFER_ACCEPT, STATUS_CONNECTED, &AuthSocket::_HandleXferAccept }, + { XFER_RESUME, STATUS_CONNECTED, &AuthSocket::_HandleXferResume }, + { XFER_CANCEL, STATUS_CONNECTED, &AuthSocket::_HandleXferCancel } +}; + +#define AUTH_TOTAL_COMMANDS sizeof(table)/sizeof(AuthHandler) + +///Holds the MD5 hash of client patches present on the server +Patcher PatchesCache; + +/// Constructor - set the N and g values for SRP6 +AuthSocket::AuthSocket(RealmSocket& socket) : socket_(socket) +{ + N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7"); + g.SetDword(7); + _authed = false; + _accountSecurityLevel = SEC_PLAYER; +} + +/// Close patch file descriptor before leaving +AuthSocket::~AuthSocket(void) +{ +} + +/// Accept the connection and set the s random value for SRP6 +void AuthSocket::OnAccept(void) +{ + sLog.outBasic("Accepting connection from '%s'", socket().get_remote_address().c_str()); +} + +void AuthSocket::OnClose(void) +{ + sLog.outDebug("AuthSocket::OnClose"); +} + +/// Read the packet from the client +void AuthSocket::OnRead() +{ + uint8 _cmd; + while (1) + { + if (!socket().recv_soft((char *)&_cmd, 1)) + return; + + size_t i; + + ///- Circle through known commands and call the correct command handler + for (i = 0; i < AUTH_TOTAL_COMMANDS; ++i) + { + if ((uint8)table[i].cmd == _cmd && + (table[i].status == STATUS_CONNECTED || + (_authed && table[i].status == STATUS_AUTHED))) + { + DEBUG_LOG("[Auth] got data for cmd %u recv length %u", (uint32)_cmd, (uint32)socket().recv_len()); + + if (!(*this.*table[i].handler)()) + { + DEBUG_LOG("Command handler failed for cmd %u recv length %u", (uint32)_cmd, (uint32)socket().recv_len()); + return; + } + break; + } + } + + // Report unknown packets in the error log + if (i == AUTH_TOTAL_COMMANDS) + { + sLog.outError("[Auth] got unknown packet from '%s'", socket().get_remote_address().c_str()); + socket().shutdown(); + return; + } + } +} + +/// Make the SRP6 calculation from hash in dB +void AuthSocket::_SetVSFields(const std::string& rI) +{ + s.SetRand(s_BYTE_SIZE * 8); + + BigNumber I; + I.SetHexStr(rI.c_str()); + + // In case of leading zeros in the rI hash, restore them + uint8 mDigest[SHA_DIGEST_LENGTH]; + memset(mDigest, 0, SHA_DIGEST_LENGTH); + if (I.GetNumBytes() <= SHA_DIGEST_LENGTH) + memcpy(mDigest, I.AsByteArray(), I.GetNumBytes()); + + std::reverse(mDigest, mDigest + SHA_DIGEST_LENGTH); + + Sha1Hash sha; + sha.UpdateData(s.AsByteArray(), s.GetNumBytes()); + sha.UpdateData(mDigest, SHA_DIGEST_LENGTH); + sha.Finalize(); + BigNumber x; + x.SetBinary(sha.GetDigest(), sha.GetLength()); + v = g.ModExp(x, N); + // No SQL injection (username escaped) + const char *v_hex, *s_hex; + v_hex = v.AsHexStr(); + s_hex = s.AsHexStr(); + LoginDatabase.PExecute("UPDATE account SET v = '%s', s = '%s' WHERE username = '%s'", v_hex, s_hex, _safelogin.c_str()); + OPENSSL_free((void*)v_hex); + OPENSSL_free((void*)s_hex); +} + +/// Logon Challenge command handler +bool AuthSocket::_HandleLogonChallenge() +{ + DEBUG_LOG("Entering _HandleLogonChallenge"); + if (socket().recv_len() < sizeof(sAuthLogonChallenge_C)) + return false; + + ///- Read the first 4 bytes (header) to get the length of the remaining of the packet + std::vector<uint8> buf; + buf.resize(4); + + socket().recv((char *)&buf[0], 4); + + EndianConvert(*((uint16*)(buf[0]))); + uint16 remaining = ((sAuthLogonChallenge_C *)&buf[0])->size; + DEBUG_LOG("[AuthChallenge] got header, body is %#04x bytes", remaining); + + if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (socket().recv_len() < remaining)) + return false; + + //No big fear of memory outage (size is int16, i.e. < 65536) + buf.resize(remaining + buf.size() + 1); + buf[buf.size() - 1] = 0; + sAuthLogonChallenge_C *ch = (sAuthLogonChallenge_C*)&buf[0]; + + ///- Read the remaining of the packet + socket().recv((char *)&buf[4], remaining); + DEBUG_LOG("[AuthChallenge] got full packet, %#04x bytes", ch->size); + DEBUG_LOG("[AuthChallenge] name(%d): '%s'", ch->I_len, ch->I); + + // BigEndian code, nop in little endian case + // size already converted + EndianConvert(*((uint32*)(&ch->gamename[0]))); + EndianConvert(ch->build); + EndianConvert(*((uint32*)(&ch->platform[0]))); + EndianConvert(*((uint32*)(&ch->os[0]))); + EndianConvert(*((uint32*)(&ch->country[0]))); + EndianConvert(ch->timezone_bias); + EndianConvert(ch->ip); + + ByteBuffer pkt; + + _login = (const char*)ch->I; + _build = ch->build; + _expversion = (AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : NO_VALID_EXP_FLAG) + (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG); + + ///- Normalize account name + //utf8ToUpperOnlyLatin(_login); -- client already send account in expected form + + //Escape the user login to avoid further SQL injection + //Memory will be freed on AuthSocket object destruction + _safelogin = _login; + LoginDatabase.escape_string(_safelogin); + + _build = ch->build; + + pkt << (uint8) AUTH_LOGON_CHALLENGE; + pkt << (uint8) 0x00; + + ///- Verify that this IP is not in the ip_banned table + // No SQL injection possible (paste the IP address as passed by the socket) + LoginDatabase.Execute("DELETE FROM ip_banned WHERE unbandate<=UNIX_TIMESTAMP() AND unbandate<>bandate"); + + std::string address(socket().get_remote_address().c_str()); + LoginDatabase.escape_string(address); + QueryResult_AutoPtr result = LoginDatabase.PQuery("SELECT * FROM ip_banned WHERE ip = '%s'",address.c_str()); + if (result) + { + pkt << (uint8)WOW_FAIL_BANNED; + sLog.outBasic("[AuthChallenge] Banned ip %s tries to login!", address.c_str ()); + } + else + { + ///- Get the account details from the account table + // No SQL injection (escaped user name) + + result = LoginDatabase.PQuery("SELECT a.sha_pass_hash,a.id,a.locked,a.last_ip,aa.gmlevel,a.v,a.s " + "FROM account a " + "LEFT JOIN account_access aa " + "ON (a.id = aa.id) " + "WHERE a.username = '%s'",_safelogin.c_str ()); + if (result) + { + ///- If the IP is 'locked', check that the player comes indeed from the correct IP address + bool locked = false; + if ((*result)[2].GetUInt8() == 1) // if ip is locked + { + DEBUG_LOG("[AuthChallenge] Account '%s' is locked to IP - '%s'", _login.c_str(), (*result)[3].GetString()); + DEBUG_LOG("[AuthChallenge] Player address is '%s'", socket().get_remote_address().c_str()); + if (strcmp((*result)[3].GetString(),socket().get_remote_address().c_str())) + { + DEBUG_LOG("[AuthChallenge] Account IP differs"); + pkt << (uint8) WOW_FAIL_SUSPENDED; + locked=true; + } + else + DEBUG_LOG("[AuthChallenge] Account IP matches"); + } + else + DEBUG_LOG("[AuthChallenge] Account '%s' is not locked to ip", _login.c_str()); + + if (!locked) + { + //set expired bans to inactive + LoginDatabase.Execute("UPDATE account_banned SET active = 0 WHERE unbandate<=UNIX_TIMESTAMP() AND unbandate<>bandate"); + ///- If the account is banned, reject the logon attempt + QueryResult_AutoPtr banresult = LoginDatabase.PQuery("SELECT bandate,unbandate FROM account_banned WHERE id = %u AND active = 1", (*result)[1].GetUInt32()); + if (banresult) + { + if ((*banresult)[0].GetUInt64() == (*banresult)[1].GetUInt64()) + { + pkt << (uint8) WOW_FAIL_BANNED; + sLog.outBasic("[AuthChallenge] Banned account %s tries to login!",_login.c_str ()); + } + else + { + pkt << (uint8) WOW_FAIL_SUSPENDED; + sLog.outBasic("[AuthChallenge] Temporarily banned account %s tries to login!",_login.c_str ()); + } + } + else + { + ///- Get the password from the account table, upper it, and make the SRP6 calculation + std::string rI = (*result)[0].GetCppString(); + + ///- Don't calculate (v, s) if there are already some in the database + std::string databaseV = (*result)[5].GetCppString(); + std::string databaseS = (*result)[6].GetCppString(); + + sLog.outDebug("database authentication values: v='%s' s='%s'", databaseV.c_str(), databaseS.c_str()); + + // multiply with 2, bytes are stored as hexstring + if (databaseV.size() != s_BYTE_SIZE*2 || databaseS.size() != s_BYTE_SIZE*2) + _SetVSFields(rI); + else + { + s.SetHexStr(databaseS.c_str()); + v.SetHexStr(databaseV.c_str()); + } + + b.SetRand(19 * 8); + BigNumber gmod = g.ModExp(b, N); + B = ((v * 3) + gmod) % N; + + ASSERT(gmod.GetNumBytes() <= 32); + + BigNumber unk3; + unk3.SetRand(16 * 8); + + ///- Fill the response packet with the result + pkt << uint8(WOW_SUCCESS); + + // B may be calculated < 32B so we force minimal length to 32B + pkt.append(B.AsByteArray(32), 32); // 32 bytes + pkt << uint8(1); + pkt.append(g.AsByteArray(), 1); + pkt << uint8(32); + pkt.append(N.AsByteArray(32), 32); + pkt.append(s.AsByteArray(), s.GetNumBytes()); // 32 bytes + pkt.append(unk3.AsByteArray(16), 16); + uint8 securityFlags = 0; + pkt << uint8(securityFlags); // security flags (0x0...0x04) + + if (securityFlags & 0x01) // PIN input + { + pkt << uint32(0); + pkt << uint64(0) << uint64(0); // 16 bytes hash? + } + + if (securityFlags & 0x02) // Matrix input + { + pkt << uint8(0); + pkt << uint8(0); + pkt << uint8(0); + pkt << uint8(0); + pkt << uint64(0); + } + + if (securityFlags & 0x04) // Security token input + pkt << uint8(1); + + uint8 secLevel = (*result)[4].GetUInt8(); + _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes(secLevel) : SEC_ADMINISTRATOR; + + _localizationName.resize(4); + for (int i = 0; i < 4; ++i) + _localizationName[i] = ch->country[4-i-1]; + + sLog.outBasic("[AuthChallenge] account %s is using '%c%c%c%c' locale (%u)", _login.c_str (), ch->country[3], ch->country[2], ch->country[1], ch->country[0], GetLocaleByName(_localizationName)); + } + } + } + else //no account + { + pkt<< (uint8) WOW_FAIL_UNKNOWN_ACCOUNT; + } + } + + socket().send((char const*)pkt.contents(), pkt.size()); + return true; +} + +/// Logon Proof command handler +bool AuthSocket::_HandleLogonProof() +{ + DEBUG_LOG("Entering _HandleLogonProof"); + ///- Read the packet + sAuthLogonProof_C lp; + + if (!socket().recv((char *)&lp, sizeof(sAuthLogonProof_C))) + return false; + + /// <ul><li> If the client has no valid version + if (_expversion == NO_VALID_EXP_FLAG) + { + ///- Check if we have the appropriate patch on the disk + + sLog.outDebug("Client with invalid version, patching is not implemented"); + socket().shutdown(); + return true; + } + /// </ul> + + ///- Continue the SRP6 calculation based on data received from the client + BigNumber A; + + A.SetBinary(lp.A, 32); + + // SRP safeguard: abort if A==0 + if (A.isZero()) + { + socket().shutdown(); + return true; + } + + Sha1Hash sha; + sha.UpdateBigNumbers(&A, &B, NULL); + sha.Finalize(); + BigNumber u; + u.SetBinary(sha.GetDigest(), 20); + BigNumber S = (A * (v.ModExp(u, N))).ModExp(b, N); + + uint8 t[32]; + uint8 t1[16]; + uint8 vK[40]; + memcpy(t, S.AsByteArray(32), 32); + for (int i = 0; i < 16; ++i) + { + t1[i] = t[i * 2]; + } + sha.Initialize(); + sha.UpdateData(t1, 16); + sha.Finalize(); + for (int i = 0; i < 20; ++i) + { + vK[i * 2] = sha.GetDigest()[i]; + } + for (int i = 0; i < 16; ++i) + { + t1[i] = t[i * 2 + 1]; + } + sha.Initialize(); + sha.UpdateData(t1, 16); + sha.Finalize(); + for (int i = 0; i < 20; ++i) + { + vK[i * 2 + 1] = sha.GetDigest()[i]; + } + K.SetBinary(vK, 40); + + uint8 hash[20]; + + sha.Initialize(); + sha.UpdateBigNumbers(&N, NULL); + sha.Finalize(); + memcpy(hash, sha.GetDigest(), 20); + sha.Initialize(); + sha.UpdateBigNumbers(&g, NULL); + sha.Finalize(); + for (int i = 0; i < 20; ++i) + { + hash[i] ^= sha.GetDigest()[i]; + } + BigNumber t3; + t3.SetBinary(hash, 20); + + sha.Initialize(); + sha.UpdateData(_login); + sha.Finalize(); + uint8 t4[SHA_DIGEST_LENGTH]; + memcpy(t4, sha.GetDigest(), SHA_DIGEST_LENGTH); + + sha.Initialize(); + sha.UpdateBigNumbers(&t3, NULL); + sha.UpdateData(t4, SHA_DIGEST_LENGTH); + sha.UpdateBigNumbers(&s, &A, &B, &K, NULL); + sha.Finalize(); + BigNumber M; + M.SetBinary(sha.GetDigest(), 20); + + ///- Check if SRP6 results match (password is correct), else send an error + if (!memcmp(M.AsByteArray(), lp.M1, 20)) + { + sLog.outBasic("User '%s' successfully authenticated", _login.c_str()); + + ///- Update the sessionkey, last_ip, last login time and reset number of failed logins in the account table for this account + // No SQL injection (escaped user name) and IP address as received by socket + const char* K_hex = K.AsHexStr(); + LoginDatabase.PExecute("UPDATE account SET sessionkey = '%s', last_ip = '%s', last_login = NOW(), locale = '%u', failed_logins = 0 WHERE username = '%s'", K_hex, socket().get_remote_address().c_str(), GetLocaleByName(_localizationName), _safelogin.c_str()); + OPENSSL_free((void*)K_hex); + + ///- Finish SRP6 and send the final result to the client + sha.Initialize(); + sha.UpdateBigNumbers(&A, &M, &K, NULL); + sha.Finalize(); + + if (_expversion & POST_BC_EXP_FLAG)//2.4.3 and 3.1.3 clients (10146 is Chinese build for 3.1.3) + { + sAuthLogonProof_S proof; + memcpy(proof.M2, sha.GetDigest(), 20); + proof.cmd = AUTH_LOGON_PROOF; + proof.error = 0; + proof.unk1 = 0x00800000; + proof.unk2 = 0x00; + proof.unk3 = 0x00; + socket().send((char *)&proof, sizeof(proof)); + } + else + { + sAuthLogonProof_S_Old proof; + memcpy(proof.M2, sha.GetDigest(), 20); + proof.cmd = AUTH_LOGON_PROOF; + proof.error = 0; + //proof.unk1 = 0x00800000; + proof.unk2 = 0x00; + //proof.unk3 = 0x00; + socket().send((char *)&proof, sizeof(proof)); + } + + ///- Set _authed to true! + _authed = true; + } + else + { + char data[4]= { AUTH_LOGON_PROOF, WOW_FAIL_UNKNOWN_ACCOUNT, 3, 0}; + socket().send(data, sizeof(data)); + sLog.outBasic("[AuthChallenge] account %s tried to login with wrong password!",_login.c_str ()); + + uint32 MaxWrongPassCount = sConfig.GetIntDefault("WrongPass.MaxCount", 0); + if (MaxWrongPassCount > 0) + { + //Increment number of failed logins by one and if it reaches the limit temporarily ban that account or IP + LoginDatabase.PExecute("UPDATE account SET failed_logins = failed_logins + 1 WHERE username = '%s'",_safelogin.c_str()); + + if (QueryResult_AutoPtr loginfail = LoginDatabase.PQuery("SELECT id, failed_logins FROM account WHERE username = '%s'", _safelogin.c_str())) + { + Field* fields = loginfail->Fetch(); + uint32 failed_logins = fields[1].GetUInt32(); + + if (failed_logins >= MaxWrongPassCount) + { + uint32 WrongPassBanTime = sConfig.GetIntDefault("WrongPass.BanTime", 600); + bool WrongPassBanType = sConfig.GetBoolDefault("WrongPass.BanType", false); + + if (WrongPassBanType) + { + uint32 acc_id = fields[0].GetUInt32(); + LoginDatabase.PExecute("INSERT INTO account_banned VALUES ('%u',UNIX_TIMESTAMP(),UNIX_TIMESTAMP()+'%u','Trinity realmd','Failed login autoban',1)", + acc_id, WrongPassBanTime); + sLog.outBasic("[AuthChallenge] account %s got banned for '%u' seconds because it failed to authenticate '%u' times", + _login.c_str(), WrongPassBanTime, failed_logins); + } + else + { + std::string current_ip(socket().get_remote_address().c_str()); + LoginDatabase.escape_string(current_ip); + LoginDatabase.PExecute("INSERT INTO ip_banned VALUES ('%s',UNIX_TIMESTAMP(),UNIX_TIMESTAMP()+'%u','Trinity realmd','Failed login autoban')", + current_ip.c_str(), WrongPassBanTime); + sLog.outBasic("[AuthChallenge] IP %s got banned for '%u' seconds because account %s failed to authenticate '%u' times", + current_ip.c_str(), WrongPassBanTime, _login.c_str(), failed_logins); + } + } + } + } + } + + return true; +} + +/// Reconnect Challenge command handler +bool AuthSocket::_HandleReconnectChallenge() +{ + DEBUG_LOG("Entering _HandleReconnectChallenge"); + if (socket().recv_len() < sizeof(sAuthLogonChallenge_C)) + return false; + + ///- Read the first 4 bytes (header) to get the length of the remaining of the packet + std::vector<uint8> buf; + buf.resize(4); + + socket().recv((char *)&buf[0], 4); + + EndianConvert(*((uint16*)(buf[0]))); + uint16 remaining = ((sAuthLogonChallenge_C *)&buf[0])->size; + DEBUG_LOG("[ReconnectChallenge] got header, body is %#04x bytes", remaining); + + if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (socket().recv_len() < remaining)) + return false; + + //No big fear of memory outage (size is int16, i.e. < 65536) + buf.resize(remaining + buf.size() + 1); + buf[buf.size() - 1] = 0; + sAuthLogonChallenge_C *ch = (sAuthLogonChallenge_C*)&buf[0]; + + ///- Read the remaining of the packet + socket().recv((char *)&buf[4], remaining); + DEBUG_LOG("[ReconnectChallenge] got full packet, %#04x bytes", ch->size); + DEBUG_LOG("[ReconnectChallenge] name(%d): '%s'", ch->I_len, ch->I); + + _login = (const char*)ch->I; + _safelogin = _login; + + QueryResult_AutoPtr result = LoginDatabase.PQuery ("SELECT sessionkey FROM account WHERE username = '%s'", _safelogin.c_str ()); + + // Stop if the account is not found + if (!result) + { + sLog.outError("[ERROR] user %s tried to login and we cannot find his session key in the database.", _login.c_str()); + socket().shutdown(); + return false; + } + + Field* fields = result->Fetch (); + K.SetHexStr (fields[0].GetString ()); + + ///- Sending response + ByteBuffer pkt; + pkt << (uint8) AUTH_RECONNECT_CHALLENGE; + pkt << (uint8) 0x00; + _reconnectProof.SetRand(16 * 8); + pkt.append(_reconnectProof.AsByteArray(16), 16); // 16 bytes random + pkt << (uint64) 0x00 << (uint64) 0x00; // 16 bytes zeros + socket().send((char const*)pkt.contents(), pkt.size()); + return true; +} + +/// Reconnect Proof command handler +bool AuthSocket::_HandleReconnectProof() +{ + DEBUG_LOG("Entering _HandleReconnectProof"); + ///- Read the packet + sAuthReconnectProof_C lp; + if (!socket().recv((char *)&lp, sizeof(sAuthReconnectProof_C))) + return false; + + if (_login.empty() || !_reconnectProof.GetNumBytes() || !K.GetNumBytes()) + return false; + + BigNumber t1; + t1.SetBinary(lp.R1, 16); + + Sha1Hash sha; + sha.Initialize(); + sha.UpdateData(_login); + sha.UpdateBigNumbers(&t1, &_reconnectProof, &K, NULL); + sha.Finalize(); + + if (!memcmp(sha.GetDigest(), lp.R2, SHA_DIGEST_LENGTH)) + { + ///- Sending response + ByteBuffer pkt; + pkt << (uint8) AUTH_RECONNECT_PROOF; + pkt << (uint8) 0x00; + pkt << (uint16) 0x00; // 2 bytes zeros + socket().send((char const*)pkt.contents(), pkt.size()); + + ///- Set _authed to true! + _authed = true; + + return true; + } + else + { + sLog.outError("[ERROR] user %s tried to login, but session invalid.", _login.c_str()); + socket().shutdown(); + return false; + } +} + +/// %Realm List command handler +bool AuthSocket::_HandleRealmList() +{ + DEBUG_LOG("Entering _HandleRealmList"); + if (socket().recv_len() < 5) + return false; + + socket().recv_skip(5); + + ///- Get the user id (else close the connection) + // No SQL injection (escaped user name) + + QueryResult_AutoPtr result = LoginDatabase.PQuery("SELECT id FROM account WHERE username = '%s'",_safelogin.c_str()); + if (!result) + { + sLog.outError("[ERROR] user %s tried to login and we cannot find him in the database.",_login.c_str()); + socket().shutdown(); + return false; + } + + uint32 id = (*result)[0].GetUInt32(); + + ///- Update realm list if need + sRealmList->UpdateIfNeed(); + + ///- Circle through realms in the RealmList and construct the return packet (including # of user characters in each realm) + ByteBuffer pkt; + + size_t RealmListSize = 0; + for (RealmList::RealmMap::const_iterator i = sRealmList->begin(); i != sRealmList->end(); ++i) + { + // don't work with realms which not compatible with the client + if (_expversion & POST_BC_EXP_FLAG) // 2.4.3 and 3.1.3 cliens + { + if (i->second.gamebuild != _build) + continue; + } + else if (_expversion & PRE_BC_EXP_FLAG) // 1.12.1 and 1.12.2 clients are compatible with eachother + { + if (!AuthHelper::IsPreBCAcceptedClientBuild(i->second.gamebuild)) + continue; + } + + uint8 AmountOfCharacters; + + // No SQL injection. id of realm is controlled by the database. + result = LoginDatabase.PQuery("SELECT numchars FROM realmcharacters WHERE realmid = '%d' AND acctid='%u'",i->second.m_ID,id); + if (result) + { + Field *fields = result->Fetch(); + AmountOfCharacters = fields[0].GetUInt8(); + } + else + AmountOfCharacters = 0; + + uint8 lock = (i->second.allowedSecurityLevel > _accountSecurityLevel) ? 1 : 0; + + pkt << i->second.icon; // realm type + if ( _expversion & POST_BC_EXP_FLAG )//only 2.4.3 and 3.1.3 cliens + pkt << lock; // if 1, then realm locked + pkt << i->second.color; // if 2, then realm is offline + pkt << i->first; + pkt << i->second.address; + pkt << i->second.populationLevel; + pkt << AmountOfCharacters; + pkt << i->second.timezone; // realm category + if ( _expversion & POST_BC_EXP_FLAG )//2.4.3 and 3.1.3 clients + pkt << (uint8) 0x2C; // unk, may be realm number/id? + else + pkt << (uint8) 0x0; //1.12.1 and 1.12.2 clients + + ++RealmListSize; + } + + if ( _expversion & POST_BC_EXP_FLAG )//2.4.3 and 3.1.3 cliens + { + pkt << (uint8) 0x10; + pkt << (uint8) 0x00; + }else{//1.12.1 and 1.12.2 clients + pkt << (uint8) 0x00; + pkt << (uint8) 0x02; + } + + // make a ByteBuffer which stores the RealmList's size + ByteBuffer RealmListSizeBuffer; + RealmListSizeBuffer << (uint32)0; + if (_expversion & POST_BC_EXP_FLAG) // only 2.4.3 and 3.1.3 cliens + RealmListSizeBuffer << (uint16)RealmListSize; + else + RealmListSizeBuffer << (uint32)RealmListSize; + + ByteBuffer hdr; + hdr << (uint8) REALM_LIST; + hdr << (uint16)(pkt.size() + RealmListSizeBuffer.size()); + hdr.append(RealmListSizeBuffer); // append RealmList's size buffer + hdr.append(pkt); // append realms in the realmlist + + socket().send((char const*)hdr.contents(), hdr.size()); + + return true; +} + +/// Resume patch transfer +bool AuthSocket::_HandleXferResume() +{ + DEBUG_LOG("Entering _HandleXferResume"); + ///- Check packet length and patch existence + if (socket().recv_len() < 9 || !pPatch) + { + sLog.outError("Error while resuming patch transfer (wrong packet)"); + return false; + } + + ///- Launch a PatcherRunnable thread starting at given patch file offset + uint64 start; + socket().recv_skip(1); + socket().recv((char*)&start,sizeof(start)); + fseek(pPatch, start, 0); + + ACE_Based::Thread u(new PatcherRunnable(this)); + return true; +} + +/// Cancel patch transfer +bool AuthSocket::_HandleXferCancel() +{ + DEBUG_LOG("Entering _HandleXferCancel"); + + ///- Close and delete the socket + socket().recv_skip(1); //clear input buffer + + socket().shutdown(); + + return true; +} + +/// Accept patch transfer +bool AuthSocket::_HandleXferAccept() +{ + DEBUG_LOG("Entering _HandleXferAccept"); + + ///- Check packet length and patch existence + if (!pPatch) + { + sLog.outError("Error while accepting patch transfer (wrong packet)"); + return false; + } + + ///- Launch a PatcherRunnable thread, starting at the beginning of the patch file + socket().recv_skip(1); // clear input buffer + fseek(pPatch, 0, 0); + + ACE_Based::Thread u(new PatcherRunnable(this)); + return true; +} + +PatcherRunnable::PatcherRunnable(class AuthSocket * as) +{ + mySocket = as; +} + +/// Send content of patch file to the client +void PatcherRunnable::run() +{ +} + +/// Preload MD5 hashes of existing patch files on server +#ifndef _WIN32 +#include <dirent.h> +#include <errno.h> +void Patcher::LoadPatchesInfo() +{ + DIR * dirp; + //int errno; + struct dirent * dp; + dirp = opendir("./patches/"); + if (!dirp) + return; + while (dirp) + { + errno = 0; + if ((dp = readdir(dirp)) != NULL) + { + int l = strlen(dp->d_name); + if (l < 8) + continue; + if (!memcmp(&dp->d_name[l-4],".mpq",4)) + LoadPatchMD5(dp->d_name); + } + else + { + if (errno != 0) + { + closedir(dirp); + return; + } + break; + } + } + + if (dirp) + closedir(dirp); +} + +#else +void Patcher::LoadPatchesInfo() +{ + WIN32_FIND_DATA fil; + HANDLE hFil=FindFirstFile("./patches/*.mpq", &fil); + if (hFil == INVALID_HANDLE_VALUE) + return; // no patches were found + + do + { + LoadPatchMD5(fil.cFileName); + } + while(FindNextFile(hFil, &fil)); +} +#endif + +/// Calculate and store MD5 hash for a given patch file +void Patcher::LoadPatchMD5(char * szFileName) +{ + ///- Try to open the patch file + std::string path = "./patches/"; + path += szFileName; + FILE *pPatch = fopen(path.c_str(), "rb"); + sLog.outDebug("Loading patch info from %s\n", path.c_str()); + if (!pPatch) + { + sLog.outError("Error loading patch %s\n", path.c_str()); + return; + } + + ///- Calculate the MD5 hash + MD5_CTX ctx; + MD5_Init(&ctx); + uint8* buf = new uint8[512*1024]; + + while (!feof(pPatch)) + { + size_t read = fread(buf, 1, 512*1024, pPatch); + MD5_Update(&ctx, buf, read); + } + delete [] buf; + fclose(pPatch); + + ///- Store the result in the internal patch hash map + _patches[path] = new PATCH_INFO; + MD5_Final((uint8 *)&_patches[path]->md5, &ctx); +} + +/// Get cached MD5 hash for a given patch file +bool Patcher::GetHash(char * pat, uint8 mymd5[16]) +{ + for (Patches::iterator i = _patches.begin(); i != _patches.end(); ++i) + if (!stricmp(pat, i->first.c_str())) + { + memcpy(mymd5, i->second->md5, 16); + return true; + } + + return false; +} + +/// Launch the patch hashing mechanism on object creation +Patcher::Patcher() +{ + LoadPatchesInfo(); +} + +/// Empty and delete the patch map on termination +Patcher::~Patcher() +{ + for (Patches::iterator i = _patches.begin(); i != _patches.end(); ++i) + delete i->second; +} diff --git a/src/server/authserver/Server/AuthSocket.h b/src/server/authserver/Server/AuthSocket.h new file mode 100644 index 00000000000..bfd0fa4fdca --- /dev/null +++ b/src/server/authserver/Server/AuthSocket.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/// \addtogroup realmd +/// @{ +/// \file + +#ifndef _AUTHSOCKET_H +#define _AUTHSOCKET_H + +#include "Common.h" +#include "Auth/BigNumber.h" + +#include "RealmSocket.h" + +enum RealmFlags +{ + REALM_FLAG_NONE = 0x00, + REALM_FLAG_INVALID = 0x01, + REALM_FLAG_OFFLINE = 0x02, + REALM_FLAG_SPECIFYBUILD = 0x04, // client will show realm version in RealmList screen in form "RealmName (major.minor.revision.build)" + REALM_FLAG_UNK1 = 0x08, + REALM_FLAG_UNK2 = 0x10, + REALM_FLAG_RECOMMENDED = 0x20, // client checks pop == 600f + REALM_FLAG_NEW = 0x40, // client checks pop == 200f + REALM_FLAG_FULL = 0x80 // client checks pop == 400f +}; + +/// Handle login commands +class AuthSocket: public RealmSocket::Session +{ + public: + const static int s_BYTE_SIZE = 32; + + AuthSocket(RealmSocket& socket); + virtual ~AuthSocket(void); + + virtual void OnRead(void); + virtual void OnAccept(void); + virtual void OnClose(void); + + bool _HandleLogonChallenge(); + bool _HandleLogonProof(); + bool _HandleReconnectChallenge(); + bool _HandleReconnectProof(); + bool _HandleRealmList(); + //data transfer handle for patch + + bool _HandleXferResume(); + bool _HandleXferCancel(); + bool _HandleXferAccept(); + + void _SetVSFields(const std::string& rI); + + FILE *pPatch; + ACE_Thread_Mutex patcherLock; + + private: + RealmSocket& socket_; + RealmSocket& socket(void) { return socket_; } + + BigNumber N, s, g, v; + BigNumber b, B; + BigNumber K; + BigNumber _reconnectProof; + + bool _authed; + + std::string _login; + std::string _safelogin; + + // Since GetLocaleByName() is _NOT_ bijective, we have to store the locale as a string. Otherwise we can't differ + // between enUS and enGB, which is important for the patch system + std::string _localizationName; + uint16 _build; + uint8 _expversion; + AccountTypes _accountSecurityLevel; +}; +#endif +/// @} diff --git a/src/server/authserver/Server/RealmAcceptor.h b/src/server/authserver/Server/RealmAcceptor.h new file mode 100644 index 00000000000..5e243ea915b --- /dev/null +++ b/src/server/authserver/Server/RealmAcceptor.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** \file + \ingroup realmd + */ + +#ifndef __REALMACCEPTOR_H__ +#define __REALMACCEPTOR_H__ + +#include <ace/Acceptor.h> +#include <ace/SOCK_Acceptor.h> + +#include "RealmSocket.h" +#include "AuthSocket.h" + +class RealmAcceptor : public ACE_Acceptor<RealmSocket, ACE_SOCK_Acceptor> +{ + public: + RealmAcceptor(void) { } + virtual ~RealmAcceptor(void) { } + + protected: + virtual int make_svc_handler(RealmSocket *&sh) + { + if (sh == 0) + ACE_NEW_RETURN(sh, RealmSocket, -1); + + sh->reactor(reactor()); + sh->set_session(new AuthSocket(*sh)); + return 0; + } +}; + +#endif /* __REALMACCEPTOR_H__ */ diff --git a/src/server/authserver/Server/RealmSocket.cpp b/src/server/authserver/Server/RealmSocket.cpp new file mode 100644 index 00000000000..7eb96cb96f8 --- /dev/null +++ b/src/server/authserver/Server/RealmSocket.cpp @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** \file + \ingroup realmd + */ + +#include "RealmSocket.h" + +#include "Log.h" + +#include <ace/OS_NS_string.h> +#include <ace/INET_Addr.h> +#include <ace/SString.h> + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +RealmSocket::Session::Session(void) +{ +} + +RealmSocket::Session::~Session(void) +{ +} + +RealmSocket::RealmSocket(void): + session_(NULL), + input_buffer_(4096), + remote_address_() +{ + reference_counting_policy().value( + ACE_Event_Handler::Reference_Counting_Policy::ENABLED); + + msg_queue()->high_water_mark(8*1024*1024); + msg_queue()->low_water_mark(8*1024*1024); + +} + +RealmSocket::~RealmSocket(void) +{ + if (msg_queue()) + msg_queue()->close(); + + // delete RealmSocketObject must never be called from our code. + closing_ = true; + + if (session_) + delete session_; + + peer().close(); +} + +int RealmSocket::open(void * arg) +{ + ACE_INET_Addr addr; + + if (peer ().get_remote_addr (addr) == -1) + { + sLog.outError ("RealmSocket::open: peer ().get_remote_addr errno = %s", ACE_OS::strerror (errno)); + return -1; + } + + remote_address_ = addr.get_host_addr(); + + // Register with ACE Reactor + if (Base::open(arg) == -1) + return -1; + + if (session_ != NULL) + { + session_->OnAccept(); + } + + // reactor takes care of the socket from now on + remove_reference(); + + return 0; +} + +int RealmSocket::close(int) +{ + shutdown(); + + closing_ = true; + + remove_reference(); + + return 0; +} + +const ACE_CString& RealmSocket::get_remote_address(void) const +{ + return remote_address_; +} + +size_t RealmSocket::recv_len(void) const +{ + return input_buffer_.length(); +} + +bool RealmSocket::recv_soft(char *buf, size_t len) +{ + if (input_buffer_.length() < len) + return false; + + ACE_OS::memcpy(buf, input_buffer_.rd_ptr(), len); + + return true; +} + +bool RealmSocket::recv(char *buf, size_t len) +{ + bool ret = recv_soft(buf, len); + + if (ret) + recv_skip(len); + + return ret; +} + +void RealmSocket::recv_skip(size_t len) +{ + input_buffer_.rd_ptr(len); +} + +ssize_t RealmSocket::noblk_send(ACE_Message_Block &message_block) +{ + const size_t len = message_block.length(); + + if (len == 0) + return -1; + + // Try to send the message directly. + ssize_t n = peer().send(message_block.rd_ptr(), len, MSG_NOSIGNAL); + + if (n < 0) + { + if (errno == EWOULDBLOCK) + // Blocking signal + return 0; + else + // Error happened + return -1; + } + else if (n == 0) + { + // Can this happen ? + return -1; + } + + // return bytes transmitted + return n; +} + +bool RealmSocket::send(const char *buf, size_t len) +{ + if (buf == NULL || len == 0) + return true; + + ACE_Data_Block db( + len, + ACE_Message_Block::MB_DATA, + (const char*)buf, + 0, + 0, + ACE_Message_Block::DONT_DELETE, + 0); + + ACE_Message_Block message_block( + &db, + ACE_Message_Block::DONT_DELETE, + 0); + + message_block.wr_ptr(len); + + if (msg_queue()->is_empty()) + { + // Try to send it directly. + ssize_t n = noblk_send(message_block); + + if (n < 0) + return false; + else if (n == len) + return true; + + // fall down + message_block.rd_ptr((size_t)n); + } + + ACE_Message_Block *mb = message_block.clone(); + + if (msg_queue()->enqueue_tail(mb, (ACE_Time_Value *) &ACE_Time_Value::zero) == -1) + { + mb->release(); + return false; + } + + if (reactor()->schedule_wakeup(this, ACE_Event_Handler::WRITE_MASK) == -1) + return false; + + return true; +} + +int RealmSocket::handle_output(ACE_HANDLE /*= ACE_INVALID_HANDLE*/) +{ + if (closing_) + return -1; + + ACE_Message_Block *mb = 0; + + if (msg_queue()->is_empty()) + { + reactor()->cancel_wakeup(this, ACE_Event_Handler::WRITE_MASK); + return 0; + } + + if (msg_queue()->dequeue_head(mb, (ACE_Time_Value *) &ACE_Time_Value::zero) == -1) + return -1; + + ssize_t n = noblk_send(*mb); + + if (n < 0) + { + mb->release(); + return -1; + } + else if (n == mb->length()) + { + mb->release(); + return 1; + } + else + { + mb->rd_ptr(n); + + if (msg_queue()->enqueue_head(mb, (ACE_Time_Value *) &ACE_Time_Value::zero) == -1) + { + mb->release(); + return -1; + } + + return 0; + } + + ACE_NOTREACHED(return -1); +} + +int RealmSocket::handle_close(ACE_HANDLE h, ACE_Reactor_Mask /*m*/) +{ + // As opposed to WorldSocket::handle_close, we don't need locks here. + + closing_ = true; + + if (h == ACE_INVALID_HANDLE) + peer ().close_writer (); + + if (session_ != NULL) + { + session_->OnClose(); + } + + return 0; +} + +int RealmSocket::handle_input(ACE_HANDLE /*= ACE_INVALID_HANDLE*/) +{ + if (closing_) + return -1; + + const ssize_t space = input_buffer_.space(); + + ssize_t n = peer().recv(input_buffer_.wr_ptr(), space); + + if (n < 0) + { + return errno == EWOULDBLOCK ? 0 : -1; + } + else if (n == 0) + { + // EOF + return -1; + } + + input_buffer_.wr_ptr((size_t)n); + + if (session_ != NULL) + { + session_->OnRead(); + + input_buffer_.crunch(); + } + + // return 1 in case there is more data to read from OS + return n == space ? 1 : 0; +} + + +void RealmSocket::set_session(Session* session) +{ + if (session_ != NULL) + delete session_; + + session_ = session; +} + diff --git a/src/server/authserver/Server/RealmSocket.h b/src/server/authserver/Server/RealmSocket.h new file mode 100644 index 00000000000..13be8327533 --- /dev/null +++ b/src/server/authserver/Server/RealmSocket.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** \file + \ingroup realmd + */ + +#ifndef __REALMSOCKET_H__ +#define __REALMSOCKET_H__ + +#include <ace/Basic_Types.h> +#include <ace/Synch_Traits.h> +#include <ace/Svc_Handler.h> +#include <ace/SOCK_Stream.h> +#include <ace/Message_Block.h> +#include <ace/Basic_Types.h> + +class RealmSocket : public ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH> +{ + private: + typedef ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH> Base; + + public: + class Session + { + public: + Session(void); + virtual ~Session(void); + + virtual void OnRead(void) = 0; + virtual void OnAccept(void) = 0; + virtual void OnClose(void) = 0; + }; + + RealmSocket(void); + virtual ~RealmSocket(void); + + size_t recv_len(void) const; + bool recv_soft(char *buf, size_t len); + bool recv(char *buf, size_t len); + void recv_skip(size_t len); + + bool send(const char *buf, size_t len); + + const ACE_CString& get_remote_address(void) const; + + virtual int open(void *); + + virtual int close(int); + + virtual int handle_input(ACE_HANDLE = ACE_INVALID_HANDLE); + virtual int handle_output(ACE_HANDLE = ACE_INVALID_HANDLE); + + virtual int handle_close(ACE_HANDLE = ACE_INVALID_HANDLE, + ACE_Reactor_Mask = ACE_Event_Handler::ALL_EVENTS_MASK); + + void set_session(Session* session); + + private: + ssize_t noblk_send(ACE_Message_Block &message_block); + + private: + ACE_Message_Block input_buffer_; + Session* session_; + ACE_CString remote_address_; +}; + +#endif /* __REALMSOCKET_H__ */ diff --git a/src/server/worldserver/CommandLine/CliRunnable.cpp b/src/server/worldserver/CommandLine/CliRunnable.cpp new file mode 100644 index 00000000000..b39faf694db --- /dev/null +++ b/src/server/worldserver/CommandLine/CliRunnable.cpp @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/// \addtogroup Trinityd +/// @{ +/// \file + +#include "Common.h" +#include "ObjectMgr.h" +#include "World.h" +#include "WorldSession.h" +#include "Config/ConfigEnv.h" + +#include "AccountMgr.h" +#include "Chat.h" +#include "CliRunnable.h" +#include "Language.h" +#include "Log.h" +#include "MapManager.h" +#include "Player.h" +#include "Util.h" + +#if PLATFORM != WINDOWS +#include <readline/readline.h> +#include <readline/history.h> + +char * command_finder(const char* text, int state) +{ + static int idx,len; + const char* ret; + ChatCommand *cmd = ChatHandler::getCommandTable(); + + if(!state) + { + idx = 0; + len = strlen(text); + } + + while(ret = cmd[idx].Name) + { + if(!cmd[idx].AllowConsole) + { + idx++; + continue; + } + + idx++; + //printf("Checking %s \n", cmd[idx].Name); + if (strncmp(ret, text, len) == 0) + return strdup(ret); + if(cmd[idx].Name == NULL) + break; + } + + return ((char*)NULL); +} + +char ** cli_completion(const char * text, int start, int end) +{ + char ** matches; + matches = (char**)NULL; + + if(start == 0) + matches = rl_completion_matches((char*)text,&command_finder); + else + rl_bind_key('\t',rl_abort); + return (matches); +} +#endif + +void utf8print(const char* str) +{ +#if PLATFORM == PLATFORM_WINDOWS + wchar_t wtemp_buf[6000]; + size_t wtemp_len = 6000-1; + if(!Utf8toWStr(str,strlen(str),wtemp_buf,wtemp_len)) + return; + + char temp_buf[6000]; + CharToOemBuffW(&wtemp_buf[0],&temp_buf[0],wtemp_len+1); + printf(temp_buf); +#else +{ + va_list v; + vprintf(str, v); + va_end(v); + fflush(stdout); +} +#endif +} + +/// Delete a user account and all associated characters in this realm +/// \todo This function has to be enhanced to respect the login/realm split (delete char, delete account chars in realm, delete account chars in realm then delete account +bool ChatHandler::HandleAccountDeleteCommand(const char* args) +{ + if(!*args) + return false; + + ///- Get the account name from the command line + char *account_name_str=strtok ((char*)args," "); + if (!account_name_str) + return false; + + std::string account_name = account_name_str; + if(!AccountMgr::normalizeString(account_name)) + { + PSendSysMessage(LANG_ACCOUNT_NOT_EXIST,account_name.c_str()); + SetSentErrorMessage(true); + return false; + } + + uint32 account_id = accmgr.GetId(account_name); + if(!account_id) + { + PSendSysMessage(LANG_ACCOUNT_NOT_EXIST,account_name.c_str()); + SetSentErrorMessage(true); + return false; + } + + /// Commands not recommended call from chat, but support anyway + /// can delete only for account with less security + /// This is also reject self apply in fact + if(HasLowerSecurityAccount (NULL,account_id,true)) + return false; + + AccountOpResult result = accmgr.DeleteAccount(account_id); + switch(result) + { + case AOR_OK: + PSendSysMessage(LANG_ACCOUNT_DELETED,account_name.c_str()); + break; + case AOR_NAME_NOT_EXIST: + PSendSysMessage(LANG_ACCOUNT_NOT_EXIST,account_name.c_str()); + SetSentErrorMessage(true); + return false; + case AOR_DB_INTERNAL_ERROR: + PSendSysMessage(LANG_ACCOUNT_NOT_DELETED_SQL_ERROR,account_name.c_str()); + SetSentErrorMessage(true); + return false; + default: + PSendSysMessage(LANG_ACCOUNT_NOT_DELETED,account_name.c_str()); + SetSentErrorMessage(true); + return false; + } + + return true; +} + +bool ChatHandler::HandleCharacterDeleteCommand(const char* args) +{ + if(!*args) + return false; + + char *character_name_str = strtok((char*)args," "); + if(!character_name_str) + return false; + + std::string character_name = character_name_str; + if(!normalizePlayerName(character_name)) + return false; + + uint64 character_guid; + uint32 account_id; + + Player *player = objmgr.GetPlayer(character_name.c_str()); + if(player) + { + character_guid = player->GetGUID(); + account_id = player->GetSession()->GetAccountId(); + player->GetSession()->KickPlayer(); + } + else + { + character_guid = objmgr.GetPlayerGUIDByName(character_name); + if(!character_guid) + { + PSendSysMessage(LANG_NO_PLAYER,character_name.c_str()); + SetSentErrorMessage(true); + return false; + } + + account_id = objmgr.GetPlayerAccountIdByGUID(character_guid); + } + + std::string account_name; + accmgr.GetName (account_id,account_name); + + Player::DeleteFromDB(character_guid, account_id, true); + PSendSysMessage(LANG_CHARACTER_DELETED,character_name.c_str(),GUID_LOPART(character_guid),account_name.c_str(), account_id); + return true; +} + +/// Exit the realm +bool ChatHandler::HandleServerExitCommand(const char* /*args*/) +{ + SendSysMessage(LANG_COMMAND_EXIT); + World::StopNow(SHUTDOWN_EXIT_CODE); + return true; +} + +/// Display info on users currently in the realm +bool ChatHandler::HandleAccountOnlineListCommand(const char* /*args*/) +{ + ///- Get the list of accounts ID logged to the realm + QueryResult_AutoPtr resultDB = CharacterDatabase.Query("SELECT name,account,map,zone FROM characters WHERE online > 0"); + if (!resultDB) + { + SendSysMessage(LANG_ACCOUNT_LIST_EMPTY); + return true; + } + + ///- Display the list of account/characters online + SendSysMessage(LANG_ACCOUNT_LIST_BAR_HEADER); + SendSysMessage(LANG_ACCOUNT_LIST_HEADER); + SendSysMessage(LANG_ACCOUNT_LIST_BAR); + + ///- Circle through accounts + do + { + Field *fieldsDB = resultDB->Fetch(); + std::string name = fieldsDB[0].GetCppString(); + uint32 account = fieldsDB[1].GetUInt32(); + + ///- Get the username, last IP and GM level of each account + // No SQL injection. account is uint32. + QueryResult_AutoPtr resultLogin = + LoginDatabase.PQuery("SELECT a.username, a.last_ip, aa.gmlevel, a.expansion " + "FROM account a " + "LEFT JOIN account_access aa " + "ON (a.id = aa.id) " + "WHERE a.id = '%u'", account); + if(resultLogin) + { + Field *fieldsLogin = resultLogin->Fetch(); + PSendSysMessage(LANG_ACCOUNT_LIST_LINE, + fieldsLogin[0].GetString(),name.c_str(),fieldsLogin[1].GetString(),fieldsDB[2].GetInt32(),fieldsDB[3].GetInt32(),fieldsLogin[3].GetUInt32(),fieldsLogin[2].GetUInt32()); + } + else + PSendSysMessage(LANG_ACCOUNT_LIST_ERROR,name.c_str()); + + }while(resultDB->NextRow()); + + SendSysMessage(LANG_ACCOUNT_LIST_BAR); + return true; +} + +/// Create an account +bool ChatHandler::HandleAccountCreateCommand(const char* args) +{ + if(!*args) + return false; + + ///- %Parse the command line arguments + char *szAcc = strtok((char*)args, " "); + char *szPassword = strtok(NULL, " "); + if(!szAcc || !szPassword) + return false; + + // normalized in accmgr.CreateAccount + std::string account_name = szAcc; + std::string password = szPassword; + + AccountOpResult result = accmgr.CreateAccount(account_name, password); + switch(result) + { + case AOR_OK: + PSendSysMessage(LANG_ACCOUNT_CREATED,account_name.c_str()); + break; + case AOR_NAME_TOO_LONG: + SendSysMessage(LANG_ACCOUNT_TOO_LONG); + SetSentErrorMessage(true); + return false; + case AOR_NAME_ALREDY_EXIST: + SendSysMessage(LANG_ACCOUNT_ALREADY_EXIST); + SetSentErrorMessage(true); + return false; + case AOR_DB_INTERNAL_ERROR: + PSendSysMessage(LANG_ACCOUNT_NOT_CREATED_SQL_ERROR,account_name.c_str()); + SetSentErrorMessage(true); + return false; + default: + PSendSysMessage(LANG_ACCOUNT_NOT_CREATED,account_name.c_str()); + SetSentErrorMessage(true); + return false; + } + + return true; +} + +/// Set the level of logging +bool ChatHandler::HandleServerSetLogFileLevelCommand(const char *args) +{ + if(!*args) + return false; + + char *NewLevel = strtok((char*)args, " "); + if (!NewLevel) + return false; + + sLog.SetLogFileLevel(NewLevel); + return true; +} + +/// Set the level of logging +bool ChatHandler::HandleServerSetLogLevelCommand(const char *args) +{ + if(!*args) + return false; + + char *NewLevel = strtok((char*)args, " "); + if (!NewLevel) + return false; + + sLog.SetLogLevel(NewLevel); + return true; +} + +/// set diff time record interval +bool ChatHandler::HandleServerSetDiffTimeCommand(const char *args) +{ + if(!*args) + return false; + + char *NewTimeStr = strtok((char*)args, " "); + if(!NewTimeStr) + return false; + + int32 NewTime =atoi(NewTimeStr); + if(NewTime < 0) + return false; + + sWorld.SetRecordDiffInterval(NewTime); + printf( "Record diff every %u ms\n", NewTime); + return true; +} + +/// @} + +#ifdef linux +// Non-blocking keypress detector, when return pressed, return 1, else always return 0 +int kb_hit_return() +{ + struct timeval tv; + fd_set fds; + tv.tv_sec = 0; + tv.tv_usec = 0; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + select(STDIN_FILENO+1, &fds, NULL, NULL, &tv); + return FD_ISSET(STDIN_FILENO, &fds); +} +#endif + +/// %Thread start +void CliRunnable::run() +{ + ///- Init new SQL thread for the world database (one connection call enough) + WorldDatabase.ThreadStart(); // let thread do safe mySQL requests + + char commandbuf[256]; + + ///- Display the list of available CLI functions then beep + sLog.outString(""); + #if PLATFORM != WINDOWS + rl_attempted_completion_function = cli_completion; + #endif + if(sConfig.GetBoolDefault("BeepAtStart", true)) + printf("\a"); // \a = Alert + + // print this here the first time + // later it will be printed after command queue updates + printf("TC>"); + + ///- As long as the World is running (no World::m_stopEvent), get the command line and handle it + while (!World::IsStopped()) + { + fflush(stdout); + + char *command_str ; // = fgets(commandbuf,sizeof(commandbuf),stdin); + + #if PLATFORM == WINDOWS + command_str = fgets(commandbuf,sizeof(commandbuf),stdin); + #else + command_str = readline("TC>"); + rl_bind_key('\t',rl_complete); + #endif + if (command_str != NULL) + { + for (int x=0; command_str[x]; x++) + if(command_str[x]=='\r'||command_str[x]=='\n') + { + command_str[x]=0; + break; + } + + if(!*command_str) + { + #if PLATFORM == WINDOWS + printf("TC>"); + #endif + continue; + } + + std::string command; + if(!consoleToUtf8(command_str,command)) // convert from console encoding to utf8 + { + #if PLATFORM == WINDOWS + printf("TC>"); + #endif + continue; + } + fflush(stdout); + sWorld.QueueCliCommand(&utf8print,command.c_str()); + #if PLATFORM != WINDOWS + add_history(command.c_str()); + #endif + + } + else if (feof(stdin)) + { + World::StopNow(SHUTDOWN_EXIT_CODE); + } + + } + + ///- End the database thread + WorldDatabase.ThreadEnd(); // free mySQL thread resources +} diff --git a/src/server/worldserver/CommandLine/CliRunnable.h b/src/server/worldserver/CommandLine/CliRunnable.h new file mode 100644 index 00000000000..9f990b2b469 --- /dev/null +++ b/src/server/worldserver/CommandLine/CliRunnable.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/// \addtogroup Trinityd +/// @{ +/// \file + +#ifndef __CLIRUNNABLE_H +#define __CLIRUNNABLE_H + +/// Command Line Interface handling thread +class CliRunnable : public ACE_Based::Runnable +{ + public: + void run(); +}; +#endif +/// @} diff --git a/src/server/worldserver/RemoteAccess/RASocket.cpp b/src/server/worldserver/RemoteAccess/RASocket.cpp new file mode 100644 index 00000000000..32c16d9980f --- /dev/null +++ b/src/server/worldserver/RemoteAccess/RASocket.cpp @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** \file + \ingroup Trinityd +*/ + +#include "Common.h" +#include "Config/ConfigEnv.h" +#include "Database/DatabaseEnv.h" +#include "AccountMgr.h" +#include "Log.h" +#include "RASocket.h" +#include "Util.h" +#include "World.h" + +/// \todo Make this thread safe if in the future 2 admins should be able to log at the same time. +SOCKET r; + +#define dropclient {Sendf("I'm busy right now, come back later."); \ + SetCloseAndDelete(); \ + return; \ + } + +uint32 iSession=0; ///< Session number (incremented each time a new connection is made) +unsigned int iUsers=0; ///< Number of active administrators + +typedef int(* pPrintf)(const char*,...); + +void ParseCommand(CliCommandHolder::Print*, char*command); + +/// RASocket constructor +RASocket::RASocket(ISocketHandler &h): TcpSocket(h) +{ + + ///- Increment the session number + iSess =iSession++ ; + + ///- Get the config parameters + bSecure = sConfig.GetBoolDefault( "RA.Secure", true ); + iMinLevel = sConfig.GetIntDefault( "RA.MinLevel", 3 ); + + ///- Initialize buffer and data + iInputLength=0; + buff=new char[RA_BUFF_SIZE]; + stage=NONE; +} + +/// RASocket destructor +RASocket::~RASocket() +{ + ///- Delete buffer and decrease active admins count + delete [] buff; + + sLog.outRemote("Connection was closed.\n"); + + if(stage==OK) + iUsers--; +} + +/// Accept an incoming connection +void RASocket::OnAccept() +{ + std::string ss=GetRemoteAddress(); + sLog.outRemote("Incoming connection from %s.\n",ss.c_str()); + ///- If there is already an active admin, drop the connection + if(iUsers) + dropclient + + ///- Else print Motd + Sendf("%s\r\n",sWorld.GetMotd()); +} + +/// Read data from the network +void RASocket::OnRead() +{ + ///- Read data and check input length + TcpSocket::OnRead(); + + unsigned int sz=ibuf.GetLength(); + if(iInputLength+sz>=RA_BUFF_SIZE) + { + sLog.outRemote("Input buffer overflow, possible DOS attack.\n"); + SetCloseAndDelete(); + return; + } + + ///- If there is already an active admin (other than you), drop the connection + if(stage!=OK && iUsers) + dropclient + + char *inp = new char [sz+1]; + ibuf.Read(inp,sz); + + /// \todo Can somebody explain this 'Linux bugfix'? + if(stage==NONE) + if(sz>4) //linux remote telnet + if(memcmp(inp ,"USER ",5)) + { + delete [] inp;return; + printf("lin bugfix"); + } //linux bugfix + + ///- Discard data after line break or line feed + bool gotenter=false; + unsigned int y=0; + for (; y<sz; y++) + if(inp[y]=='\r'||inp[y]=='\n') + { + gotenter=true; + break; + } + + //No buffer overflow (checked above) + memcpy(&buff[iInputLength],inp,y); + iInputLength+=y; + delete [] inp; + if(gotenter) + { + + buff[iInputLength]=0; + iInputLength=0; + switch(stage) + { + /// <ul> <li> If the input is 'USER <username>' + case NONE: + if(!memcmp(buff,"USER ",5)) //got "USER" cmd + { + szLogin=&buff[5]; + + ///- Get the password from the account table + std::string login = szLogin; + + ///- Convert Account name to Upper Format + AccountMgr::normalizeString(login); + + ///- Escape the Login to allow quotes in names + LoginDatabase.escape_string(login); + + QueryResult_AutoPtr result = LoginDatabase.PQuery("SELECT a.id, aa.gmlevel, aa.RealmID FROM account a LEFT JOIN account_access aa ON (a.id = aa.id) WHERE a.username = '%s'",login.c_str ()); + + ///- If the user is not found, deny access + if(!result) + { + Sendf("-No such user.\r\n"); + sLog.outRemote("User %s does not exist.\n",szLogin.c_str()); + if(bSecure)SetCloseAndDelete(); + } + else + { + Field *fields = result->Fetch(); + + //szPass=fields[0].GetString(); + + ///- if gmlevel is too low, deny access + if(fields[1].GetUInt32()<iMinLevel || fields[1].GetUInt32() == NULL) + { + Sendf("-Not enough privileges.\r\n"); + sLog.outRemote("User %s has no privilege.\n",szLogin.c_str()); + if(bSecure)SetCloseAndDelete(); + } + else if(fields[2].GetInt32() != -1) + { + ///- if RealmID isn't -1, deny access + Sendf("-Not enough privileges.\r\n"); + sLog.outRemote("User %s has to be assigned on all realms (with RealmID = '-1').\n",szLogin.c_str()); + if(bSecure)SetCloseAndDelete(); + } + else + { + stage=LG; + } + } + } + break; + ///<li> If the input is 'PASS <password>' (and the user already gave his username) + case LG: + if(!memcmp(buff,"PASS ",5)) //got "PASS" cmd + { //login+pass ok + ///- If password is correct, increment the number of active administrators + std::string login = szLogin; + std::string pw = &buff[5]; + + AccountMgr::normalizeString(login); + AccountMgr::normalizeString(pw); + LoginDatabase.escape_string(login); + LoginDatabase.escape_string(pw); + + QueryResult_AutoPtr check = LoginDatabase.PQuery( + "SELECT 1 FROM account WHERE username = '%s' AND sha_pass_hash=SHA1(CONCAT('%s',':','%s'))", + login.c_str(), login.c_str(), pw.c_str()); + + if(check) + { + r=GetSocket(); + stage=OK; + ++iUsers; + + Sendf("+Logged in.\r\n"); + sLog.outRemote("User %s has logged in.\n",szLogin.c_str()); + Sendf("TC>"); + } + else + { + ///- Else deny access + Sendf("-Wrong pass.\r\n"); + sLog.outRemote("User %s has failed to log in.\n",szLogin.c_str()); + if(bSecure)SetCloseAndDelete(); + } + } + break; + ///<li> If user is logged, parse and execute the command + case OK: + if(strlen(buff)) + { + sLog.outRemote("Got '%s' cmd.\n",buff); + sWorld.QueueCliCommand(&RASocket::zprint , buff); + } + else + Sendf("TC>"); + break; + ///</ul> + }; + + } +} + +/// Output function +void RASocket::zprint( const char * szText ) +{ + if( !szText ) + return; + + #ifdef RA_CRYPT + + char *megabuffer=strdup(szText); + unsigned int sz=strlen(megabuffer); + Encrypt(megabuffer,sz); + send(r,megabuffer,sz,0); + delete [] megabuffer; + + #else + + unsigned int sz=strlen(szText); + send(r,szText,sz,0); + + #endif +} diff --git a/src/server/worldserver/RemoteAccess/RASocket.h b/src/server/worldserver/RemoteAccess/RASocket.h new file mode 100644 index 00000000000..5c13724f90d --- /dev/null +++ b/src/server/worldserver/RemoteAccess/RASocket.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/// \addtogroup Trinityd +/// @{ +/// \file + +#ifndef _RASOCKET_H +#define _RASOCKET_H + +#include "sockets/TcpSocket.h" + +#include "Common.h" + +#define RA_BUFF_SIZE 1024 + +class ISocketHandler; + +/// Remote Administration socket +class RASocket: public TcpSocket +{ + public: + + RASocket(ISocketHandler& h); + ~RASocket(); + + void OnAccept(); + void OnRead(); + + private: + + char * buff; + std::string szLogin; + uint32 iSess; + unsigned int iInputLength; + bool bLog; + bool bSecure; //kick on wrong pass, non exist. user, user with no priv + //will protect from DOS, bruteforce attacks + //some 'smart' protection must be added for more security + uint8 iMinLevel; + enum + { + NONE, //initial value + LG, //only login was entered + OK, //both login and pass were given, and they are correct and user have enough priv. + }stage; + + static void zprint( const char * szText ); +}; +#endif +/// @} diff --git a/src/server/worldserver/WorldThread/WorldRunnable.cpp b/src/server/worldserver/WorldThread/WorldRunnable.cpp new file mode 100644 index 00000000000..c674ddbc06f --- /dev/null +++ b/src/server/worldserver/WorldThread/WorldRunnable.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** \file + \ingroup Trinityd +*/ + +#include "Common.h" +#include "ObjectAccessor.h" +#include "World.h" +#include "WorldSocketMgr.h" +#include "Database/DatabaseEnv.h" + +#include "BattleGroundMgr.h" +#include "MapManager.h" +#include "Timer.h" +#include "WorldRunnable.h" + +#define WORLD_SLEEP_CONST 50 + +#ifdef WIN32 +#include "ServiceWin32.h" +extern int m_ServiceStatus; +#endif + +/// Heartbeat for the World +void WorldRunnable::run() +{ + ///- Init new SQL thread for the world database + WorldDatabase.ThreadStart(); // let thread do safe mySQL requests (one connection call enough) + + sWorld.InitResultQueue(); + + uint32 realCurrTime = 0; + uint32 realPrevTime = getMSTime(); + + uint32 prevSleepTime = 0; // used for balanced full tick time length near WORLD_SLEEP_CONST + + ///- While we have not World::m_stopEvent, update the world + while (!World::IsStopped()) + { + ++World::m_worldLoopCounter; + realCurrTime = getMSTime(); + + uint32 diff = getMSTimeDiff(realPrevTime,realCurrTime); + + sWorld.Update( diff ); + realPrevTime = realCurrTime; + + // diff (D0) include time of previous sleep (d0) + tick time (t0) + // we want that next d1 + t1 == WORLD_SLEEP_CONST + // we can't know next t1 and then can use (t0 + d1) == WORLD_SLEEP_CONST requirement + // d1 = WORLD_SLEEP_CONST - t0 = WORLD_SLEEP_CONST - (D0 - d0) = WORLD_SLEEP_CONST + d0 - D0 + if (diff <= WORLD_SLEEP_CONST+prevSleepTime) + { + prevSleepTime = WORLD_SLEEP_CONST+prevSleepTime-diff; + ACE_Based::Thread::Sleep(prevSleepTime); + } + else + prevSleepTime = 0; + + #ifdef WIN32 + if (m_ServiceStatus == 0) World::StopNow(SHUTDOWN_EXIT_CODE); + while (m_ServiceStatus == 2) Sleep(1000); + #endif + } + + sWorld.KickAll(); // save and kick all players + sWorld.UpdateSessions( 1 ); // real players unload required UpdateSessions call + + // unload battleground templates before different singletons destroyed + sBattleGroundMgr.DeleteAllBattleGrounds(); + + sWorldSocketMgr->StopNetwork(); + + MapManager::Instance().UnloadAll(); // unload all grids (including locked in memory) + + ///- End the database thread + WorldDatabase.ThreadEnd(); // free mySQL thread resources +} diff --git a/src/server/worldserver/WorldThread/WorldRunnable.h b/src/server/worldserver/WorldThread/WorldRunnable.h new file mode 100644 index 00000000000..f14ee021f36 --- /dev/null +++ b/src/server/worldserver/WorldThread/WorldRunnable.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/// \addtogroup Trinityd +/// @{ +/// \file + +#ifndef __WORLDRUNNABLE_H +#define __WORLDRUNNABLE_H + +/// Heartbeat thread for the World +class WorldRunnable : public ACE_Based::Runnable +{ + public: + void run(); +}; +#endif +/// @} |