From 5651828bf74edb760d67700942fc65d51c816e0a Mon Sep 17 00:00:00 2001 From: Neo2003 Date: Sat, 4 Oct 2008 06:17:19 -0500 Subject: [svn] * Added ACE for Linux and Windows (Thanks Derex for Linux part and partial Windows part) * Updated to 6721 and 676 * Fixed TrinityScript logo * Version updated to 0.2.6721.676 --HG-- branch : trunk rename : 6700-670 => 6721-676 --- src/game/WorldSocket.cpp | 1369 ++++++++++++++++++++++++++++++---------------- 1 file changed, 897 insertions(+), 472 deletions(-) (limited to 'src/game/WorldSocket.cpp') diff --git a/src/game/WorldSocket.cpp b/src/game/WorldSocket.cpp index d52c0962862..cb1c0233b4d 100644 --- a/src/game/WorldSocket.cpp +++ b/src/game/WorldSocket.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2005-2008 MaNGOS * * This program is free software; you can redistribute it and/or modify @@ -16,649 +16,1074 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -/** \file - \ingroup u2w -*/ - #include "Common.h" -#include "Log.h" +#include "WorldSocket.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Util.h" +#include "World.h" +#include "WorldPacket.h" +#include "SharedDefines.h" +#include "ByteBuffer.h" +#include "AddonHandler.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 "Log.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 +struct ServerPktHeader { - uint16 size; - uint32 cmd; + ACE_UINT16 size; + ACE_UINT16 cmd; }; -/// Server Packet Header -struct ServerPktHeader +struct ClientPktHeader { - uint16 size; - uint16 cmd; + ACE_UINT16 size; + ACE_UINT32 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) +// used when testing to alow login without password and encryption +// #define _NETCODE_FAKE_AUTH + +WorldSocket::WorldSocket (void) : +WorldHandler (), +m_Session (0), +m_RecvWPct (0), +m_RecvPct (), +m_Header (sizeof (ClientPktHeader)), +m_OutBuffer (0), +m_OutBufferSize (65536), +m_OutActive (false), +m_Seed (static_cast (rand32 ())), +m_OverSpeedPings (0), +m_LastPingTime (ACE_Time_Value::zero) { - _seed = static_cast(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); + this->reference_counting_policy ().value (ACE_Event_Handler::Reference_Counting_Policy::ENABLED); } -/// WorldSocket destructor -WorldSocket::~WorldSocket() +WorldSocket::~WorldSocket (void) { - if(_session) - _session->SetSocket(0); + if (m_RecvWPct) + delete m_RecvWPct; - WorldPacket *packet; + if (m_OutBuffer) + m_OutBuffer->release (); - ///- Go through the to-be-sent queue and delete remaining packets - while(!_sendQueue.empty()) - { - packet = _sendQueue.next(); - delete packet; - } + this->closing_ = true; + + this->peer ().close (); + + WorldPacket* pct; + while (m_PacketQueue.dequeue_head (pct) == 0) + delete pct; } -/// Copy the packet to the to-be-sent queue -void WorldSocket::SendPacket(WorldPacket const* packet) +bool +WorldSocket::IsClosed (void) const { - WorldPacket *pck = new WorldPacket(*packet); - ASSERT(pck); - _sendQueue.add(pck); + return this->closing_; } -/// On client connection -void WorldSocket::OnAccept() +void +WorldSocket::CloseSocket (void) { - ///- Add the current socket to the list of sockets to be managed (WorldSocketMgr) - sWorldSocketMgr.AddSocket(this); - Utility::ResolveLocal(); + { + ACE_GUARD (LockType, Guard, m_OutBufferLock); + + if (this->closing_) + return; + + this->closing_ = true; + + this->peer ().close_writer (); + } - ///- Send a AUTH_CHALLENGE packet - WorldPacket packet( SMSG_AUTH_CHALLENGE, 4 ); - packet << _seed; + { + ACE_GUARD (LockType, Guard, m_SessionLock); + + m_Session = NULL; + } - SendPacket(&packet); } -/// Read the client transmitted data -void WorldSocket::OnRead() +const std::string& +WorldSocket::GetRemoteAddress (void) const { - TcpSocket::OnRead(); + return m_Address; +} - while(1) - { - ///- Read the packet header and decipher it (if needed) - if (!_remaining) - { - if (ibuf.GetLength() < 6) - break; +int +WorldSocket::SendPacket (const WorldPacket& pct) +{ + ACE_GUARD_RETURN (LockType, Guard, m_OutBufferLock, -1); - ClientPktHeader hdr; + if (this->closing_) + return -1; - ibuf.Read((char *)&hdr, 6); - _crypt.DecryptRecv((uint8 *)&hdr, 6); + // Dump outgoing packet. + if (sWorldLog.LogWorld ()) + { + sWorldLog.Log ("SERVER:\nSOCKET: %u\nLENGTH: %u\nOPCODE: %s (0x%.4X)\nDATA:\n", + (uint32) get_handle (), + pct.size (), + LookupOpcodeName (pct.GetOpcode ()), + pct.GetOpcode ()); + + uint32 p = 0; + while (p < pct.size ()) + { + for (uint32 j = 0; j < 16 && p < pct.size (); j++) + sWorldLog.Log ("%.2X ", const_cast(pct)[p++]); - _remaining = ntohs(hdr.size) - 4; - _cmd = hdr.cmd; + sWorldLog.Log ("\n"); } - if (ibuf.GetLength() < _remaining) - break; - - ///- Read the remaining of the packet - WorldPacket packet((uint16)_cmd, _remaining); + sWorldLog.Log ("\n\n"); + } - packet.resize(_remaining); - if(_remaining) ibuf.Read((char*)packet.contents(), _remaining); - _remaining = 0; + if (iSendPacket (pct) == -1) + { + WorldPacket* npct; - ///- 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"); - } + ACE_NEW_RETURN (npct, WorldPacket (pct), -1); - ///- If the packet is PING, KEEP_ALIVE or AUTH_SESSION, handle immediately - switch (_cmd) + // NOTE maybe check of the size of the queue can be good ? + // to make it bounded instead of unbounded + if (m_PacketQueue.enqueue_tail (npct) == -1) { - 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; - } + delete npct; + sLog.outError ("WorldSocket::SendPacket: m_PacketQueue.enqueue_tail failed"); + return -1; } } + + return 0; } -/// On socket closing -void WorldSocket::CloseSocket() +long +WorldSocket::AddReference (void) { - ///- Set CloseAndDelete flag for TcpSocket class - SetCloseAndDelete(true); + return static_cast (this->add_reference ()); +} - ///- Set _session to NULL. Prevent crashes - _session = NULL; +long +WorldSocket::RemoveReference (void) +{ + return static_cast (this->remove_reference ()); } -/// On socket deleting -void WorldSocket::OnDelete() +int +WorldSocket::open (void *a) { - ///- Stop sending remaining data through this socket - if (_session) + ACE_UNUSED_ARG (a); + + // Prevent double call to this func. + if (m_OutBuffer) + return -1; + + // This will also prevent the socket from being Updated + // while we are initializing it. + m_OutActive = true; + + // Hook for the manager. + if (sWorldSocketMgr->OnSocketOpen (this) == -1) + return -1; + + // Allocate the buffer. + ACE_NEW_RETURN (m_OutBuffer, ACE_Message_Block (m_OutBufferSize), -1); + + // Store peer address. + ACE_INET_Addr remote_addr; + + if (this->peer ().get_remote_addr (remote_addr) == -1) { - _session->SetSocket(NULL); - // Session deleted from World session list at socket==0, This is only back reference from socket to session. - _session = NULL; + sLog.outError ("WorldSocket::open: peer ().get_remote_addr errno = %s", ACE_OS::strerror (errno)); + return -1; } - ///- Remove the socket from the WorldSocketMgr list - sWorldSocketMgr.RemoveSocket(this); + m_Address = remote_addr.get_host_addr (); - ///- Removes socket from player queue - sWorld.RemoveQueuedPlayer(this); -} + // Send startup packet. + WorldPacket packet (SMSG_AUTH_CHALLENGE, 4); + packet << m_Seed; -/// 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 ) + if (SendPacket (packet) == -1) + return -1; + + // Register with ACE Reactor + if (this->reactor ()->register_handler + (this, + ACE_Event_Handler::READ_MASK | ACE_Event_Handler::WRITE_MASK) == -1) { - packet.Initialize( SMSG_AUTH_RESPONSE, 1 ); - packet << uint8( AUTH_UNKNOWN_ACCOUNT ); - SendPacket( &packet ); - sLog.outDetail( "SOCKET: Sent Auth Response (unknown account)." ); - return; + sLog.outError ("WorldSocket::open: unable to register client handler errno = %s", ACE_OS::strerror (errno)); + return -1; } - Field* fields = result->Fetch(); + // reactor takes care of the socket from now on + this->remove_reference (); - tbc = fields[8].GetUInt8() && sWorld.getConfig(CONFIG_EXPANSION) > 0; + return 0; +} - N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7"); - g.SetDword(7); - I.SetHexStr(fields[5].GetString()); +int +WorldSocket::close (int) +{ + this->shutdown (); - //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()); + this->closing_ = true; - std::reverse(mDigest,mDigest+SHA_DIGEST_LENGTH); + this->remove_reference (); - 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); + return 0; +} - 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); +int +WorldSocket::handle_input (ACE_HANDLE) +{ + if (this->closing_) + return -1; - ///- Re-check ip locking (same check as in realmd). - if(fields[4].GetUInt8() == 1) // if ip is locked + switch (this->handle_input_missing_data ()) { - 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; - } + case -1 : + { + if ((errno == EWOULDBLOCK) || + (errno == EAGAIN)) + { + //return 0; + return this->Update (); // interesting line ,isnt it ? + } + + DEBUG_LOG ("WorldSocket::handle_input: Peer error closing connection errno = %s", ACE_OS::strerror (errno)); + + return -1; + } + case 0: + { + DEBUG_LOG ("WorldSocket::handle_input: Peer has closed connection\n"); + + errno = ECONNRESET; + + return -1; + } + case 1: + return 1; } - id = fields[0].GetUInt32(); - security = fields[1].GetUInt16(); - K.SetHexStr(fields[2].GetString()); - time_t mutetime = time_t(fields[9].GetUInt64()); + //return 0; + return this->Update (); // another interesting line ;) +} + +int +WorldSocket::handle_output (ACE_HANDLE) +{ + ACE_GUARD_RETURN (LockType, Guard, m_OutBufferLock, -1); - LocaleConstant locale = LocaleConstant(fields[10].GetUInt8()); - if (locale>=MAX_LOCALE) - locale=LOCALE_enUS; + if (this->closing_) + return -1; - delete result; + const size_t send_len = m_OutBuffer->length (); - ///- 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 + if (send_len == 0) + return this->cancel_wakeup_output (Guard); + +// TODO SO_NOSIGPIPE on platforms that support it +#ifdef MSG_NOSIGNAL + ssize_t n = this->peer ().send (m_OutBuffer->rd_ptr (), send_len, MSG_NOSIGNAL); +#else + ssize_t n = this->peer ().send (m_OutBuffer->rd_ptr (), send_len); +#endif // MSG_NOSIGNAL + + if (n == 0) + return -1; + else if (n == -1) { - packet.Initialize( SMSG_AUTH_RESPONSE, 1 ); - packet << uint8( AUTH_BANNED ); - SendPacket( &packet ); + if (errno == EWOULDBLOCK || errno == EAGAIN) + return this->schedule_wakeup_output (Guard); - sLog.outDetail( "SOCKET: Sent Auth Response (Account banned)." ); - delete banresult; - return; + return -1; } - - ///- Check locked state for server - AccountTypes allowedAccountType = sWorld.GetPlayerSecurityLimit(); - if( allowedAccountType > SEC_PLAYER && security < allowedAccountType) + else if (n < send_len) //now n > 0 { - WorldPacket Packet(SMSG_AUTH_RESPONSE, 1); - Packet << uint8(AUTH_UNAVAILABLE); - SendPacket(&Packet); - return; - } + m_OutBuffer->rd_ptr (static_cast (n)); + + // move the data to the base of the buffer + m_OutBuffer->crunch (); - ///- 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 this->schedule_wakeup_output (Guard); + } + else //now n == send_len { - return; + m_OutBuffer->reset (); + + if (!iFlushPacketQueue ()) + return this->cancel_wakeup_output (Guard); + else + return this->schedule_wakeup_output (Guard); } - ///- Check that Key and account name are the same on client and server - Sha1Hash sha; + ACE_NOTREACHED (return 0); +} - uint32 t = 0; - uint32 seed = _seed; +int +WorldSocket::handle_close (ACE_HANDLE h, ACE_Reactor_Mask) +{ + // Critical section + { + ACE_GUARD_RETURN (LockType, Guard, m_OutBufferLock, -1); - sha.UpdateData(account); - sha.UpdateData((uint8 *)&t, 4); - sha.UpdateData((uint8 *)&clientSeed, 4); - sha.UpdateData((uint8 *)&seed, 4); - sha.UpdateBigNumbers(&K, NULL); - sha.Finalize(); + this->closing_ = true; - if (memcmp(sha.GetDigest(), digest, 20)) - { - packet.Initialize( SMSG_AUTH_RESPONSE, 1 ); - packet << uint8( AUTH_FAILED ); - SendPacket( &packet ); + if (h == ACE_INVALID_HANDLE) + this->peer ().close_writer (); + } - sLog.outDetail( "SOCKET: Sent Auth Response (authentication failed)." ); - return; - } + // Critical section + { + ACE_GUARD_RETURN (LockType, Guard, m_SessionLock, -1); - ///- Initialize the encryption with the Key - _crypt.SetKey(&K); - _crypt.Init(); + m_Session = NULL; + } - ///- 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); + return 0; +} - ///- 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); +int +WorldSocket::Update (void) +{ + if (this->closing_) + return -1; - 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()); - } + if (m_OutActive || m_OutBuffer->length () == 0) + return 0; - ///- 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 ) + return this->handle_output (this->get_handle ()); +} + +int +WorldSocket::handle_input_header (void) +{ + ACE_ASSERT (m_RecvWPct == NULL); + + if (m_Header.length () != sizeof (ClientPktHeader)) { - 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; + sLog.outError ("WorldSocket::handle_input_header: internal error: invalid header"); + errno = EINVAL; + return -1; } - ///- Create and send the Addon packet - if(sAddOnHandler.BuildAddonPacket(&recvPacket, &SendAddonPacked)) - SendPacket(&SendAddonPacked); + m_Crypt.DecryptRecv ((ACE_UINT8*) m_Header.rd_ptr (), sizeof (ClientPktHeader)); - if(inQueue) - return; + ClientPktHeader& header = *((ClientPktHeader*) m_Header.rd_ptr ()); - sWorld.UpdateMaxSessionCounters(); + header.size = ACE_NTOHS (header.size); - // Updates the population - if (pLimit > 0) +#if ACE_BYTE_ORDER == ACE_BIG_ENDIAN + header.cmd = ACE_SWAP_LONG (header.cmd) +#endif // ACE_BIG_ENDIAN + + if ((header.size < 4) || + (header.size > 10240) || + (header.cmd <= 0) || + (header.cmd > 10240) + ) { - 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); + sLog.outError ("WorldSocket::handle_input_header: client sent mailformed packet size = %d , cmd = %d", + header.size, + header.cmd); + + errno = EINVAL; + return -1; } - return; + header.size -= 4; + + ACE_NEW_RETURN (m_RecvWPct, WorldPacket ((uint16) header.cmd, header.size), -1); + + if(header.size > 0) + { + m_RecvWPct->resize (header.size); + m_RecvPct.base ((char*) m_RecvWPct->contents (), m_RecvWPct->size ()); + } + else + { + ACE_ASSERT(m_RecvPct.space() == 0); + } + + + return 0; } -/// Handle the Ping packet -void WorldSocket::_HandlePing(WorldPacket& recvPacket) +int +WorldSocket::handle_input_payload (void) { - uint32 ping; - uint32 latency; + // set errno properly here on error !!! + // now have a header and payload + + ACE_ASSERT (m_RecvPct.space () == 0); + ACE_ASSERT (m_Header.space () == 0); + ACE_ASSERT (m_RecvWPct != NULL); + + const int ret = this->ProcessIncoming (m_RecvWPct); - CHECK_PACKET_SIZE(recvPacket,8); + m_RecvPct.base (NULL, 0); + m_RecvPct.reset (); + m_RecvWPct = NULL; - ///- Get the ping packet content - recvPacket >> ping; - recvPacket >> latency; + m_Header.reset (); - if (_session ) - _session->SetLatency(latency); + if (ret == -1) + errno = EINVAL; + + return ret; +} + +int +WorldSocket::handle_input_missing_data (void) +{ + char buf [1024]; - ///- check ping speed for players - if(_session && _session->GetSecurity() == SEC_PLAYER) + ACE_Data_Block db (sizeof (buf), + ACE_Message_Block::MB_DATA, + buf, + 0, + 0, + ACE_Message_Block::DONT_DELETE, + 0); + + ACE_Message_Block message_block (&db, + ACE_Message_Block::DONT_DELETE, + 0); + + const size_t recv_size = message_block.space (); + + const ssize_t n = this->peer ().recv (message_block.wr_ptr (), + recv_size); + + if (n <= 0) + return n; + + message_block.wr_ptr (n); + + while (message_block.length () > 0) { - uint32 cur_mstime = getMSTime(); + if (m_Header.space () > 0) + { + //need to recieve the header + const size_t to_header = (message_block.length () > m_Header.space () ? m_Header.space () : message_block.length ()); + m_Header.copy (message_block.rd_ptr (), to_header); + message_block.rd_ptr (to_header); + + if (m_Header.space () > 0) + { + //couldnt recieve the whole header this time + ACE_ASSERT (message_block.length () == 0); + errno = EWOULDBLOCK; + return -1; + } + + //we just recieved nice new header + if (this->handle_input_header () == -1) + { + ACE_ASSERT ((errno != EWOULDBLOCK) && (errno != EAGAIN)); + return -1; + } + } + + // Its possible on some error situations that this happens + // for example on closing when epoll recieves more chunked data and stuff + // hope this is not hack ,as proper m_RecvWPct is asserted around + if (!m_RecvWPct) + { + sLog.outError ("Forsing close on input m_RecvWPct = NULL"); + errno = EINVAL; + return -1; + } - // 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 + // We have full readed header, now check the data payload + if (m_RecvPct.space () > 0) { - ++m_OverSpeedPings; + //need more data in the payload + const size_t to_data = (message_block.length () > m_RecvPct.space () ? m_RecvPct.space () : message_block.length ()); + m_RecvPct.copy (message_block.rd_ptr (), to_data); + message_block.rd_ptr (to_data); - uint32 max_count = sWorld.getConfig(CONFIG_MAX_OVERSPEED_PINGS); - if(max_count && m_OverSpeedPings > max_count) + if (m_RecvPct.space () > 0) { - 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; + //couldnt recieve the whole data this time + ACE_ASSERT (message_block.length () == 0); + errno = EWOULDBLOCK; + return -1; } } - else - m_OverSpeedPings = 0; + //just recieved fresh new payload + if (this->handle_input_payload () == -1) + { + ACE_ASSERT ((errno != EWOULDBLOCK) && (errno != EAGAIN)); + return -1; + } } - ///- And put the pong answer in the to-be-sent queue - WorldPacket packet( SMSG_PONG, 4 ); - packet << ping; - SendPacket(&packet); + return n == recv_size ? 1 : 2; +} + +int +WorldSocket::cancel_wakeup_output (GuardType& g) +{ + if (!m_OutActive) + return 0; + + m_OutActive = false; + + g.release (); - return; + if (this->reactor ()->cancel_wakeup + (this, ACE_Event_Handler::WRITE_MASK) == -1) + { + // would be good to store errno from reactor with errno guard + sLog.outError ("WorldSocket::cancel_wakeup_output"); + return -1; + } + + return 0; } -/// Handle the update order for the socket -void WorldSocket::SendSinglePacket() +int +WorldSocket::schedule_wakeup_output (GuardType& g) { - WorldPacket *packet; - ServerPktHeader hdr; + if (m_OutActive) + return 0; + + m_OutActive = true; - ///- If we have packet to send - if (!_sendQueue.empty()) + g.release (); + + if (this->reactor ()->schedule_wakeup + (this, ACE_Event_Handler::WRITE_MASK) == -1) { - packet = _sendQueue.next(); + sLog.outError ("WorldSocket::schedule_wakeup_output"); + return -1; + } - hdr.size = ntohs((uint16)packet->size() + 2); - hdr.cmd = packet->GetOpcode(); + return 0; +} - 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++]); +int +WorldSocket::ProcessIncoming (WorldPacket* new_pct) +{ + ACE_ASSERT (new_pct); + + // manage memory ;) + ACE_Auto_Ptr aptr (new_pct); - sWorldLog.Log("\n"); - } + const ACE_UINT16 opcode = new_pct->GetOpcode (); - sWorldLog.Log("\n\n"); + if (this->closing_) + return -1; + + // dump recieved packet + if (sWorldLog.LogWorld ()) + { + sWorldLog.Log ("CLIENT:\nSOCKET: %u\nLENGTH: %u\nOPCODE: %s (0x%.4X)\nDATA:\n", + (uint32) get_handle (), + new_pct->size (), + LookupOpcodeName (new_pct->GetOpcode ()), + new_pct->GetOpcode ()); + + uint32 p = 0; + while (p < new_pct->size ()) + { + for (uint32 j = 0; j < 16 && p < new_pct->size (); j++) + sWorldLog.Log ("%.2X ", (*new_pct)[p++]); + sWorldLog.Log ("\n"); } + sWorldLog.Log ("\n\n"); + } - ///- Encrypt (if needed) the header - _crypt.EncryptSend((uint8*)&hdr, 4); + // like one switch ;) + if (opcode == CMSG_PING) + { + return HandlePing (*new_pct); + } + else if (opcode == CMSG_AUTH_SESSION) + { + if (m_Session) + { + sLog.outError ("WorldSocket::ProcessIncoming: Player send CMSG_AUTH_SESSION again"); + return -1; + } - ///- Send the header and body to the client - TcpSocket::SendBuf((char*)&hdr, 4); - if(!packet->empty()) TcpSocket::SendBuf((char*)packet->contents(), packet->size()); + return HandleAuthSession (*new_pct); + } + else if (opcode == CMSG_KEEP_ALIVE) + { + DEBUG_LOG ("CMSG_KEEP_ALIVE ,size: %d", new_pct->size ()); - delete packet; + return 0; } + else + { + ACE_GUARD_RETURN (LockType, Guard, m_SessionLock, -1); + + if (m_Session != NULL) + { + // OK ,give the packet to WorldSession + aptr.release (); + // WARNINIG here we call it with locks held. + // Its possible to cause deadlock if QueuePacket calls back + m_Session->QueuePacket (new_pct); + return 0; + } + else + { + sLog.outError ("WorldSocket::ProcessIncoming: Client not authed opcode = ", opcode); + return -1; + } + } + + ACE_NOTREACHED (return 0); } -void WorldSocket::Update(time_t diff) +int +WorldSocket::HandleAuthSession (WorldPacket& recvPacket) { - const uint32 SEND_PACKETS_MAX = 100; - const uint32 SEND_BUFFER_SIZE = 1024; + uint8 digest[20]; + uint32 clientSeed; + uint32 unk2; + uint32 BuiltNumberClient; + uint32 id, security; + bool tbc = false; + LocaleConstant locale; + std::string account; + Sha1Hash sha1; + BigNumber v, s, g, N, x, I; + WorldPacket packet, SendAddonPacked; + + BigNumber K; + + if (recvPacket.size () < (4 + 4 + 1 + 4 + 20)) + { + sLog.outError ("WorldSocket::HandleAuthSession: wrong packet size"); + return -1; + } + + // Read the content of the packet + recvPacket >> BuiltNumberClient; // for now no use + recvPacket >> unk2; + recvPacket >> account; - uint8 sendBuffer[SEND_BUFFER_SIZE]; + if (recvPacket.size () < (4 + 4 + (account.size () + 1) + 4 + 20)) + { + sLog.outError ("WorldSocket::HandleAuthSession: wrong packet size second check"); + return -1; + } + + recvPacket >> clientSeed; + recvPacket.read (digest, 20); + + DEBUG_LOG ("WorldSocket::HandleAuthSession: client %u, unk2 %u, account %s, clientseed %u", + BuiltNumberClient, + unk2, + account.c_str (), + clientSeed); - while (!_sendQueue.empty()) +#if defined _NETCODE_FAKE_AUTH + bool dontchechtheacc = false; + uint8 digest_fake[sizeof (digest)]; + memset ((void*) digest_fake, '\0', sizeof (digest_fake)); + if (memcmp ((void*) digest, (void*) digest_fake, sizeof (digest_fake)) == 0) { - bool haveBigPacket = false; - uint32 bufferSize = 0; + dontchechtheacc = true; + } +#endif //_NETCODE_FAKE_AUTH + + // 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. + + QueryResult *result = + loginDatabase.PQuery ("SELECT " + "id, " //0 + "gmlevel, " //1 + "sessionkey, " //2 + "last_ip, " //3 + "locked, " //4 + "sha_pass_hash, " //5 + "v, " //6 + "s, " //7 + "tbc, " //8 + "mutetime, " //9 + "locale " //10 + "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.outError ("WorldSocket::HandleAuthSession: Sent Auth Response (unknown account)."); + return -1; + } + + 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 (); - ///- While we have packets to send - for (uint32 packetCount = 0; (packetCount < SEND_PACKETS_MAX) && !_sendQueue.empty(); packetCount++) + DEBUG_LOG ("WorldSocket::HandleAuthSession: (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 defined _NETCODE_FAKE_AUTH + if (!dontchechtheacc) + { +#endif + if (!vold || strcmp (vStr, vold)) { - ServerPktHeader *hdr = (ServerPktHeader*)&sendBuffer[bufferSize]; + packet.Initialize (SMSG_AUTH_RESPONSE, 1); + packet << uint8 (AUTH_UNKNOWN_ACCOUNT); + SendPacket (packet); + delete result; + OPENSSL_free ((void*) sStr); + OPENSSL_free ((void*) vStr); + + sLog.outError ("WorldSocket::HandleAuthSession: User not logged."); + return -1; + } +#if defined _NETCODE_FAKE_AUTH + } +#endif - // check merge possibility. - WorldPacket *front = _sendQueue.front(); - uint32 packetSize = front->size(); + OPENSSL_free ((void*) sStr); + OPENSSL_free ((void*) vStr); - if ((sizeof(*hdr) + packetSize) > SEND_BUFFER_SIZE) - { - haveBigPacket = true; - break; - } + ///- 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); - if ((bufferSize + sizeof(*hdr) + packetSize) > sizeof(sendBuffer)) - break; + delete result; + sLog.outError ("WorldSocket::HandleAuthSession: Sent Auth Response (Account IP differs)."); + return -1; + } + } - // can be merged - WorldPacket *packet = _sendQueue.next(); + id = fields[0].GetUInt32 (); + security = fields[1].GetUInt16 (); + K.SetHexStr (fields[2].GetString ()); - hdr->size = ntohs((uint16)packetSize + 2); - hdr->cmd = packet->GetOpcode(); + time_t mutetime = time_t (fields[9].GetUInt64 ()); - 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++]); + locale = LocaleConstant (fields[10].GetUInt8 ()); + if (locale >= MAX_LOCALE) + locale = LOCALE_enUS; - sWorldLog.Log("\n"); - } + delete result; - sWorldLog.Log("\n\n"); - } +#if defined _NETCODE_FAKE_AUTH + if (!dontchechtheacc) + { +#endif + // Re-check account ban (same check as in realmd) + 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); - ///- Encrypt (if needed) the header - _crypt.EncryptSend((uint8*)hdr, sizeof(*hdr)); - bufferSize += sizeof(*hdr); + delete banresult; - if (packetSize) - { - memcpy(&sendBuffer[bufferSize], packet->contents(), packetSize); - bufferSize += packetSize; - } + sLog.outError ("WorldSocket::HandleAuthSession: Sent Auth Response (Account banned)."); + return -1; + } + + // Check locked state for server + AccountTypes allowedAccountType = sWorld.GetPlayerSecurityLimit (); - ///- Send the header and body to the client - delete packet; + if (allowedAccountType > SEC_PLAYER && security < allowedAccountType) + { + WorldPacket Packet (SMSG_AUTH_RESPONSE, 1); + Packet << uint8 (AUTH_UNAVAILABLE); + + SendPacket (packet); + + sLog.outBasic ("WorldSocket::HandleAuthSession: User tryes to login but his security level is not enough"); + return -1; } - // send merged packets - if (bufferSize) TcpSocket::SendBuf((char*)sendBuffer, bufferSize); - // send too big non-merged packet - if (haveBigPacket) SendSinglePacket(); + // Check that Key and account name are the same on client and server + Sha1Hash sha; + + uint32 t = 0; + uint32 seed = m_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.outError ("WorldSocket::HandleAuthSession: Sent Auth Response (authentification failed)."); + return -1; + } +#if defined _NETCODE_FAKE_AUTH } +#endif + + std::string address = this->GetRemoteAddress (); + + DEBUG_LOG ("WorldSocket::HandleAuthSession: Client '%s' authenticated successfully from %s.", + account.c_str (), + address.c_str ()); + + // Update the last_ip in the database + // No SQL injection, username escaped. + loginDatabase.escape_string (address); + + loginDatabase.PExecute ("UPDATE account " + "SET last_ip = '%s' " + "WHERE username = '%s'", + address.c_str (), + safe_account.c_str ()); + + // TODO protect here probably ? + // Althought atm the socket is singlethreaded + ACE_NEW_RETURN (m_Session, WorldSession (id, this, security, tbc, mutetime, locale), -1); + +#if defined _NETCODE_FAKE_AUTH + if (!dontchechtheacc) + { +#endif + this->m_Crypt.SetKey (&K); + this->m_Crypt.Init (); +#if defined _NETCODE_FAKE_AUTH + } +#endif + + // In case needed sometime the second arg is in microseconds 1 000 000 = 1 sec + ACE_OS::sleep (ACE_Time_Value (0, 10000)); + + // TODO error handling + sWorld.AddSession (this->m_Session); + + // Create and send the Addon packet + if (sAddOnHandler.BuildAddonPacket (&recvPacket, &SendAddonPacked)) + SendPacket (SendAddonPacked); + + return 0; } -/// Handle the authentication waiting queue (to be completed) -void WorldSocket::SendAuthWaitQue(uint32 position) +int +WorldSocket::HandlePing (WorldPacket& recvPacket) { - if(position == 0) + uint32 ping; + uint32 latency; + + if (recvPacket.size () < 8) { - WorldPacket packet( SMSG_AUTH_RESPONSE, 1 ); - packet << uint8( AUTH_OK ); - SendPacket(&packet); + sLog.outError ("WorldSocket::_HandlePing wrong packet size"); + return -1; } + + // Get the ping packet content + recvPacket >> ping; + recvPacket >> latency; + + if (m_LastPingTime == ACE_Time_Value::zero) + m_LastPingTime = ACE_OS::gettimeofday (); // for 1st ping + else + { + ACE_Time_Value cur_time = ACE_OS::gettimeofday (); + ACE_Time_Value diff_time (cur_time); + diff_time -= m_LastPingTime; + m_LastPingTime = cur_time; + + if (diff_time < ACE_Time_Value (27)) + { + ++m_OverSpeedPings; + + uint32 max_count = sWorld.getConfig (CONFIG_MAX_OVERSPEED_PINGS); + + if (max_count && m_OverSpeedPings > max_count) + { + ACE_GUARD_RETURN (LockType, Guard, m_SessionLock, -1); + + if (m_Session && m_Session->GetSecurity () == SEC_PLAYER) + { + sLog.outError ("WorldSocket::HandlePing: Player kicked for " + "overspeeded pings adress = %s", + GetRemoteAddress ().c_str ()); + + return -1; + } + } + } + else + m_OverSpeedPings = 0; + } + + // critical section + { + ACE_GUARD_RETURN (LockType, Guard, m_SessionLock, -1); + + if (m_Session) + m_Session->SetLatency (latency); else + { + sLog.outError ("WorldSocket::HandlePing: peer sent CMSG_PING, " + "but is not authenticated or got recently kicked," + " adress = %s", + this->GetRemoteAddress ().c_str ()); + return -1; + } + } + + WorldPacket packet (SMSG_PONG, 4); + packet << ping; + return this->SendPacket (packet); +} + +int +WorldSocket::iSendPacket (const WorldPacket& pct) +{ + if (m_OutBuffer->space () < pct.size () + sizeof (ServerPktHeader)) { - WorldPacket packet( SMSG_AUTH_RESPONSE, 5 ); - packet << uint8( AUTH_WAIT_QUEUE ); - packet << uint32 (position); //amount of players in queue - SendPacket(&packet); + errno = ENOBUFS; + return -1; } + + ServerPktHeader header; + + header.cmd = pct.GetOpcode (); + +#if ACE_BYTE_ORDER == ACE_BIG_ENDIAN + header.cmd = ACE_SWAP_WORD (header.cmd) +#endif + + header.size = (uint16) pct.size () + 2; + header.size = ACE_HTONS (header.size); + + m_Crypt.EncryptSend ((uint8*) & header, sizeof (header)); + + if (m_OutBuffer->copy ((char*) & header, sizeof (header)) == -1) + ACE_ASSERT (false); + + if (!pct.empty ()) + if (m_OutBuffer->copy ((char*) pct.contents (), pct.size ()) == -1) + ACE_ASSERT (false); + + return 0; } -void WorldSocket::SizeError(WorldPacket const& packet, uint32 size) const +bool +WorldSocket::iFlushPacketQueue () { - 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); + WorldPacket *pct; + bool haveone = false; + + while (m_PacketQueue.dequeue_head (pct) == 0) + { + if (iSendPacket (*pct) == -1) + { + if (m_PacketQueue.enqueue_head (pct) == -1) + { + delete pct; + sLog.outError ("WorldSocket::iFlushPacketQueue m_PacketQueue->enqueue_head"); + return false; + } + + break; + } + else + { + haveone = true; + delete pct; + } + } + + return haveone; } -- cgit v1.2.3