diff options
Diffstat (limited to 'src/game/WorldSocket.cpp')
-rw-r--r-- | src/game/WorldSocket.cpp | 664 |
1 files changed, 664 insertions, 0 deletions
diff --git a/src/game/WorldSocket.cpp b/src/game/WorldSocket.cpp new file mode 100644 index 00000000000..d52c0962862 --- /dev/null +++ b/src/game/WorldSocket.cpp @@ -0,0 +1,664 @@ +/* + * Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.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 u2w +*/ + +#include "Common.h" +#include "Log.h" +#include "Opcodes.h" +#include "Database/DatabaseEnv.h" +#include "Auth/Sha1.h" +#include "WorldPacket.h" +#include "WorldSocket.h" +#include "WorldSession.h" +#include "World.h" +#include "WorldSocketMgr.h" +#include "Policies/SingletonImp.h" +#include "WorldLog.h" +#include "AddonHandler.h" +#include "sockets/Utility.h" +#include "Util.h" + +// 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 platform +#if defined( __GNUC__ ) +#pragma pack(1) +#else +#pragma pack(push,1) +#endif + +/// Client Packet Header +struct ClientPktHeader +{ + uint16 size; + uint32 cmd; +}; + +/// Server Packet Header +struct ServerPktHeader +{ + uint16 size; + uint16 cmd; +}; + +// GCC have alternative #pragma pack() syntax and old gcc version not support pack(pop), also any gcc version not support it at some platform +#if defined( __GNUC__ ) +#pragma pack() +#else +#pragma pack(pop) +#endif + +#define SOCKET_CHECK_PACKET_SIZE(P,S) if((P).size() < (S)) return SizeError((P),(S)); + +/// WorldSocket construction and initialization. +WorldSocket::WorldSocket(ISocketHandler &sh): TcpSocket(sh), _cmd(0), _remaining(0), _session(NULL) +{ + _seed = static_cast<uint32>(rand32()); + m_LastPingMSTime = 0; // first time it will counted as overspeed maybe, but this is not important + m_OverSpeedPings = 0; + + if (sWorld.getConfig(CONFIG_TCP_NO_DELAY)) + SetTcpNodelay(true); +} + +/// WorldSocket destructor +WorldSocket::~WorldSocket() +{ + if(_session) + _session->SetSocket(0); + + WorldPacket *packet; + + ///- Go through the to-be-sent queue and delete remaining packets + while(!_sendQueue.empty()) + { + packet = _sendQueue.next(); + delete packet; + } +} + +/// Copy the packet to the to-be-sent queue +void WorldSocket::SendPacket(WorldPacket const* packet) +{ + WorldPacket *pck = new WorldPacket(*packet); + ASSERT(pck); + _sendQueue.add(pck); +} + +/// On client connection +void WorldSocket::OnAccept() +{ + ///- Add the current socket to the list of sockets to be managed (WorldSocketMgr) + sWorldSocketMgr.AddSocket(this); + Utility::ResolveLocal(); + + ///- Send a AUTH_CHALLENGE packet + WorldPacket packet( SMSG_AUTH_CHALLENGE, 4 ); + packet << _seed; + + SendPacket(&packet); +} + +/// Read the client transmitted data +void WorldSocket::OnRead() +{ + TcpSocket::OnRead(); + + while(1) + { + ///- Read the packet header and decipher it (if needed) + if (!_remaining) + { + if (ibuf.GetLength() < 6) + break; + + ClientPktHeader hdr; + + ibuf.Read((char *)&hdr, 6); + _crypt.DecryptRecv((uint8 *)&hdr, 6); + + _remaining = ntohs(hdr.size) - 4; + _cmd = hdr.cmd; + } + + if (ibuf.GetLength() < _remaining) + break; + + ///- Read the remaining of the packet + WorldPacket packet((uint16)_cmd, _remaining); + + packet.resize(_remaining); + if(_remaining) ibuf.Read((char*)packet.contents(), _remaining); + _remaining = 0; + + ///- If log of world packets is enable, log the incoming packet + if( sWorldLog.LogWorld() ) + { + sWorldLog.Log("CLIENT:\nSOCKET: %u\nLENGTH: %u\nOPCODE: %s (0x%.4X)\nDATA:\n", + (uint32)GetSocket(), + packet.size(), + LookupOpcodeName(packet.GetOpcode()), + packet.GetOpcode()); + + uint32 p = 0; + while (p < packet.size()) + { + for (uint32 j = 0; j < 16 && p < packet.size(); j++) + sWorldLog.Log("%.2X ", packet[p++]); + sWorldLog.Log("\n"); + } + sWorldLog.Log("\n\n"); + } + + ///- If the packet is PING, KEEP_ALIVE or AUTH_SESSION, handle immediately + switch (_cmd) + { + case CMSG_KEEP_ALIVE: + break; // just ignore, network connectivity timeout preventing + case CMSG_PING: + { + _HandlePing(packet); + break; + } + case CMSG_AUTH_SESSION: + { + _HandleAuthSession(packet); + break; + } + default: + { + ///- Else, put it in the world session queue for this user (need to be already authenticated) + if (_session) + _session->QueuePacket(packet); + else + sLog.outDetail("Received out of place packet with cmdid 0x%.4X", _cmd); + break; + } + } + } +} + +/// On socket closing +void WorldSocket::CloseSocket() +{ + ///- Set CloseAndDelete flag for TcpSocket class + SetCloseAndDelete(true); + + ///- Set _session to NULL. Prevent crashes + _session = NULL; +} + +/// On socket deleting +void WorldSocket::OnDelete() +{ + ///- Stop sending remaining data through this socket + if (_session) + { + _session->SetSocket(NULL); + // Session deleted from World session list at socket==0, This is only back reference from socket to session. + _session = NULL; + } + + ///- Remove the socket from the WorldSocketMgr list + sWorldSocketMgr.RemoveSocket(this); + + ///- Removes socket from player queue + sWorld.RemoveQueuedPlayer(this); +} + +/// Handle the client authentication packet +void WorldSocket::_HandleAuthSession(WorldPacket& recvPacket) +{ + uint8 digest[20]; + uint32 clientSeed; + uint32 unk2; + uint32 BuiltNumberClient; + uint32 id, security; + bool tbc = false; + std::string account; + Sha1Hash sha1; + BigNumber v, s, g, N, x, I; + WorldPacket packet, SendAddonPacked; + + BigNumber K; + + SOCKET_CHECK_PACKET_SIZE(recvPacket,4+4+1+4+20); + + ///- Read the content of the packet + recvPacket >> BuiltNumberClient; // for now no use + recvPacket >> unk2; + recvPacket >> account; + + // recheck size + SOCKET_CHECK_PACKET_SIZE(recvPacket,4+4+(account.size()+1)+4+20); + + recvPacket >> clientSeed; + recvPacket.read(digest, 20); + + sLog.outDebug("Auth: client %u, unk2 %u, account %s, clientseed %u", BuiltNumberClient, unk2, account.c_str(), clientSeed); + + ///- Normalize account name + //utf8ToUpperOnlyLatin(account); -- client already send account in expected form + + ///- Get the account information from the realmd database + std::string safe_account = account; // Duplicate, else will screw the SHA hash verification below + loginDatabase.escape_string(safe_account); + //No SQL injection, username escaped. + // 0 1 2 3 4 5 6 7 8 9 10 + QueryResult *result = loginDatabase.PQuery("SELECT id, gmlevel, sessionkey, last_ip, locked, sha_pass_hash, v, s, tbc, mutetime, locale FROM account WHERE username = '%s'", safe_account.c_str()); + + ///- Stop if the account is not found + if ( !result ) + { + packet.Initialize( SMSG_AUTH_RESPONSE, 1 ); + packet << uint8( AUTH_UNKNOWN_ACCOUNT ); + SendPacket( &packet ); + sLog.outDetail( "SOCKET: Sent Auth Response (unknown account)." ); + return; + } + + Field* fields = result->Fetch(); + + tbc = fields[8].GetUInt8() && sWorld.getConfig(CONFIG_EXPANSION) > 0; + + N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7"); + g.SetDword(7); + I.SetHexStr(fields[5].GetString()); + + //In case of leading zeros in the I 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); + + s.SetHexStr(fields[7].GetString()); + sha1.UpdateData(s.AsByteArray(), s.GetNumBytes()); + sha1.UpdateData(mDigest, SHA_DIGEST_LENGTH); + sha1.Finalize(); + x.SetBinary(sha1.GetDigest(), sha1.GetLength()); + v = g.ModExp(x, N); + + const char* sStr = s.AsHexStr(); //Must be freed by OPENSSL_free() + const char* vStr = v.AsHexStr(); //Must be freed by OPENSSL_free() + const char* vold = fields[6].GetString(); + sLog.outDebug("SOCKET: (s,v) check s: %s v_old: %s v_new: %s", sStr, vold, vStr ); + loginDatabase.PExecute("UPDATE account SET v = '0', s = '0' WHERE username = '%s'", safe_account.c_str()); + if ( !vold || strcmp( vStr, vold ) ) + { + packet.Initialize( SMSG_AUTH_RESPONSE, 1 ); + packet << uint8( AUTH_UNKNOWN_ACCOUNT ); + SendPacket( &packet ); + sLog.outDetail( "SOCKET: User not logged."); + delete result; + OPENSSL_free((void*)sStr); + OPENSSL_free((void*)vStr); + return; + } + OPENSSL_free((void*)sStr); + OPENSSL_free((void*)vStr); + + ///- Re-check ip locking (same check as in realmd). + if(fields[4].GetUInt8() == 1) // if ip is locked + { + if ( strcmp(fields[3].GetString(),GetRemoteAddress().c_str()) ) + { + packet.Initialize( SMSG_AUTH_RESPONSE, 1 ); + packet << uint8( AUTH_FAILED ); + SendPacket( &packet ); + + sLog.outDetail( "SOCKET: Sent Auth Response (Account IP differs)." ); + delete result; + return; + } + } + + id = fields[0].GetUInt32(); + security = fields[1].GetUInt16(); + K.SetHexStr(fields[2].GetString()); + time_t mutetime = time_t(fields[9].GetUInt64()); + + LocaleConstant locale = LocaleConstant(fields[10].GetUInt8()); + if (locale>=MAX_LOCALE) + locale=LOCALE_enUS; + + delete result; + + ///- Re-check account ban (same check as in realmd) /// TO DO: why on earth do 2 checks for same thing? + QueryResult *banresult = loginDatabase.PQuery("SELECT bandate,unbandate FROM account_banned WHERE id = '%u' AND active = 1", id); + if(banresult) // if account banned + { + packet.Initialize( SMSG_AUTH_RESPONSE, 1 ); + packet << uint8( AUTH_BANNED ); + SendPacket( &packet ); + + sLog.outDetail( "SOCKET: Sent Auth Response (Account banned)." ); + delete banresult; + return; + } + + ///- Check locked state for server + AccountTypes allowedAccountType = sWorld.GetPlayerSecurityLimit(); + if( allowedAccountType > SEC_PLAYER && security < allowedAccountType) + { + WorldPacket Packet(SMSG_AUTH_RESPONSE, 1); + Packet << uint8(AUTH_UNAVAILABLE); + SendPacket(&Packet); + return; + } + + ///- kick already loaded player with same account (if any) and remove session + ///- if player is in loading and want to load again, return + if(!sWorld.RemoveSession(id)) + { + return; + } + + ///- Check that Key and account name are the same on client and server + Sha1Hash sha; + + uint32 t = 0; + uint32 seed = _seed; + + sha.UpdateData(account); + sha.UpdateData((uint8 *)&t, 4); + sha.UpdateData((uint8 *)&clientSeed, 4); + sha.UpdateData((uint8 *)&seed, 4); + sha.UpdateBigNumbers(&K, NULL); + sha.Finalize(); + + if (memcmp(sha.GetDigest(), digest, 20)) + { + packet.Initialize( SMSG_AUTH_RESPONSE, 1 ); + packet << uint8( AUTH_FAILED ); + SendPacket( &packet ); + + sLog.outDetail( "SOCKET: Sent Auth Response (authentication failed)." ); + return; + } + + ///- Initialize the encryption with the Key + _crypt.SetKey(&K); + _crypt.Init(); + + ///- Send 'Auth is ok' + packet.Initialize( SMSG_AUTH_RESPONSE, 1+4+1+4+1 ); + packet << uint8( AUTH_OK ); + packet << uint32(0); // unknown random value... + packet << uint8(0); // can be 0 and 2 + packet << uint32(0); // const 0 + packet << uint8(tbc ? 1 : 0); // 0 - normal, 1 - TBC, must be set in database manually for each account + SendPacket(&packet); + + ///- Create a new WorldSession for the player and add it to the World + _session = new WorldSession(id, this,security,tbc,mutetime,locale); + sWorld.AddSession(_session); + + if(sLog.IsOutDebug()) // optimize disabled debug output + { + sLog.outDebug( "SOCKET: Client '%s' authenticated successfully.", account.c_str() ); + sLog.outDebug( "Account: '%s' Logged in from IP %s.", account.c_str(), GetRemoteAddress().c_str()); + } + + ///- Update the last_ip in the database + //No SQL injection, username escaped. + std::string address = GetRemoteAddress(); + loginDatabase.escape_string(address); + loginDatabase.PExecute("UPDATE account SET last_ip = '%s' WHERE username = '%s'",address.c_str(), safe_account.c_str()); + + // do small delay (10ms) at accepting successful authed connection to prevent dropping packets by client + // don't must harm anyone (let login ~100 accounts in 1 sec ;) ) + #ifdef WIN32 + Sleep(10); + #else + ZThread::Thread::sleep(10); + #endif + + ///- Check that we do not exceed the maximum number of online players in the realm + uint32 Sessions = sWorld.GetActiveAndQueuedSessionCount(); + uint32 pLimit = sWorld.GetPlayerAmountLimit(); + uint32 QueueSize = sWorld.GetQueueSize(); //number of players in the queue + bool inQueue = false; + --Sessions; //so we don't count the user trying to login as a session and queue the socket that we are using + + if( pLimit > 0 && Sessions >= pLimit && security == SEC_PLAYER ) + { + sWorld.AddQueuedPlayer(this); + SendAuthWaitQue(sWorld.GetQueuePos(this)); + sWorld.UpdateMaxSessionCounters(); + sLog.outDetail( "PlayerQueue: %s is in Queue Position (%u).",safe_account.c_str(),++QueueSize); + inQueue = true; + } + + ///- Create and send the Addon packet + if(sAddOnHandler.BuildAddonPacket(&recvPacket, &SendAddonPacked)) + SendPacket(&SendAddonPacked); + + if(inQueue) + return; + + sWorld.UpdateMaxSessionCounters(); + + // Updates the population + if (pLimit > 0) + { + float popu = sWorld.GetActiveSessionCount(); //updated number of users on the server + popu /= pLimit; + popu *= 2; + loginDatabase.PExecute("UPDATE realmlist SET population = '%f' WHERE id = '%d'",popu,realmID); + sLog.outDetail( "Server Population (%f).",popu); + } + + return; +} + +/// Handle the Ping packet +void WorldSocket::_HandlePing(WorldPacket& recvPacket) +{ + uint32 ping; + uint32 latency; + + CHECK_PACKET_SIZE(recvPacket,8); + + ///- Get the ping packet content + recvPacket >> ping; + recvPacket >> latency; + + if (_session ) + _session->SetLatency(latency); + + ///- check ping speed for players + if(_session && _session->GetSecurity() == SEC_PLAYER) + { + uint32 cur_mstime = getMSTime(); + + // can overflow and start from 0 + uint32 diff_mstime = getMSTimeDiff(m_LastPingMSTime,cur_mstime); + m_LastPingMSTime = cur_mstime; + if(diff_mstime < 27000) // should be 30000 (=30 secs), add little tolerance + { + ++m_OverSpeedPings; + + uint32 max_count = sWorld.getConfig(CONFIG_MAX_OVERSPEED_PINGS); + if(max_count && m_OverSpeedPings > max_count) + { + sLog.outBasic("Player %s from account id %u kicked for overspeed ping packets from client (non-playable connection lags or cheating) ",_session->GetPlayerName(),_session->GetAccountId()); + _session->KickPlayer(); + return; + } + } + else + m_OverSpeedPings = 0; + + } + + ///- And put the pong answer in the to-be-sent queue + WorldPacket packet( SMSG_PONG, 4 ); + packet << ping; + SendPacket(&packet); + + return; +} + +/// Handle the update order for the socket +void WorldSocket::SendSinglePacket() +{ + WorldPacket *packet; + ServerPktHeader hdr; + + ///- If we have packet to send + if (!_sendQueue.empty()) + { + packet = _sendQueue.next(); + + hdr.size = ntohs((uint16)packet->size() + 2); + hdr.cmd = packet->GetOpcode(); + + if( sWorldLog.LogWorld() ) + { + sWorldLog.Log("SERVER:\nSOCKET: %u\nLENGTH: %u\nOPCODE: %s (0x%.4X)\nDATA:\n", + (uint32)GetSocket(), + packet->size(), + LookupOpcodeName(packet->GetOpcode()), + packet->GetOpcode()); + + uint32 p = 0; + while (p < packet->size()) + { + for (uint32 j = 0; j < 16 && p < packet->size(); j++) + sWorldLog.Log("%.2X ", (*packet)[p++]); + + sWorldLog.Log("\n"); + } + + sWorldLog.Log("\n\n"); + } + + ///- Encrypt (if needed) the header + _crypt.EncryptSend((uint8*)&hdr, 4); + + ///- Send the header and body to the client + TcpSocket::SendBuf((char*)&hdr, 4); + if(!packet->empty()) TcpSocket::SendBuf((char*)packet->contents(), packet->size()); + + delete packet; + } +} + +void WorldSocket::Update(time_t diff) +{ + const uint32 SEND_PACKETS_MAX = 100; + const uint32 SEND_BUFFER_SIZE = 1024; + + uint8 sendBuffer[SEND_BUFFER_SIZE]; + + while (!_sendQueue.empty()) + { + bool haveBigPacket = false; + uint32 bufferSize = 0; + + ///- While we have packets to send + for (uint32 packetCount = 0; (packetCount < SEND_PACKETS_MAX) && !_sendQueue.empty(); packetCount++) + { + ServerPktHeader *hdr = (ServerPktHeader*)&sendBuffer[bufferSize]; + + // check merge possibility. + WorldPacket *front = _sendQueue.front(); + uint32 packetSize = front->size(); + + if ((sizeof(*hdr) + packetSize) > SEND_BUFFER_SIZE) + { + haveBigPacket = true; + break; + } + + if ((bufferSize + sizeof(*hdr) + packetSize) > sizeof(sendBuffer)) + break; + + // can be merged + WorldPacket *packet = _sendQueue.next(); + + hdr->size = ntohs((uint16)packetSize + 2); + hdr->cmd = packet->GetOpcode(); + + if( sWorldLog.LogWorld() ) + { + sWorldLog.Log("SERVER:\nSOCKET: %u\nLENGTH: %u\nOPCODE: %s (0x%.4X)\nDATA:\n", + (uint32)GetSocket(), + packetSize, + LookupOpcodeName(packet->GetOpcode()), + packet->GetOpcode()); + + uint32 p = 0; + while (p < packetSize) + { + for (uint32 j = 0; j < 16 && p < packetSize; j++) + sWorldLog.Log("%.2X ", (*packet)[p++]); + + sWorldLog.Log("\n"); + } + + sWorldLog.Log("\n\n"); + } + + ///- Encrypt (if needed) the header + _crypt.EncryptSend((uint8*)hdr, sizeof(*hdr)); + bufferSize += sizeof(*hdr); + + if (packetSize) + { + memcpy(&sendBuffer[bufferSize], packet->contents(), packetSize); + bufferSize += packetSize; + } + + ///- Send the header and body to the client + delete packet; + } + + // send merged packets + if (bufferSize) TcpSocket::SendBuf((char*)sendBuffer, bufferSize); + // send too big non-merged packet + if (haveBigPacket) SendSinglePacket(); + } +} + +/// Handle the authentication waiting queue (to be completed) +void WorldSocket::SendAuthWaitQue(uint32 position) +{ + if(position == 0) + { + WorldPacket packet( SMSG_AUTH_RESPONSE, 1 ); + packet << uint8( AUTH_OK ); + SendPacket(&packet); + } + else + { + WorldPacket packet( SMSG_AUTH_RESPONSE, 5 ); + packet << uint8( AUTH_WAIT_QUEUE ); + packet << uint32 (position); //amount of players in queue + SendPacket(&packet); + } +} + +void WorldSocket::SizeError(WorldPacket const& packet, uint32 size) const +{ + sLog.outError("Client send packet %s (%u) with size %u but expected %u (attempt crash server?), skipped", + LookupOpcodeName(packet.GetOpcode()),packet.GetOpcode(),packet.size(),size); +} |