From 11641a8bd73e33f1d83be6993bcb8be393febc7b Mon Sep 17 00:00:00 2001 From: Antz Date: Mon, 5 Jan 2015 13:34:31 +0000 Subject: [PATCH] [Realmd] Added submodule back in --- src/realmd/AuthCodes.h | 108 +++ src/realmd/AuthSocket.cpp | 1137 ++++++++++++++++++++++++++++++++ src/realmd/AuthSocket.h | 165 +++++ src/realmd/BufferedSocket.cpp | 274 ++++++++ src/realmd/BufferedSocket.h | 165 +++++ src/realmd/CMakeLists.txt | 105 +++ src/realmd/Main.cpp | 434 ++++++++++++ src/realmd/PatchHandler.cpp | 221 +++++++ src/realmd/PatchHandler.h | 164 +++++ src/realmd/README.md | 4 + src/realmd/RealmList.cpp | 201 ++++++ src/realmd/RealmList.h | 186 ++++++ src/realmd/realmd.conf.dist.in | 132 ++++ src/realmd/realmd.ico | Bin 0 -> 32038 bytes src/realmd/realmd.rc | 25 + 15 files changed, 3321 insertions(+) create mode 100644 src/realmd/AuthCodes.h create mode 100644 src/realmd/AuthSocket.cpp create mode 100644 src/realmd/AuthSocket.h create mode 100644 src/realmd/BufferedSocket.cpp create mode 100644 src/realmd/BufferedSocket.h create mode 100644 src/realmd/CMakeLists.txt create mode 100644 src/realmd/Main.cpp create mode 100644 src/realmd/PatchHandler.cpp create mode 100644 src/realmd/PatchHandler.h create mode 100644 src/realmd/README.md create mode 100644 src/realmd/RealmList.cpp create mode 100644 src/realmd/RealmList.h create mode 100644 src/realmd/realmd.conf.dist.in create mode 100644 src/realmd/realmd.ico create mode 100644 src/realmd/realmd.rc diff --git a/src/realmd/AuthCodes.h b/src/realmd/AuthCodes.h new file mode 100644 index 000000000..7cb8b9f26 --- /dev/null +++ b/src/realmd/AuthCodes.h @@ -0,0 +1,108 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2015 MaNGOS project + * + * 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 + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +/** \file + \ingroup realmd +*/ + +#ifndef MANGOS_H_AUTHCODES +#define MANGOS_H_AUTHCODES + +/** + * @brief + * + */ +enum eAuthCmd +{ + CMD_AUTH_LOGON_CHALLENGE = 0x00, + CMD_AUTH_LOGON_PROOF = 0x01, + CMD_AUTH_RECONNECT_CHALLENGE = 0x02, + CMD_AUTH_RECONNECT_PROOF = 0x03, + CMD_REALM_LIST = 0x10, + CMD_XFER_INITIATE = 0x30, + CMD_XFER_DATA = 0x31, + // these opcodes no longer exist in currently supported client + CMD_XFER_ACCEPT = 0x32, + CMD_XFER_RESUME = 0x33, + CMD_XFER_CANCEL = 0x34 +}; + +/** + * @brief not used by us currently + * + */ +enum eAuthSrvCmd +{ + CMD_GRUNT_AUTH_CHALLENGE = 0x0, + CMD_GRUNT_AUTH_VERIFY = 0x2, + CMD_GRUNT_CONN_PING = 0x10, + CMD_GRUNT_CONN_PONG = 0x11, + CMD_GRUNT_HELLO = 0x20, + CMD_GRUNT_PROVESESSION = 0x21, + CMD_GRUNT_KICK = 0x24, + CMD_GRUNT_PCWARNING = 0x29, + CMD_GRUNT_STRINGS = 0x41, + CMD_GRUNT_SUNKENUPDATE = 0x44, + CMD_GRUNT_SUNKEN_ONLINE = 0x46 +}; + +/** + * @brief + * + */ +enum AuthResult +{ + WOW_SUCCESS = 0x00, + WOW_FAIL_UNKNOWN0 = 0x01, ///< ? Unable to connect + WOW_FAIL_UNKNOWN1 = 0x02, ///< ? Unable to connect + WOW_FAIL_BANNED = 0x03, ///< This account has been closed and is no longer available for use. Please go to /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 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 for more information + // client reject next login attempts after this error, so in code used WOW_FAIL_UNKNOWN_ACCOUNT for both cases + WOW_FAIL_ALREADY_ONLINE = 0x06, ///< This account is already logged into . 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 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 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 account has been temporarily suspended. Please go to /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 + 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 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. + // WOW_FAIL_OVERMIND_CONVERTED + // WOW_FAIL_ANTI_INDULGENCE + // WOW_FAIL_EXPIRED + // WOW_FAIL_NO_GAME_ACCOUNT + // WOW_FAIL_BILLING_LOCK + // WOW_FAIL_IGR_WITHOUT_BNET + // WOW_FAIL_AA_LOCK + // WOW_FAIL_UNLOCKABLE_LOCK + // WOW_FAIL_MUST_USE_BNET + // WOW_FAIL_OTHER +}; + +#endif diff --git a/src/realmd/AuthSocket.cpp b/src/realmd/AuthSocket.cpp new file mode 100644 index 000000000..b960bdffe --- /dev/null +++ b/src/realmd/AuthSocket.cpp @@ -0,0 +1,1137 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2015 MaNGOS project + * + * 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 + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +/** \file + \ingroup realmd +*/ + +#include "Common.h" +#include "Database/DatabaseEnv.h" +#include "Config/Config.h" +#include "Log.h" +#include "RealmList.h" +#include "AuthSocket.h" +#include "AuthCodes.h" +#include "PatchHandler.h" + +#include +//#include "Util.h" -- for commented utf8ToUpperOnlyLatin + +#include +#include +#include + +extern DatabaseType LoginDatabase; + +enum eStatus +{ + STATUS_CONNECTED = 0, + STATUS_AUTHED +}; + +enum AccountFlags +{ + ACCOUNT_FLAG_GM = 0x00000001, + ACCOUNT_FLAG_TRIAL = 0x00000008, + ACCOUNT_FLAG_PROPASS = 0x00800000, +}; + +// 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 sAuthLogonChallenge_C sAuthReconnectChallenge_C; +/* +typedef struct +{ + uint8 cmd; + uint8 error; + uint8 unk2; + uint8 B[32]; + uint8 g_len; + uint8 g[1]; + uint8 N_len; + uint8 N[32]; + uint8 s[32]; + uint8 unk3[16]; +} sAuthLogonChallenge_S; +*/ + +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 +{ + uint16 unk1; + uint32 unk2; + uint8 unk3[4]; + uint16 unk4[20]; +} sAuthLogonProofKey_C; +*/ +typedef struct AUTH_LOGON_PROOF_S +{ + uint8 cmd; + uint8 error; + uint8 M2[20]; + uint32 accountFlags; // see enum AccountFlags + uint32 surveyId; // SurveyId + uint16 unkFlags; // some flags (AccountMsgAvailable = 0x01) +} sAuthLogonProof_S; + +typedef struct AUTH_LOGON_PROOF_S_BUILD_6005 +{ + uint8 cmd; + uint8 error; + uint8 M2[20]; + // uint32 unk1; + uint32 unk2; + // uint16 unk3; +} sAuthLogonProof_S_BUILD_6005; + +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 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 + +const AuthHandler table[] = +{ + { CMD_AUTH_LOGON_CHALLENGE, STATUS_CONNECTED, &AuthSocket::_HandleLogonChallenge }, + { CMD_AUTH_LOGON_PROOF, STATUS_CONNECTED, &AuthSocket::_HandleLogonProof }, + { CMD_AUTH_RECONNECT_CHALLENGE, STATUS_CONNECTED, &AuthSocket::_HandleReconnectChallenge}, + { CMD_AUTH_RECONNECT_PROOF, STATUS_CONNECTED, &AuthSocket::_HandleReconnectProof }, + { CMD_REALM_LIST, STATUS_AUTHED, &AuthSocket::_HandleRealmList }, + { CMD_XFER_ACCEPT, STATUS_CONNECTED, &AuthSocket::_HandleXferAccept }, + { CMD_XFER_RESUME, STATUS_CONNECTED, &AuthSocket::_HandleXferResume }, + { CMD_XFER_CANCEL, STATUS_CONNECTED, &AuthSocket::_HandleXferCancel } +}; + +#define AUTH_TOTAL_COMMANDS sizeof(table)/sizeof(AuthHandler) + +/// Constructor - set the N and g values for SRP6 +AuthSocket::AuthSocket() +{ + N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7"); + g.SetDword(7); + _authed = false; + + _accountSecurityLevel = SEC_PLAYER; + + _build = 0; + patch_ = ACE_INVALID_HANDLE; +} + +/// Close patch file descriptor before leaving +AuthSocket::~AuthSocket() +{ + if (patch_ != ACE_INVALID_HANDLE) + { ACE_OS::close(patch_); } +} + +/// Accept the connection and set the s random value for SRP6 +void AuthSocket::OnAccept() +{ + BASIC_LOG("Accepting connection from '%s'", get_remote_address().c_str()); +} + +/// Read the packet from the client +void AuthSocket::OnRead() +{ + uint8 _cmd; + while (1) + { + if (!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)recv_len()); + + if (!(*this.*table[i].handler)()) + { + DEBUG_LOG("Command handler failed for cmd %u recv length %u", + (uint32)_cmd, (uint32)recv_len()); + + return; + } + break; + } + } + + ///- Report unknown commands in the debug log + if (i == AUTH_TOTAL_COMMANDS) + { + DEBUG_LOG("[Auth] got unknown packet %u", (uint32)_cmd); + 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); +} + +void AuthSocket::SendProof(Sha1Hash sha) +{ + switch (_build) + { + case 5875: // 1.12.1 + case 6005: // 1.12.2 + case 6141: // 1.12.3 + { + sAuthLogonProof_S_BUILD_6005 proof; + memcpy(proof.M2, sha.GetDigest(), 20); + proof.cmd = CMD_AUTH_LOGON_PROOF; + proof.error = 0; + proof.unk2 = 0x00; + + send((char*)&proof, sizeof(proof)); + break; + } + case 8606: // 2.4.3 + case 10505: // 3.2.2a + case 11159: // 3.3.0a + case 11403: // 3.3.2 + case 11723: // 3.3.3a + case 12340: // 3.3.5a + case 13623: // 4.0.6a + case 15050: // 4.3.0 + case 15595: // 4.3.4 + case 16357: // 5.1.0 + case 16992: // 5.3.0 + case 17055: // 5.3.0 + case 17116: // 5.3.0 + case 17128: // 5.3.0 + case 17538: // 5.4.1 + case 17658: // 5.4.2 + case 17688: // 5.4.2a + case 17898: // 5.4.7 + case 17930: // 5.4.7 + case 17956: // 5.4.7 + case 18019: // 5.4.7 + case 18291: // 5.4.8 + case 18414: // 5.4.8 + default: // or later + { + sAuthLogonProof_S proof; + memcpy(proof.M2, sha.GetDigest(), 20); + proof.cmd = CMD_AUTH_LOGON_PROOF; + proof.error = 0; + proof.accountFlags = ACCOUNT_FLAG_PROPASS; + proof.surveyId = 0x00000000; + proof.unkFlags = 0x0000; + + send((char*)&proof, sizeof(proof)); + break; + } + } +} + +/// Logon Challenge command handler +bool AuthSocket::_HandleLogonChallenge() +{ + DEBUG_LOG("Entering _HandleLogonChallenge"); + if (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 buf; + buf.resize(4); + + recv((char*)&buf[0], 4); + +#ifdef MANGOS_BIGENDIAN + EndianConvert(*((uint16*)(buf[0]))); +#endif + + uint16 remaining = ((sAuthLogonChallenge_C*)&buf[0])->size; + DEBUG_LOG("[AuthChallenge] got header, body is %#04x bytes", remaining); + + if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (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 + 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; + + ///- 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); + + pkt << (uint8) CMD_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) + std::string address = get_remote_address(); + LoginDatabase.escape_string(address); + QueryResult* result = LoginDatabase.PQuery("SELECT unbandate FROM ip_banned WHERE " + // permanent still banned + "(unbandate = bandate OR unbandate > UNIX_TIMESTAMP()) AND ip = '%s'", address.c_str()); + if (result) + { + pkt << (uint8)WOW_FAIL_BANNED; + BASIC_LOG("[AuthChallenge] Banned ip %s tries to login!", get_remote_address().c_str()); + delete result; + } + else + { + ///- Get the account details from the account table + // No SQL injection (escaped user name) + + result = LoginDatabase.PQuery("SELECT sha_pass_hash,id,locked,last_ip,gmlevel,v,s FROM account WHERE 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'", get_remote_address().c_str()); + if (strcmp((*result)[3].GetString(), 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) + { + ///- If the account is banned, reject the logon attempt + QueryResult* banresult = LoginDatabase.PQuery("SELECT bandate,unbandate FROM account_banned WHERE " + "id = %u AND active = 1 AND (unbandate > UNIX_TIMESTAMP() OR unbandate = bandate)", (*result)[1].GetUInt32()); + if (banresult) + { + if ((*banresult)[0].GetUInt64() == (*banresult)[1].GetUInt64()) + { + pkt << (uint8) WOW_FAIL_BANNED; + BASIC_LOG("[AuthChallenge] Banned account %s tries to login!", _login.c_str()); + } + else + { + pkt << (uint8) WOW_FAIL_SUSPENDED; + BASIC_LOG("[AuthChallenge] Temporarily banned account %s tries to login!", _login.c_str()); + } + + delete banresult; + } + 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(); + + DEBUG_LOG("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; + + MANGOS_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]; } + + BASIC_LOG("[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)); + } + } + delete result; + } + else // no account + { + pkt << (uint8) WOW_FAIL_UNKNOWN_ACCOUNT; + } + } + 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 (!recv((char*)&lp, sizeof(sAuthLogonProof_C))) + { return false; } + + ///- Check if the client has one of the expected version numbers + bool valid_version = FindBuildInfo(_build) != NULL; + + ///
  • If the client has no valid version + if (!valid_version) + { + if (this->patch_ != ACE_INVALID_HANDLE) + { return false; } + + ///- Check if we have the apropriate patch on the disk + // file looks like: 65535enGB.mpq + char tmp[64]; + + snprintf(tmp, 24, "./patches/%d%s.mpq", _build, _localizationName.c_str()); + + char filename[PATH_MAX]; + if (ACE_OS::realpath(tmp, filename) != NULL) + { + patch_ = ACE_OS::open(filename, GENERIC_READ | FILE_FLAG_SEQUENTIAL_SCAN); + } + + if (patch_ == ACE_INVALID_HANDLE) + { + // no patch found + ByteBuffer pkt; + pkt << (uint8) CMD_AUTH_LOGON_CHALLENGE; + pkt << (uint8) 0x00; + pkt << (uint8) WOW_FAIL_VERSION_INVALID; + DEBUG_LOG("[AuthChallenge] %u is not a valid client version!", _build); + DEBUG_LOG("[AuthChallenge] Patch %s not found", tmp); + send((char const*)pkt.contents(), pkt.size()); + return true; + } + + XFER_INIT xferh; + + ACE_OFF_T file_size = ACE_OS::filesize(this->patch_); + + if (file_size == -1) + { + close_connection(); + return false; + } + + if (!PatchCache::instance()->GetHash(tmp, (uint8*)&xferh.md5)) + { + // calculate patch md5, happens if patch was added while realmd was running + PatchCache::instance()->LoadPatchMD5(tmp); + PatchCache::instance()->GetHash(tmp, (uint8*)&xferh.md5); + } + + uint8 data[2] = { CMD_AUTH_LOGON_PROOF, WOW_FAIL_VERSION_UPDATE}; + send((const char*)data, sizeof(data)); + + memcpy(&xferh, "0\x05Patch", 7); + xferh.cmd = CMD_XFER_INITIATE; + xferh.file_size = file_size; + + send((const char*)&xferh, sizeof(xferh)); + return true; + } + ///
+ + ///- 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()) + { return false; } + + 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)) + { + BASIC_LOG("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, 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(); + + SendProof(sha); + + ///- Set _authed to true! + _authed = true; + } + else + { + if (_build > 6005) // > 1.12.2 + { + char data[4] = { CMD_AUTH_LOGON_PROOF, WOW_FAIL_UNKNOWN_ACCOUNT, 3, 0}; + send(data, sizeof(data)); + } + else + { + // 1.x not react incorrectly at 4-byte message use 3 as real error + char data[2] = { CMD_AUTH_LOGON_PROOF, WOW_FAIL_UNKNOWN_ACCOUNT}; + send(data, sizeof(data)); + } + BASIC_LOG("[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* 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','MaNGOS realmd','Failed login autoban',1)", + acc_id, WrongPassBanTime); + BASIC_LOG("[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 = get_remote_address(); + LoginDatabase.escape_string(current_ip); + LoginDatabase.PExecute("INSERT INTO ip_banned VALUES ('%s',UNIX_TIMESTAMP(),UNIX_TIMESTAMP()+'%u','MaNGOS realmd','Failed login autoban')", + current_ip.c_str(), WrongPassBanTime); + BASIC_LOG("[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); + } + } + delete loginfail; + } + } + } + return true; +} + +/// Reconnect Challenge command handler +bool AuthSocket::_HandleReconnectChallenge() +{ + DEBUG_LOG("Entering _HandleReconnectChallenge"); + if (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 buf; + buf.resize(4); + + 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()) || (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 + 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; + LoginDatabase.escape_string(_safelogin); + + EndianConvert(ch->build); + _build = ch->build; + + QueryResult* 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 can not find his session key in the database.", _login.c_str()); + close_connection(); + return false; + } + + Field* fields = result->Fetch(); + K.SetHexStr(fields[0].GetString()); + delete result; + + ///- Sending response + ByteBuffer pkt; + pkt << (uint8) CMD_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 + 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 (!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) CMD_AUTH_RECONNECT_PROOF; + pkt << (uint8) 0x00; + //If we keep from sending this we don't receive Session Expired on the client when + //changing realms after being logged on to the world + if (_build > 6141) // Last vanilla, 1.12.3 + pkt << (uint16) 0x00; // 2 bytes zeros + 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()); + close_connection(); + return false; + } +} + +/// %Realm List command handler +bool AuthSocket::_HandleRealmList() +{ + DEBUG_LOG("Entering _HandleRealmList"); + if (recv_len() < 5) + { return false; } + recv_skip(5); + + ///- Get the user id (else close the connection) + // No SQL injection (escaped user name) + + QueryResult* result = LoginDatabase.PQuery("SELECT id,sha_pass_hash FROM account WHERE username = '%s'", _safelogin.c_str()); + if (!result) + { + sLog.outError("[ERROR] user %s tried to login and we can not find him in the database.", _login.c_str()); + close_connection(); + return false; + } + + uint32 id = (*result)[0].GetUInt32(); + std::string rI = (*result)[1].GetCppString(); + delete result; + + ///- 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; + LoadRealmlist(pkt, id); + + ByteBuffer hdr; + hdr << (uint8) CMD_REALM_LIST; + hdr << (uint16)pkt.size(); + hdr.append(pkt); + + send((char const*)hdr.contents(), hdr.size()); + return true; +} + +void AuthSocket::LoadRealmlist(ByteBuffer& pkt, uint32 acctid) +{ + switch (_build) + { + case 5875: // 1.12.1 + case 6005: // 1.12.2 + case 6141: // 1.12.3 + { + pkt << uint32(0); // unused value + pkt << uint8(sRealmList.size()); + + for (RealmList::RealmMap::const_iterator i = sRealmList.begin(); i != sRealmList.end(); ++i) + { + uint8 AmountOfCharacters; + + // No SQL injection. id of realm is controlled by the database. + QueryResult* result = LoginDatabase.PQuery("SELECT numchars FROM realmcharacters WHERE realmid = '%d' AND acctid='%u'", i->second.m_ID, acctid); + if (result) + { + Field* fields = result->Fetch(); + AmountOfCharacters = fields[0].GetUInt8(); + delete result; + } + else + AmountOfCharacters = 0; + + bool ok_build = std::find(i->second.realmbuilds.begin(), i->second.realmbuilds.end(), _build) != i->second.realmbuilds.end(); + + RealmBuildInfo const* buildInfo = ok_build ? FindBuildInfo(_build) : NULL; + if (!buildInfo) + { buildInfo = &i->second.realmBuildInfo; } + + RealmFlags realmflags = i->second.realmflags; + + // 1.x clients not support explicitly REALM_FLAG_SPECIFYBUILD, so manually form similar name as show in more recent clients + std::string name = i->first; + if (realmflags & REALM_FLAG_SPECIFYBUILD) + { + char buf[20]; + snprintf(buf, 20, " (%u,%u,%u)", buildInfo->major_version, buildInfo->minor_version, buildInfo->bugfix_version); + name += buf; + } + + // Show offline state for unsupported client builds and locked realms (1.x clients not support locked state show) + if (!ok_build || (i->second.allowedSecurityLevel > _accountSecurityLevel)) + { realmflags = RealmFlags(realmflags | REALM_FLAG_OFFLINE); } + + pkt << uint32(i->second.icon); // realm type + pkt << uint8(realmflags); // realmflags + pkt << name; // name + pkt << i->second.address; // address + pkt << float(i->second.populationLevel); + pkt << uint8(AmountOfCharacters); + pkt << uint8(i->second.timezone); // realm category + pkt << uint8(0x00); // unk, may be realm number/id? + } + + pkt << uint16(0x0002); // unused value (why 2?) + break; + } + + case 8606: // 2.4.3 + case 10505: // 3.2.2a + case 11159: // 3.3.0a + case 11403: // 3.3.2 + case 11723: // 3.3.3a + case 12340: // 3.3.5a + case 13623: // 4.0.6a + case 15050: // 4.3.0 + case 15595: // 4.3.4 + case 16357: // 5.1.0 + case 16992: // 5.3.0 + case 17055: // 5.3.0 + case 17116: // 5.3.0 + case 17128: // 5.3.0 + case 17538: // 5.4.1 + case 17658: // 5.4.2 + case 17688: // 5.4.2a + case 17898: // 5.4.7 + case 17930: // 5.4.7 + case 17956: // 5.4.7 + case 18019: // 5.4.7 + case 18291: // 5.4.8 + case 18414: // 5.4.8 + default: // and later + { + pkt << uint32(0); // unused value + pkt << uint16(sRealmList.size()); + + for (RealmList::RealmMap::const_iterator i = sRealmList.begin(); i != sRealmList.end(); ++i) + { + uint8 AmountOfCharacters; + + // No SQL injection. id of realm is controlled by the database. + QueryResult* result = LoginDatabase.PQuery("SELECT numchars FROM realmcharacters WHERE realmid = '%d' AND acctid='%u'", i->second.m_ID, acctid); + if (result) + { + Field* fields = result->Fetch(); + AmountOfCharacters = fields[0].GetUInt8(); + delete result; + } + else + { AmountOfCharacters = 0; } + + bool ok_build = std::find(i->second.realmbuilds.begin(), i->second.realmbuilds.end(), _build) != i->second.realmbuilds.end(); + + RealmBuildInfo const* buildInfo = ok_build ? FindBuildInfo(_build) : NULL; + if (!buildInfo) + { buildInfo = &i->second.realmBuildInfo; } + + uint8 lock = (i->second.allowedSecurityLevel > _accountSecurityLevel) ? 1 : 0; + + RealmFlags realmFlags = i->second.realmflags; + + // Show offline state for unsupported client builds + if (!ok_build) + { realmFlags = RealmFlags(realmFlags | REALM_FLAG_OFFLINE); } + + if (!buildInfo) + { realmFlags = RealmFlags(realmFlags & ~REALM_FLAG_SPECIFYBUILD); } + + pkt << uint8(i->second.icon); // realm type (this is second column in Cfg_Configs.dbc) + pkt << uint8(lock); // flags, if 0x01, then realm locked + pkt << uint8(realmFlags); // see enum RealmFlags + pkt << i->first; // name + pkt << i->second.address; // address + pkt << float(i->second.populationLevel); + pkt << uint8(AmountOfCharacters); + pkt << uint8(i->second.timezone); // realm category (Cfg_Categories.dbc) + pkt << uint8(0x2C); // unk, may be realm number/id? + + if (realmFlags & REALM_FLAG_SPECIFYBUILD) + { + pkt << uint8(buildInfo->major_version); + pkt << uint8(buildInfo->minor_version); + pkt << uint8(buildInfo->bugfix_version); + pkt << uint16(_build); + } + } + + pkt << uint16(0x0010); // unused value (why 10?) + break; + } + } +} + +/// Resume patch transfer +bool AuthSocket::_HandleXferResume() +{ + DEBUG_LOG("Entering _HandleXferResume"); + + if (recv_len() < 9) + { return false; } + + recv_skip(1); + + uint64 start_pos; + recv((char*)&start_pos, 8); + + if (patch_ == ACE_INVALID_HANDLE) + { + close_connection(); + return false; + } + + ACE_OFF_T file_size = ACE_OS::filesize(patch_); + + if (file_size == -1 || start_pos >= (uint64)file_size) + { + close_connection(); + return false; + } + + if (ACE_OS::lseek(patch_, start_pos, SEEK_SET) == -1) + { + close_connection(); + return false; + } + + InitPatch(); + + return true; +} + +/// Cancel patch transfer +bool AuthSocket::_HandleXferCancel() +{ + DEBUG_LOG("Entering _HandleXferCancel"); + + recv_skip(1); + close_connection(); + + return true; +} + +/// Accept patch transfer +bool AuthSocket::_HandleXferAccept() +{ + DEBUG_LOG("Entering _HandleXferAccept"); + + recv_skip(1); + + InitPatch(); + + return true; +} + +void AuthSocket::InitPatch() +{ + PatchHandler* handler = new PatchHandler(ACE_OS::dup(get_handle()), patch_); + + patch_ = ACE_INVALID_HANDLE; + + if (handler->open() == -1) + { + handler->close(); + close_connection(); + } +} + diff --git a/src/realmd/AuthSocket.h b/src/realmd/AuthSocket.h new file mode 100644 index 000000000..162f16cc8 --- /dev/null +++ b/src/realmd/AuthSocket.h @@ -0,0 +1,165 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2015 MaNGOS project + * + * 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 + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +/// \addtogroup realmd +/// @{ +/// \file + +#ifndef MANGOS_H_AUTHSOCKET +#define MANGOS_H_AUTHSOCKET + +#include "Common.h" +#include "Auth/BigNumber.h" +#include "Auth/Sha1.h" +#include "ByteBuffer.h" + +#include "BufferedSocket.h" + +/** + * @brief Handle login commands + * + */ +class AuthSocket: public BufferedSocket +{ + public: + const static int s_BYTE_SIZE = 32; /**< TODO */ + + /** + * @brief + * + */ + AuthSocket(); + /** + * @brief + * + */ + ~AuthSocket(); + + /** + * @brief + * + */ + void OnAccept() override; + /** + * @brief + * + */ + void OnRead() override; + /** + * @brief + * + * @param sha + */ + void SendProof(Sha1Hash sha); + /** + * @brief + * + * @param pkt + * @param acctid + */ + void LoadRealmlist(ByteBuffer& pkt, uint32 acctid); + + /** + * @brief + * + * @return bool + */ + bool _HandleLogonChallenge(); + /** + * @brief + * + * @return bool + */ + bool _HandleLogonProof(); + /** + * @brief + * + * @return bool + */ + bool _HandleReconnectChallenge(); + /** + * @brief + * + * @return bool + */ + bool _HandleReconnectProof(); + /** + * @brief + * + * @return bool + */ + bool _HandleRealmList(); + + /** + * @brief data transfer handle for patch + * + * @return bool + */ + bool _HandleXferResume(); + /** + * @brief + * + * @return bool + */ + bool _HandleXferCancel(); + /** + * @brief + * + * @return bool + */ + bool _HandleXferAccept(); + + /** + * @brief + * + * @param rI + */ + void _SetVSFields(const std::string& rI); + + private: + + BigNumber N, s, g, v; /**< TODO */ + BigNumber b, B; /**< TODO */ + BigNumber K; /**< TODO */ + BigNumber _reconnectProof; /**< TODO */ + + bool _authed; /**< TODO */ + + std::string _login; /**< TODO */ + std::string _safelogin; /**< TODO */ + + std::string _localizationName; /**< 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 */ + uint16 _build; /**< TODO */ + AccountTypes _accountSecurityLevel; /**< TODO */ + + ACE_HANDLE patch_; /**< TODO */ + + /** + * @brief + * + */ + void InitPatch(); +}; +#endif +/// @} diff --git a/src/realmd/BufferedSocket.cpp b/src/realmd/BufferedSocket.cpp new file mode 100644 index 000000000..973ac309b --- /dev/null +++ b/src/realmd/BufferedSocket.cpp @@ -0,0 +1,274 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2015 MaNGOS project + * + * 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 + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +/** \file + \ingroup realmd + */ + +#include "Common.h" +#include "BufferedSocket.h" + +#include +#include +#include + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +BufferedSocket::BufferedSocket(void): + input_buffer_(4096), + remote_address_("") +{ +} + +/*virtual*/ BufferedSocket::~BufferedSocket(void) +{ +} + +/*virtual*/ int BufferedSocket::open(void* arg) +{ + if (Base::open(arg) == -1) + { return -1; } + + ACE_INET_Addr addr; + + if (peer().get_remote_addr(addr) == -1) + { return -1; } + + char address[1024]; + + addr.get_host_addr(address, 1024); + + this->remote_address_ = address; + + this->OnAccept(); + + return 0; +} + +const std::string& BufferedSocket::get_remote_address(void) const +{ + return this->remote_address_; +} + +size_t BufferedSocket::recv_len(void) const +{ + return this->input_buffer_.length(); +} + +bool BufferedSocket::recv_soft(char* buf, size_t len) +{ + if (this->input_buffer_.length() < len) + { return false; } + + ACE_OS::memcpy(buf, this->input_buffer_.rd_ptr(), len); + + return true; +} + +bool BufferedSocket::recv(char* buf, size_t len) +{ + bool ret = this->recv_soft(buf, len); + + if (ret) + { this->recv_skip(len); } + + return ret; +} + +void BufferedSocket::recv_skip(size_t len) +{ + this->input_buffer_.rd_ptr(len); +} + +ssize_t BufferedSocket::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 = this->peer().send(message_block.rd_ptr(), len, MSG_NOSIGNAL); + + if (n < 0) + { + if (errno == EWOULDBLOCK) + // Blocking signal + { return 0; } + else + // Error + { return -1; } + } + else if (n == 0) + { + // Can this happen ? + return -1; + } + + // return bytes transmitted + return n; +} + +bool BufferedSocket::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 (this->msg_queue()->is_empty()) + { + // Try to send it directly. + ssize_t n = this->noblk_send(message_block); + + if (n < 0) + { return false; } + else if (n == len) + { return true; } + + // adjust how much bytes we sent + message_block.rd_ptr((size_t)n); + + // fall down + } + + // enqueue the message, note: clone is needed cause we cant enqueue stuff on the stack + ACE_Message_Block* mb = message_block.clone(); + + if (this->msg_queue()->enqueue_tail(mb, (ACE_Time_Value*) &ACE_Time_Value::zero) == -1) + { + mb->release(); + return false; + } + + // tell reactor to call handle_output() when we can send more data + if (this->reactor()->schedule_wakeup(this, ACE_Event_Handler::WRITE_MASK) == -1) + { return false; } + + return true; +} + +/*virtual*/ int BufferedSocket::handle_output(ACE_HANDLE /*= ACE_INVALID_HANDLE*/) +{ + ACE_Message_Block* mb = 0; + + if (this->msg_queue()->is_empty()) + { + // if no more data to send, then cancel notification + this->reactor()->cancel_wakeup(this, ACE_Event_Handler::WRITE_MASK); + return 0; + } + + if (this->msg_queue()->dequeue_head(mb, (ACE_Time_Value*) &ACE_Time_Value::zero) == -1) + { return -1; } + + ssize_t n = this->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 (this->msg_queue()->enqueue_head(mb, (ACE_Time_Value*) &ACE_Time_Value::zero) == -1) + { + mb->release(); + return -1; + } + + return 0; + } + + ACE_NOTREACHED(return -1); +} + +/*virtual*/ int BufferedSocket::handle_input(ACE_HANDLE /*= ACE_INVALID_HANDLE*/) +{ + const ssize_t space = this->input_buffer_.space(); + + ssize_t n = this->peer().recv(this->input_buffer_.wr_ptr(), space); + + if (n < 0) + { + // blocking signal or error + return errno == EWOULDBLOCK ? 0 : -1; + } + else if (n == 0) + { + // EOF + return -1; + } + + this->input_buffer_.wr_ptr((size_t)n); + + this->OnRead(); + + // move data in the buffer to the beginning of the buffer + this->input_buffer_.crunch(); + + // return 1 in case there might be more data to read from OS + return n == space ? 1 : 0; +} + +/*virtual*/ int BufferedSocket::handle_close(ACE_HANDLE /*h*/, ACE_Reactor_Mask /*m*/) +{ + this->OnClose(); + + Base::handle_close(); + + return 0; +} + +void BufferedSocket::close_connection(void) +{ + this->peer().close_reader(); + this->peer().close_writer(); + + reactor()->remove_handler(this, ACE_Event_Handler::DONT_CALL | ACE_Event_Handler::ALL_EVENTS_MASK); +} + diff --git a/src/realmd/BufferedSocket.h b/src/realmd/BufferedSocket.h new file mode 100644 index 000000000..4cd4c6027 --- /dev/null +++ b/src/realmd/BufferedSocket.h @@ -0,0 +1,165 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2015 MaNGOS project + * + * 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 + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +/** \file + \ingroup realmd + */ + +#ifndef MANGOS_H_BUFFEREDSOCKET +#define MANGOS_H_BUFFEREDSOCKET + +#include +#include +#include +#include +#include +#include + +#include + +/** + * @brief + * + */ +class BufferedSocket: public ACE_Svc_Handler +{ + protected: + /** + * @brief + * + */ + typedef ACE_Svc_Handler Base; + + /** + * @brief + * + */ + virtual void OnRead(void) { } + /** + * @brief + * + */ + virtual void OnAccept(void) { } + /** + * @brief + * + */ + virtual void OnClose(void) { } + + public: + /** + * @brief + * + */ + BufferedSocket(void); + /** + * @brief + * + */ + virtual ~BufferedSocket(void); + + /** + * @brief + * + * @return size_t + */ + size_t recv_len(void) const; + /** + * @brief + * + * @param buf + * @param len + * @return bool + */ + bool recv_soft(char* buf, size_t len); + /** + * @brief + * + * @param buf + * @param len + * @return bool + */ + bool recv(char* buf, size_t len); + /** + * @brief + * + * @param len + */ + void recv_skip(size_t len); + + /** + * @brief + * + * @param buf + * @param len + * @return bool + */ + bool send(const char* buf, size_t len); + + /** + * @brief + * + * @return const std::string + */ + const std::string& get_remote_address(void) const; + + virtual int open(void*) override; + + /** + * @brief + * + */ + void close_connection(void); + + virtual int handle_input(ACE_HANDLE = ACE_INVALID_HANDLE) override; + virtual int handle_output(ACE_HANDLE = ACE_INVALID_HANDLE) override; + + /** + * @brief + * + * @param ACE_HANDLE + * @param ACE_Reactor_Mask + * @return int + */ + virtual int handle_close(ACE_HANDLE = ACE_INVALID_HANDLE, + ACE_Reactor_Mask = ACE_Event_Handler::ALL_EVENTS_MASK); + + private: + /** + * @brief + * + * @param message_block + * @return ssize_t + */ + ssize_t noblk_send(ACE_Message_Block& message_block); + + private: + ACE_Message_Block input_buffer_; /**< TODO */ + + protected: + std::string remote_address_; /**< TODO */ +}; + +#endif /* _BUFFEREDSOCKET_H_ */ + diff --git a/src/realmd/CMakeLists.txt b/src/realmd/CMakeLists.txt new file mode 100644 index 000000000..7772d8c29 --- /dev/null +++ b/src/realmd/CMakeLists.txt @@ -0,0 +1,105 @@ +# This code is part of MaNGOS. Contributor & Copyright details are in AUTHORS/THANKS. +# +# 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 + +set(EXECUTABLE_NAME realmd) + +set(EXECUTABLE_SRCS + AuthCodes.h + AuthSocket.cpp + AuthSocket.h + BufferedSocket.cpp + BufferedSocket.h + Main.cpp + PatchHandler.cpp + PatchHandler.h + RealmList.cpp + RealmList.h + ) + +if(WIN32) + # add resource file to windows build + set(EXECUTABLE_SRCS ${EXECUTABLE_SRCS} realmd.rc) +endif() + +include_directories( + "${CMAKE_SOURCE_DIR}/src/shared" + "${CMAKE_SOURCE_DIR}/src/framework" + "${CMAKE_BINARY_DIR}" + "${CMAKE_BINARY_DIR}/src/shared" + "${MYSQL_INCLUDE_DIR}" + "${ACE_INCLUDE_DIR}" +) + +add_executable(${EXECUTABLE_NAME} + ${EXECUTABLE_SRCS} +) + +if(NOT ACE_USE_EXTERNAL) + add_dependencies(${EXECUTABLE_NAME} ace) + target_link_libraries(${EXECUTABLE_NAME} ace) +else() + target_link_libraries(${EXECUTABLE_NAME} ACE) +endif() + +target_link_libraries(${EXECUTABLE_NAME} + shared + framework +) + +if(WIN32) + target_link_libraries(${EXECUTABLE_NAME} + optimized ${MYSQL_LIBRARY} + optimized ${OPENSSL_LIBRARIES} + debug ${MYSQL_DEBUG_LIBRARY} + debug ${OPENSSL_DEBUG_LIBRARIES} + ) + if(PLATFORM MATCHES X86) + target_link_libraries(${EXECUTABLE_NAME}) + endif() +endif() + +if(UNIX) + target_link_libraries(${EXECUTABLE_NAME} + ${MYSQL_LIBRARY} + ${OPENSSL_LIBRARIES} + ${OPENSSL_EXTRA_LIBRARIES} + ) +endif() + +set(EXECUTABLE_LINK_FLAGS "") + +if(UNIX) + set(EXECUTABLE_LINK_FLAGS "-Wl,--no-as-needed -ldl -pthread -lrt ${EXECUTABLE_LINK_FLAGS}") +endif() + +if(APPLE) + set(EXECUTABLE_LINK_FLAGS "-framework Carbon ${EXECUTABLE_LINK_FLAGS}") +endif() + +set_target_properties(${EXECUTABLE_NAME} PROPERTIES LINK_FLAGS + "${EXECUTABLE_LINK_FLAGS}" +) + +install(TARGETS ${EXECUTABLE_NAME} DESTINATION "${BIN_DIR}") +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/realmd.conf.dist.in" "${CMAKE_CURRENT_BINARY_DIR}/realmd.conf.dist") +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/realmd.conf.dist" DESTINATION "${CONF_INSTALL_DIR}") + +if(WIN32 AND MSVC) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/\${BUILD_TYPE}/${EXECUTABLE_NAME}.pdb" DESTINATION "${BIN_DIR}" CONFIGURATIONS Debug) + add_custom_command(TARGET ${EXECUTABLE_NAME} + POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_BINARY_DIR}/realmd.conf.dist" "${CONF_COPY_DIR}" + ) +endif() diff --git a/src/realmd/Main.cpp b/src/realmd/Main.cpp new file mode 100644 index 000000000..a967b188c --- /dev/null +++ b/src/realmd/Main.cpp @@ -0,0 +1,434 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2015 MaNGOS project + * + * 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,r + * 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 + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +/// \addtogroup realmd Realm Daemon +/// @{ +/// \file + +#include "Common.h" +#include "Database/DatabaseEnv.h" +#include "RealmList.h" + +#include "Config/Config.h" +#include "Log.h" +#include "AuthSocket.h" +#include "SystemConfig.h" +#include "revision_nr.h" +#include "revision_sql.h" +#include "Util.h" +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include "ServiceWin32.h" +char serviceName[] = "realmd"; +char serviceLongName[] = "MaNGOS realmd service"; +char serviceDescription[] = "Massive Network Game Object Server"; +/* + * -1 - not in service mode + * 0 - stopped + * 1 - running + * 2 - paused + */ +int m_ServiceStatus = -1; +#else +#include "PosixDaemon.h" +#endif + +bool StartDB(); +void UnhookSignals(); +void HookSignals(); + +bool stopEvent = false; ///< Setting it to true stops the server + +DatabaseType LoginDatabase; ///< Accessor to the realm server database + +/// Print out the usage string for this program on the console. +void usage(const char* prog) +{ + sLog.outString("Usage: \n %s []\n" + " -v, --version print version and exist\n\r" + " -c config_file use config_file as configuration file\n\r" +#ifdef WIN32 + " Running as service functions:\n\r" + " -s run run as service\n\r" + " -s install install service\n\r" + " -s uninstall uninstall service\n\r" +#else + " Running as daemon functions:\n\r" + " -s run run as daemon\n\r" + " -s stop stop daemon\n\r" +#endif + , prog); +} + +/// Launch the realm server +extern int main(int argc, char** argv) +{ + ///- Command line parsing + char const* cfg_file = REALMD_CONFIG_LOCATION; + + char const* options = ":c:s:"; + + ACE_Get_Opt cmd_opts(argc, argv, options); + cmd_opts.long_option("version", 'v'); + + char serviceDaemonMode = '\0'; + + int option; + while ((option = cmd_opts()) != EOF) + { + switch (option) + { + case 'c': + cfg_file = cmd_opts.opt_arg(); + break; + case 'v': + printf("%s\n", REVISION_NR); + return 0; + + case 's': + { + const char* mode = cmd_opts.opt_arg(); + + if (!strcmp(mode, "run")) + { serviceDaemonMode = 'r'; } +#ifdef WIN32 + else if (!strcmp(mode, "install")) + { serviceDaemonMode = 'i'; } + else if (!strcmp(mode, "uninstall")) + { serviceDaemonMode = 'u'; } +#else + else if (!strcmp(mode, "stop")) + { serviceDaemonMode = 's'; } +#endif + else + { + sLog.outError("Runtime-Error: -%c unsupported argument %s", cmd_opts.opt_opt(), mode); + usage(argv[0]); + Log::WaitBeforeContinueIfNeed(); + return 1; + } + break; + } + case ':': + sLog.outError("Runtime-Error: -%c option requires an input argument", cmd_opts.opt_opt()); + usage(argv[0]); + Log::WaitBeforeContinueIfNeed(); + return 1; + default: + sLog.outError("Runtime-Error: bad format of commandline arguments"); + usage(argv[0]); + Log::WaitBeforeContinueIfNeed(); + return 1; + } + } + +#ifdef WIN32 // windows service command need execute before config read + switch (serviceDaemonMode) + { + case 'i': + if (WinServiceInstall()) + { sLog.outString("Installing service"); } + return 1; + case 'u': + if (WinServiceUninstall()) + { sLog.outString("Uninstalling service"); } + return 1; + case 'r': + WinServiceRun(); + break; + } +#endif + + if (!sConfig.SetSource(cfg_file)) + { + sLog.outError("Could not find configuration file %s.", cfg_file); + Log::WaitBeforeContinueIfNeed(); + return 1; + } + +#ifndef WIN32 // posix daemon commands need apply after config read + switch (serviceDaemonMode) + { + case 'r': + startDaemon(); + break; + case 's': + stopDaemon(); + break; + } +#endif + + sLog.Initialize(); + + sLog.outString("%s [realm-daemon]", REVISION_NR); + sLog.outString(" to stop.\n"); + sLog.outString("Using configuration file %s.", cfg_file); + + ///- Check the version of the configuration file + uint32 confVersion = sConfig.GetIntDefault("ConfVersion", 0); + if (confVersion < REALMD_CONFIG_VERSION) + { + sLog.outError("*****************************************************************************"); + sLog.outError(" WARNING: Your realmd.conf version indicates your conf file is out of date!"); + sLog.outError(" Please check for updates, as your current default values may cause"); + sLog.outError(" strange behavior."); + sLog.outError("*****************************************************************************"); + Log::WaitBeforeContinueIfNeed(); + } + + DETAIL_LOG("%s (Library: %s)", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION)); + if (SSLeay() < 0x009080bfL) + { + DETAIL_LOG("WARNING: Outdated version of OpenSSL lib. Logins to server may not work!"); + DETAIL_LOG("WARNING: Minimal required version [OpenSSL 0.9.8k]"); + } + + DETAIL_LOG("Using ACE: %s", ACE_VERSION); + +#if defined (ACE_HAS_EVENT_POLL) || defined (ACE_HAS_DEV_POLL) + ACE_Reactor::instance(new ACE_Reactor(new ACE_Dev_Poll_Reactor(ACE::max_handles(), 1), 1), true); +#else + ACE_Reactor::instance(new ACE_Reactor(new ACE_TP_Reactor(), true), true); +#endif + + sLog.outBasic("Max allowed open files is %d", ACE::max_handles()); + + /// realmd PID file creation + std::string pidfile = sConfig.GetStringDefault("PidFile", ""); + if (!pidfile.empty()) + { + uint32 pid = CreatePIDFile(pidfile); + if (!pid) + { + sLog.outError("Can not create PID file %s.\n", pidfile.c_str()); + Log::WaitBeforeContinueIfNeed(); + return 1; + } + + sLog.outString("Daemon PID: %u\n", pid); + } + + ///- Initialize the database connection + if (!StartDB()) + { + Log::WaitBeforeContinueIfNeed(); + return 1; + } + + ///- Get the list of realms for the server + sRealmList.Initialize(sConfig.GetIntDefault("RealmsStateUpdateDelay", 20)); + if (sRealmList.size() == 0) + { + sLog.outError("No valid realms specified."); + Log::WaitBeforeContinueIfNeed(); + return 1; + } + + // cleanup query + // set expired bans to inactive + LoginDatabase.BeginTransaction(); + LoginDatabase.Execute("UPDATE account_banned SET active = 0 WHERE unbandate<=UNIX_TIMESTAMP() AND unbandate<>bandate"); + LoginDatabase.Execute("DELETE FROM ip_banned WHERE unbandate<=UNIX_TIMESTAMP() AND unbandate<>bandate"); + LoginDatabase.CommitTransaction(); + + ///- Launch the listening network socket + ACE_Acceptor acceptor; + + uint16 rmport = sConfig.GetIntDefault("RealmServerPort", DEFAULT_REALMSERVER_PORT); + std::string bind_ip = sConfig.GetStringDefault("BindIP", "0.0.0.0"); + + ACE_INET_Addr bind_addr(rmport, bind_ip.c_str()); + + if (acceptor.open(bind_addr, ACE_Reactor::instance(), ACE_NONBLOCK) == -1) + { + sLog.outError("MaNGOS realmd can not bind to %s:%d", bind_ip.c_str(), rmport); + Log::WaitBeforeContinueIfNeed(); + return 1; + } + + ///- Catch termination signals + HookSignals(); + + ///- Handle affinity for multiple processors and process priority on Windows +#ifdef WIN32 + { + HANDLE hProcess = GetCurrentProcess(); + + uint32 Aff = sConfig.GetIntDefault("UseProcessors", 0); + if (Aff > 0) + { + ULONG_PTR appAff; + ULONG_PTR sysAff; + + if (GetProcessAffinityMask(hProcess, &appAff, &sysAff)) + { + ULONG_PTR curAff = Aff & appAff; // remove non accessible processors + + if (!curAff) + { + sLog.outError("Processors marked in UseProcessors bitmask (hex) %x not accessible for realmd. Accessible processors bitmask (hex): %x", Aff, appAff); + } + else + { + if (SetProcessAffinityMask(hProcess, curAff)) + { sLog.outString("Using processors (bitmask, hex): %x", curAff); } + else + { sLog.outError("Can't set used processors (hex): %x", curAff); } + } + } + sLog.outString(); + } + + bool Prio = sConfig.GetBoolDefault("ProcessPriority", false); + + if (Prio) + { + if (SetPriorityClass(hProcess, HIGH_PRIORITY_CLASS)) + { sLog.outString("realmd process priority class set to HIGH"); } + else + { sLog.outError("Can't set realmd process priority class."); } + sLog.outString(); + } + } +#endif + + // server has started up successfully => enable async DB requests + LoginDatabase.AllowAsyncTransactions(); + + // maximum counter for next ping + uint32 numLoops = (sConfig.GetIntDefault("MaxPingTime", 30) * (MINUTE * 1000000 / 100000)); + uint32 loopCounter = 0; + +#ifndef WIN32 + detachDaemon(); +#endif + ///- Wait for termination signal + while (!stopEvent) + { + // dont move this outside the loop, the reactor will modify it + ACE_Time_Value interval(0, 100000); + + if (ACE_Reactor::instance()->run_reactor_event_loop(interval) == -1) + { break; } + + if ((++loopCounter) == numLoops) + { + loopCounter = 0; + DETAIL_LOG("Ping MySQL to keep connection alive"); + LoginDatabase.Ping(); + } +#ifdef WIN32 + if (m_ServiceStatus == 0) { stopEvent = true; } + while (m_ServiceStatus == 2) { Sleep(1000); } +#endif + } + + ///- Wait for the delay thread to exit + LoginDatabase.HaltDelayThread(); + + ///- Remove signal handling before leaving + UnhookSignals(); + + sLog.outString("Halting process..."); + return 0; +} + +/// Handle termination signals +/** Put the global variable stopEvent to 'true' if a termination signal is caught **/ +void OnSignal(int s) +{ + switch (s) + { + case SIGINT: + case SIGTERM: + stopEvent = true; + break; +#ifdef _WIN32 + case SIGBREAK: + stopEvent = true; + break; +#endif + } + + signal(s, OnSignal); +} + +/// Initialize connection to the database +bool StartDB() +{ + std::string dbstring = sConfig.GetStringDefault("LoginDatabaseInfo", ""); + if (dbstring.empty()) + { + sLog.outError("Database not specified"); + return false; + } + + sLog.outString("Login Database total connections: %i", 1 + 1); + + if (!LoginDatabase.Initialize(dbstring.c_str())) + { + sLog.outError("Can not connect to database"); + return false; + } + + if (!LoginDatabase.CheckRequiredField("realmd_db_version", REVISION_DB_REALMD)) + { + ///- Wait for already started DB delay threads to end + LoginDatabase.HaltDelayThread(); + return false; + } + + return true; +} + +/// Define hook 'OnSignal' for all termination signals +void HookSignals() +{ + signal(SIGINT, OnSignal); + signal(SIGTERM, OnSignal); +#ifdef _WIN32 + signal(SIGBREAK, OnSignal); +#endif +} + +/// Unhook the signals before leaving +void UnhookSignals() +{ + signal(SIGINT, 0); + signal(SIGTERM, 0); +#ifdef _WIN32 + signal(SIGBREAK, 0); +#endif +} + +/// @} diff --git a/src/realmd/PatchHandler.cpp b/src/realmd/PatchHandler.cpp new file mode 100644 index 000000000..3533bfc67 --- /dev/null +++ b/src/realmd/PatchHandler.cpp @@ -0,0 +1,221 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2015 MaNGOS project + * + * 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 + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +/** \file + \ingroup realmd + */ + +#include "Common.h" +#include "PatchHandler.h" +#include "AuthCodes.h" +#include "Log.h" + +#include +#include +#include +#include + +#include + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +#if defined( __GNUC__ ) +#pragma pack(1) +#else +#pragma pack(push,1) +#endif + +struct Chunk +{ + ACE_UINT8 cmd; + ACE_UINT16 data_size; + ACE_UINT8 data[4096]; // 4096 - page size on most arch +}; + +#if defined( __GNUC__ ) +#pragma pack() +#else +#pragma pack(pop) +#endif + +PatchHandler::PatchHandler(ACE_HANDLE socket, ACE_HANDLE patch) +{ + reactor(NULL); + set_handle(socket); + patch_fd_ = patch; +} + +PatchHandler::~PatchHandler() +{ + if (patch_fd_ != ACE_INVALID_HANDLE) + { ACE_OS::close(patch_fd_); } +} + +int PatchHandler::open(void*) +{ + if (get_handle() == ACE_INVALID_HANDLE || patch_fd_ == ACE_INVALID_HANDLE) + { return -1; } + + int nodelay = 0; + if (-1 == peer().set_option(ACE_IPPROTO_TCP, + TCP_NODELAY, + &nodelay, + sizeof(nodelay))) + { + return -1; + } + +#if defined(TCP_CORK) + int cork = 1; + if (-1 == peer().set_option(ACE_IPPROTO_TCP, + TCP_CORK, + &cork, + sizeof(cork))) + { + return -1; + } +#endif // TCP_CORK + + (void) peer().disable(ACE_NONBLOCK); + + return activate(THR_NEW_LWP | THR_DETACHED | THR_INHERIT_SCHED); +} + +int PatchHandler::svc(void) +{ + // Do 1 second sleep, similar to the one in game/WorldSocket.cpp + // Seems client have problems with too fast sends. + ACE_OS::sleep(1); + + int flags = MSG_NOSIGNAL; + + Chunk data; + data.cmd = CMD_XFER_DATA; + + ssize_t r; + + while ((r = ACE_OS::read(patch_fd_, data.data, sizeof(data.data))) > 0) + { + data.data_size = (ACE_UINT16)r; + + if (peer().send((const char*)&data, + ((size_t) r) + sizeof(data) - sizeof(data.data), + flags) == -1) + { + return -1; + } + } + + if (r == -1) + { + return -1; + } + + return 0; +} + +PatchCache::~PatchCache() +{ + for (Patches::iterator i = patches_.begin(); i != patches_.end(); ++i) + { delete i->second; } +} + +PatchCache::PatchCache() +{ + LoadPatchesInfo(); +} + +PatchCache* PatchCache::instance() +{ + return ACE_Singleton::instance(); +} + +void PatchCache::LoadPatchMD5(const 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", path.c_str()); + + if (!pPatch) + { return; } + + // Calculate the MD5 hash + MD5_CTX ctx; + MD5_Init(&ctx); + + const size_t check_chunk_size = 4 * 1024; + + ACE_UINT8 buf[check_chunk_size]; + + while (!feof(pPatch)) + { + size_t read = fread(buf, 1, check_chunk_size, pPatch); + MD5_Update(&ctx, buf, read); + } + + fclose(pPatch); + + // Store the result in the internal patch hash map + patches_[path] = new PATCH_INFO; + MD5_Final((ACE_UINT8*) & patches_[path]->md5, &ctx); +} + +bool PatchCache::GetHash(const char* pat, ACE_UINT8 mymd5[MD5_DIGEST_LENGTH]) +{ + for (Patches::iterator i = patches_.begin(); i != patches_.end(); ++i) + if (!stricmp(pat, i->first.c_str())) + { + memcpy(mymd5, i->second->md5, MD5_DIGEST_LENGTH); + return true; + } + + return false; +} + +void PatchCache::LoadPatchesInfo() +{ + ACE_DIR* dirp = ACE_OS::opendir(ACE_TEXT("./patches/")); + + if (!dirp) + { return; } + + ACE_DIRENT* dp; + + while ((dp = ACE_OS::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); } + } + + ACE_OS::closedir(dirp); +} + diff --git a/src/realmd/PatchHandler.h b/src/realmd/PatchHandler.h new file mode 100644 index 000000000..6415af25f --- /dev/null +++ b/src/realmd/PatchHandler.h @@ -0,0 +1,164 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2015 MaNGOS project + * + * 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 + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +/** \file + \ingroup realmd + */ + +#ifndef MANGOS_H_PATCHHANDLER +#define MANGOS_H_PATCHHANDLER + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/** + * @brief Caches MD5 hash of client patches present on the server + * + */ +class PatchCache +{ + public: + /** + * @brief + * + */ + ~PatchCache(); + /** + * @brief + * + */ + PatchCache(); + + /** + * @brief + * + * @return PatchCache + */ + static PatchCache* instance(); + + /** + * @brief + * + */ + struct PATCH_INFO + { + ACE_UINT8 md5[MD5_DIGEST_LENGTH]; /**< TODO */ + }; + + /** + * @brief + * + */ + typedef std::map Patches; + + /** + * @brief + * + * @return Patches::const_iterator + */ + Patches::const_iterator begin() const + { + return patches_.begin(); + } + + /** + * @brief + * + * @return Patches::const_iterator + */ + Patches::const_iterator end() const + { + return patches_.end(); + } + + /** + * @brief + * + * @param + */ + void LoadPatchMD5(const char*); + /** + * @brief + * + * @param pat + * @param mymd5[] + * @return bool + */ + bool GetHash(const char* pat, ACE_UINT8 mymd5[MD5_DIGEST_LENGTH]); + + private: + /** + * @brief + * + */ + void LoadPatchesInfo(); + Patches patches_; /**< TODO */ +}; + +/** + * @brief + * + */ +class PatchHandler: public ACE_Svc_Handler +{ + protected: + /** + * @brief + * + */ + typedef ACE_Svc_Handler Base; + + public: + /** + * @brief + * + * @param socket + * @param patch + */ + PatchHandler(ACE_HANDLE socket, ACE_HANDLE patch); + /** + * @brief + * + */ + virtual ~PatchHandler(); + + int open(void* = 0) override; + + protected: + virtual int svc(void) override; + + private: + ACE_HANDLE patch_fd_; /**< TODO */ +}; + +#endif /* _BK_PATCHHANDLER_H__ */ + diff --git a/src/realmd/README.md b/src/realmd/README.md new file mode 100644 index 000000000..1c7813acb --- /dev/null +++ b/src/realmd/README.md @@ -0,0 +1,4 @@ +Mangos Realmd +====== + +Central repository for Realmd for all cores. diff --git a/src/realmd/RealmList.cpp b/src/realmd/RealmList.cpp new file mode 100644 index 000000000..e09194314 --- /dev/null +++ b/src/realmd/RealmList.cpp @@ -0,0 +1,201 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2015 MaNGOS project + * + * 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 + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +/** \file + \ingroup realmd +*/ + +#include "Common.h" +#include "RealmList.h" +#include "AuthCodes.h" +#include "Util.h" // for Tokens typedef +#include "Policies/Singleton.h" +#include "Database/DatabaseEnv.h" + +INSTANTIATE_SINGLETON_1(RealmList); + +extern DatabaseType LoginDatabase; + +// will only support WoW 1.12.1/1.12.2/1.12.3, WoW:TBC 2.4.3, WoW:WotLK 3.3.5a and official release for WoW:Cataclysm and later, client builds 15595, 10505, 8606, 6005, 5875 +// if you need more from old build then add it in cases in realmd sources code +// list sorted from high to low build and first build used as low bound for accepted by default range (any > it will accepted by realmd at least) + +static RealmBuildInfo ExpectedRealmdClientBuilds[] = +{ + {18414, 5, 4, 8, ' '}, // highest supported build, also auto accept all above for simplify future supported builds testing + {18291, 5, 4, 8, ' '}, // highest supported build, also auto accept all above for simplify future supported builds testing + {18019, 5, 4, 7, ' '}, // highest supported build, also auto accept all above for simplify future supported builds testing + {17956, 5, 4, 7, ' '}, // highest supported build, also auto accept all above for simplify future supported builds testing + {17930, 5, 4, 7, ' '}, // highest supported build, also auto accept all above for simplify future supported builds testing + {17898, 5, 4, 7, ' '}, // highest supported build, also auto accept all above for simplify future supported builds testing + {17688, 5, 4, 2, 'a'}, // highest supported build, also auto accept all above for simplify future supported builds testing + {17658, 5, 4, 2, ' '}, // highest supported build, also auto accept all above for simplify future supported builds testing + {17538, 5, 4, 1, ' '}, // highest supported build, also auto accept all above for simplify future supported builds testing + {17128, 5, 3, 0, ' '}, // highest supported build, also auto accept all above for simplify future supported builds testing + {17116, 5, 3, 0, ' '}, // highest supported build, also auto accept all above for simplify future supported builds testing + {17055, 5, 3, 0, ' '}, // highest supported build, also auto accept all above for simplify future supported builds testing + {16992, 5, 3, 0, ' '}, // highest supported build, also auto accept all above for simplify future supported builds testing + {16357, 5, 1, 0, ' '}, // highest supported build, also auto accept all above for simplify future supported builds testing + {15595, 4, 3, 4, ' '}, + {15050, 4, 3, 0, ' '}, + {13623, 4, 0, 6, 'a'}, + {12340, 3, 3, 5, 'a'}, + {11723, 3, 3, 3, 'a'}, + {11403, 3, 3, 2, ' '}, + {11159, 3, 3, 0, 'a'}, + {10505, 3, 2, 2, 'a'}, + {8606, 2, 4, 3, ' '}, + {6141, 1, 12, 3, ' '}, + {6005, 1, 12, 2, ' '}, + {5875, 1, 12, 1, ' '}, + {0, 0, 0, 0, ' '} // terminator +}; + +RealmBuildInfo const* FindBuildInfo(uint16 _build) +{ + // first build is low bound of always accepted range + if (_build >= ExpectedRealmdClientBuilds[0].build) + { return &ExpectedRealmdClientBuilds[0]; } + + // continue from 1 with explicit equal check + for (int i = 1; ExpectedRealmdClientBuilds[i].build; ++i) + if (_build == ExpectedRealmdClientBuilds[i].build) + { return &ExpectedRealmdClientBuilds[i]; } + + // none appropriate build + return NULL; +} + +RealmList::RealmList() : m_UpdateInterval(0), m_NextUpdateTime(time(NULL)) +{ +} + +RealmList& sRealmList +{ + static RealmList realmlist; + return realmlist; +} + +/// 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, RealmFlags realmflags, uint8 timezone, AccountTypes allowedSecurityLevel, float popu, const std::string& builds) +{ + ///- Create new if not exist or update existed + Realm& realm = m_realms[name]; + + realm.m_ID = ID; + realm.icon = icon; + realm.realmflags = realmflags; + realm.timezone = timezone; + realm.allowedSecurityLevel = allowedSecurityLevel; + realm.populationLevel = popu; + + Tokens tokens = StrSplit(builds, " "); + Tokens::iterator iter; + + for (iter = tokens.begin(); iter != tokens.end(); ++iter) + { + uint32 build = atol((*iter).c_str()); + realm.realmbuilds.insert(build); + } + + uint16 first_build = !realm.realmbuilds.empty() ? *realm.realmbuilds.begin() : 0; + + realm.realmBuildInfo.build = first_build; + realm.realmBuildInfo.major_version = 0; + realm.realmBuildInfo.minor_version = 0; + realm.realmBuildInfo.bugfix_version = 0; + realm.realmBuildInfo.hotfix_version = ' '; + + if (first_build) + if (RealmBuildInfo const* bInfo = FindBuildInfo(first_build)) + if (bInfo->build == first_build) + { realm.realmBuildInfo = *bInfo; } + + ///- Append port to IP address. + std::ostringstream ss; + ss << address << ":" << port; + realm.address = ss.str(); +} + +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) +{ + DETAIL_LOG("Updating Realm List..."); + + //// 0 1 2 3 4 5 6 7 8 9 + QueryResult* result = LoginDatabase.Query("SELECT id, name, address, port, icon, realmflags, timezone, allowedSecurityLevel, population, realmbuilds FROM realmlist WHERE (realmflags & 1) = 0 ORDER BY name"); + + ///- Circle through results and add them to the realm map + if (result) + { + do + { + Field* fields = result->Fetch(); + + uint32 Id = fields[0].GetUInt32(); + std::string name = fields[1].GetCppString(); + uint8 realmflags = fields[5].GetUInt8(); + uint8 allowedSecurityLevel = fields[7].GetUInt8(); + + if (realmflags & ~(REALM_FLAG_OFFLINE | REALM_FLAG_NEW_PLAYERS | REALM_FLAG_RECOMMENDED | REALM_FLAG_SPECIFYBUILD)) + { + sLog.outError("Realm (id %u, name '%s') can only be flagged as OFFLINE (mask 0x02), NEWPLAYERS (mask 0x20), RECOMMENDED (mask 0x40), or SPECIFICBUILD (mask 0x04) in DB", Id, name.c_str()); + realmflags &= (REALM_FLAG_OFFLINE | REALM_FLAG_NEW_PLAYERS | REALM_FLAG_RECOMMENDED | REALM_FLAG_SPECIFYBUILD); + } + + UpdateRealm( + Id, name, fields[2].GetCppString(), fields[3].GetUInt32(), + fields[4].GetUInt8(), RealmFlags(realmflags), fields[6].GetUInt8(), + (allowedSecurityLevel <= SEC_ADMINISTRATOR ? AccountTypes(allowedSecurityLevel) : SEC_ADMINISTRATOR), + fields[8].GetFloat(), fields[9].GetCppString()); + + if (init) + { sLog.outString("Added realm id %u, name '%s'", Id, name.c_str()); } + } + while (result->NextRow()); + delete result; + } +} diff --git a/src/realmd/RealmList.h b/src/realmd/RealmList.h new file mode 100644 index 000000000..caddfb9ca --- /dev/null +++ b/src/realmd/RealmList.h @@ -0,0 +1,186 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2015 MaNGOS project + * + * 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 + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +/// \addtogroup realmd +/// @{ +/// \file + +#ifndef MANGOS_H_REALMLIST +#define MANGOS_H_REALMLIST + +#include "Common.h" + +/** + * @brief + * + */ +struct RealmBuildInfo +{ + int build; /**< TODO */ + int major_version; /**< TODO */ + int minor_version; /**< TODO */ + int bugfix_version; /**< TODO */ + int hotfix_version; /**< TODO */ +}; + +enum RealmVersion +{ + REALM_VERSION_VANILLA = 0, + REALM_VERSION_TBC = 1, + REALM_VERSION_WOTLK = 2, + REALM_VERSION_CATA = 3, + REALM_VERSION_MOP = 4, + REALM_VERSION_COUNT = 5 +}; + +/** + * This is used to make a link between build number and actual wow version that + * it belongs to. To get the connection between them, ie turn a build into a version + * one would use \ref RealmList::BelongsToVersion the other way around is not available + * as it does not make sense and isn't needed. + */ +RealmBuildInfo const* FindBuildInfo(uint16 _build); + +/** + * @brief + * + */ +typedef std::set RealmBuilds; + +/// Storage object for a realm +/** + * @brief + * + */ +struct Realm +{ + std::string address; + uint8 icon; + RealmFlags realmflags; // realmflags + uint8 timezone; + uint32 m_ID; + AccountTypes allowedSecurityLevel; // current allowed join security level (show as locked for not fit accounts) + float populationLevel; + RealmBuilds realmbuilds; // list of supported builds (updated in DB by mangosd) + RealmBuildInfo realmBuildInfo; // build info for show version in list +}; + +/** + * @brief Storage object for the list of realms on the server + * + */ +class RealmList +{ + public: + /** + * @brief + * + */ + typedef std::map RealmMap; + + /** + * @brief + * + * @return RealmList + */ + static RealmList& Instance(); + + /** + * @brief + * + */ + RealmList(); + /** + * @brief + * + */ + ~RealmList() {}; + + /** + * @brief + * + * @param updateInterval + */ + void Initialize(uint32 updateInterval); + /** + * Initializes a map holding a link from build number to a version. + * \see RealmVersion + */ + void UpdateIfNeed(); + + /** + * Get's the iterators for all realms supporting the given version as a pair, + * the first member is a iterator to the begin() and the second is an iterator + * to the end(). + * @param build the build number to fetch the iterators for + * @return iterators to the begin() and end() part of the realms supporting + * the given build, if there is no matching build iterators are given to end() + * and end() of a list. + */ + RealmMap::const_iterator begin() const { return m_realms.begin(); } + + /** + * Returns how many realms we have available for the current build + * @param build the build we want to know number of available realms for + * @return the number of available realms + */ + RealmMap::const_iterator end() const { return m_realms.end(); } + /** + * @brief + * + * @return uint32 + */ + uint32 size() const { return m_realms.size(); }; + private: + /** + * @brief + * + * @param init + */ + void UpdateRealms(bool init); + /** + * @brief + * + * @param ID + * @param name + * @param address + * @param port + * @param icon + * @param realmflags + * @param timezone + * @param allowedSecurityLevel + * @param popu + * @param builds + */ + void UpdateRealm(uint32 ID, const std::string& name, const std::string& address, uint32 port, uint8 icon, RealmFlags realmflags, uint8 timezone, AccountTypes allowedSecurityLevel, float popu, const std::string& builds); + private: + RealmMap m_realms; ///< Internal map of realms + uint32 m_UpdateInterval; /**< TODO */ + time_t m_NextUpdateTime; /**< TODO */ +}; + +#define sRealmList RealmList::Instance() + +#endif +/// @} diff --git a/src/realmd/realmd.conf.dist.in b/src/realmd/realmd.conf.dist.in new file mode 100644 index 000000000..1516ba599 --- /dev/null +++ b/src/realmd/realmd.conf.dist.in @@ -0,0 +1,132 @@ +################################################################################ +# Realm list demon configuration # +################################################################################ + +[RealmdConf] +ConfVersion=2010062001 + +################################################################################ +# REALMD SETTINGS +# +# LoginDatabaseInfo +# Connection settings for the script library database +# Default: hostname;port;username;password;database +# Use named pipes at Windows +# .;somenumber;username;password;database +# +# Use Unix sockets on Unix/Linux +# .;/path/to/unix_socket;username;password;database +# +# LogsDir +# Directory where log files should be written +# The given path has to exist, and be writable for the realm list demon +# Default: "" No log directory prefix, thus log files will be stored in +# the same path where the realm list demon is running +# +# PidFile +# Realm list demon PID file +# Default: "" Do not create a PID file. +# +# MaxPingTime +# Settings for maximum database-ping interval (minutes between pings) +# Default: 30 +# +# RealmServerPort +# Port on which the server will listen +# Default: 3724 +# +# BindIP +# Bind realm list demon to IP/hostname +# DO NOT CHANGE THIS UNLESS YOU _REALLY_ KNOW WHAT YOU'RE DOING +# Default: "0.0.0.0" Listen on all available addresses +# +# LogLevel +# Server console level of logging +# 0 = Minimum; 1 = Error; 2 = Detail; 3 = Full/Debug +# Default: 0 +# +# LogTime +# Include time in server console output [hh:mm:ss] +# Default: 0 (no time) +# 1 (print time) +# +# LogFile +# Logfile name +# Default: "Realmd.log" +# "" - empty name disable creating log file +# +# LogTimestamp +# Logfile with timestamp of server start in name +# Default: 0 - no timestamp appended +# 1 - append timestamp in form of _YYYY-MM-DD_HH-MM-SS +# +# LogFileLevel +# Server file level of logging +# 0 = Minimum; 1 = Error; 2 = Detail; 3 = Full/Debug +# Default: 0 +# +# LogColors +# Color for messages (format "normal_color details_color debug_color error_color) +# Colors: 0 - BLACK, 1 - RED, 2 - GREEN, 3 - BROWN, 4 - BLUE, 5 - MAGENTA, 6 - CYAN, 7 - GREY, +# 8 - YELLOW, 9 - LRED, 10 - LGREEN, 11 - LBLUE, 12 - LMAGENTA, 13 - LCYAN, 14 - WHITE +# Default: "" - none colors +# "13 7 11 9" - for example :) +# +# UseProcessors +# Used processors mask for multi-processors system (Used only at Windows) +# Default: 0 (selected by OS) +# number (bitmask value of selected processors) +# +# ProcessPriority +# Process proirity setting (Used only at Windows) +# Default: 1 (HIGH) +# 0 (Normal) +# +# WaitAtStartupError +# After startup error report wait or some time before continue (and possible close console window) +# -1 (wait until press) +# Default: 0 (not wait) +# N (>0, wait N secs) +# +# RealmsStateUpdateDelay +# Realm list Update up delay (updated at realm list request if delay expired). +# Default: 20 +# 0 (Disabled) +# +# WrongPass.MaxCount +# Number of login attemps with wrong password before the account or IP is banned +# Default: 3 (Never ban) +# +# WrongPass.BanTime +# Duration of the ban in seconds (0 means permanent ban) +# Default: 300 +# +# WrongPass.BanType +# Ban the IP or account on which login is attempted +# Default: 0 (Ban IP) +# 1 (Ban Account) +# +################################################################################ +LoginDatabaseInfo = "127.0.0.1;3306;mangos;mangos;realmd" +LogsDir = "" +PidFile = "" + +MaxPingTime = 30 +RealmServerPort = 3724 +BindIP = "0.0.0.0" + +LogLevel = 0 +LogTime = 0 +LogFile = "realm-list.log" +LogTimestamp = 0 +LogFileLevel = 0 +LogColors = "13 7 11 9" + +UseProcessors = 0 +ProcessPriority = 1 +WaitAtStartupError = 0 +RealmsStateUpdateDelay = 20 + +WrongPass.MaxCount = 3 +WrongPass.BanTime = 300 +WrongPass.BanType = 0 diff --git a/src/realmd/realmd.ico b/src/realmd/realmd.ico new file mode 100644 index 0000000000000000000000000000000000000000..bd17ee278d84709eb6b0dbb25793b8f5ea581dbb GIT binary patch literal 32038 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 t3(Nr*1fdBjZ|6eum{{YOZNjd-k literal 0 HcmV?d00001 diff --git a/src/realmd/realmd.rc b/src/realmd/realmd.rc new file mode 100644 index 000000000..f236d464b --- /dev/null +++ b/src/realmd/realmd.rc @@ -0,0 +1,25 @@ +/* + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2015 MaNGOS project + * + * 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 + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +IDI_APPICON ICON DISCARDABLE "realmd.ico"