/*
* Ascent MMORPG Server
* Copyright (C) 2005-2007 Ascent Team
*
* 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 3 of the License, or
* 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, see .
*
*/
/* echo send/received packets to console */
//#define ECHO_PACKET_LOG_TO_CONSOLE
// Class WorldSocket - Main network code functions, handles
// reading/writing of all packets.
#include "StdAfx.h"
#include "../shared/AuthCodes.h"
#ifndef CLUSTERING
#pragma pack(push, 1)
struct ClientPktHeader
{
uint16 size;
uint32 cmd;
};
struct ServerPktHeader
{
uint16 size;
uint16 cmd;
};
#pragma pack(pop)
WorldSocket::WorldSocket(SOCKET fd) : Socket(fd, sWorld.SocketSendBufSize, sWorld.SocketRecvBufSize)
{
Authed = false;
mSize = mOpcode = mRemaining = 0;
_latency = 0;
mSession = NULL;
mSeed = rand() % 0xFFFFFFF0 + 10;
pAuthenticationPacket = NULL;
mQueued = false;
mRequestID = 0;
m_nagleEanbled = false;
}
WorldSocket::~WorldSocket()
{
WorldPacket * pck;
while((pck = _queue.Pop()))
delete pck;
if(pAuthenticationPacket)
delete pAuthenticationPacket;
}
void WorldSocket::OnDisconnect()
{
if(mSession)
mSession->SetSocket(0);
if(mRequestID != 0)
{
sLogonCommHandler.UnauthedSocketClose(mRequestID);
mRequestID = 0;
}
if(mQueued)
sWorld.RemoveQueuedSocket(this); // Remove from queued sockets.
}
void WorldSocket::OutPacket(uint16 opcode, uint16 len, const void* data)
{
OUTPACKET_RESULT res = _OutPacket(opcode, len, data);
if(res == OUTPACKET_RESULT_SUCCESS)
return;
if(res == OUTPACKET_RESULT_NO_ROOM_IN_BUFFER)
{
/* queue the packet */
queueLock.Acquire();
WorldPacket * pck = new WorldPacket(opcode, len);
if(len) pck->append((const uint8*)data, len);
_queue.Push(pck);
queueLock.Release();
}
}
void WorldSocket::UpdateQueuedPackets()
{
queueLock.Acquire();
if(!_queue.HasItems())
{
queueLock.Release();
return;
}
WorldPacket * pck;
while((pck = _queue.front()))
{
/* try to push out as many as you can */
switch(_OutPacket(pck->GetOpcode(), pck->size(), pck->size() ? pck->contents() : NULL))
{
case OUTPACKET_RESULT_SUCCESS:
{
delete pck;
_queue.pop_front();
}break;
case OUTPACKET_RESULT_NO_ROOM_IN_BUFFER:
{
/* still connected */
queueLock.Release();
return;
}break;
default:
{
/* kill everything in the buffer */
while((pck == _queue.Pop()))
delete pck;
queueLock.Release();
return;
}break;
}
}
queueLock.Release();
}
OUTPACKET_RESULT WorldSocket::_OutPacket(uint16 opcode, uint16 len, const void* data)
{
bool rv;
if(!IsConnected())
return OUTPACKET_RESULT_NOT_CONNECTED;
BurstBegin();
if((m_writeByteCount + len + 4) >= m_writeBufferSize)
{
BurstEnd();
return OUTPACKET_RESULT_NO_ROOM_IN_BUFFER;
}
// Packet logger :)
sWorldLog.LogPacket(len, opcode, (const uint8*)data, 1);
// Encrypt the packet
// First, create the header.
ServerPktHeader Header;
#ifdef USING_BIG_ENDIAN
Header.size = len + 2;
Header.cmd = swap16(opcode);
#else
Header.cmd = opcode;
Header.size = ntohs(len + 2);
#endif
_crypt.EncryptSend((uint8*)&Header, 4);
// Pass the header to our send buffer
rv = BurstSend((const uint8*)&Header, 4);
// Pass the rest of the packet to our send buffer (if there is any)
if(len > 0 && rv)
{
rv = BurstSend((const uint8*)data, len);
}
if(rv) BurstPush();
BurstEnd();
return rv ? OUTPACKET_RESULT_SUCCESS : OUTPACKET_RESULT_SOCKET_ERROR;
}
void WorldSocket::OnConnect()
{
sWorld.mAcceptedConnections++;
OutPacket(SMSG_AUTH_CHALLENGE, 4, &mSeed);
_latency = getMSTime();
}
void WorldSocket::_HandleAuthSession(WorldPacket* recvPacket)
{
std::string account;
uint32 unk2;
_latency = getMSTime() - _latency;
try
{
*recvPacket >> mClientBuild;
*recvPacket >> unk2;
*recvPacket >> account;
*recvPacket >> mClientSeed;
}
catch(ByteBuffer::error &)
{
sLog.outDetail("Incomplete copy of AUTH_SESSION Received.");
return;
}
// Send out a request for this account.
mRequestID = sLogonCommHandler.ClientConnected(account, this);
if(mRequestID == 0xFFFFFFFF)
{
Disconnect();
return;
}
// Set the authentication packet
pAuthenticationPacket = recvPacket;
}
void WorldSocket::InformationRetreiveCallback(WorldPacket & recvData, uint32 requestid)
{
if(requestid != mRequestID)
return;
uint32 error;
recvData >> error;
if(error != 0)
{
// something happened wrong @ the logon server
OutPacket(SMSG_AUTH_RESPONSE, 1, "\x0D");
return;
}
// Extract account information from the packet.
string AccountName;
uint32 AccountID;
string GMFlags;
uint32 AccountFlags;
recvData >> AccountID >> AccountName >> GMFlags >> AccountFlags;
sLog.outDebug( " >> got information packet from logon: `%s` ID %u (request %u)", AccountName.c_str(), AccountID, mRequestID);
// sLog.outColor(TNORMAL, "\n");
mRequestID = 0;
// Pull the session key.
uint8 K[40];
recvData.read(K, 40);
BigNumber BNK;
BNK.SetBinary(K, 40);
// Initialize crypto.
_crypt.SetKey(K, 40);
_crypt.Init();
//checking if player is already connected
//disconnect corrent player and login this one(blizzlike)
WorldSession *session = sWorld.FindSession( AccountID );
if( session)
{
// AUTH_FAILED = 0x0D
session->Disconnect();
}
Sha1Hash sha;
uint8 digest[20];
pAuthenticationPacket->read(digest, 20);
uint32 t = 0;
sha.UpdateData(AccountName);
sha.UpdateData((uint8 *)&t, 4);
sha.UpdateData((uint8 *)&mClientSeed, 4);
sha.UpdateData((uint8 *)&mSeed, 4);
sha.UpdateBigNumbers(&BNK, NULL);
sha.Finalize();
#ifndef USING_BIG_ENDIAN
if (memcmp(sha.GetDigest(), digest, 20))
{
// AUTH_UNKNOWN_ACCOUNT = 21
OutPacket(SMSG_AUTH_RESPONSE, 1, "\x15");
return;
}
#endif
// Allocate session
mSession = new WorldSession(AccountID, AccountName, this);
ASSERT(mSession);
mSession->deleteMutex.Acquire();
// Set session properties
mSession->SetClientBuild(mClientBuild);
mSession->LoadSecurity(GMFlags);
mSession->SetAccountFlags(AccountFlags);
mSession->m_lastPing = time(NULL);
for(uint32 i = 0; i < 8; ++i)
mSession->SetAccountData(i, NULL, true, 0);
sLog.outString("> %s authenticated from %s:%u [%ums]", AccountName.c_str(), GetRemoteIP().c_str(), GetRemotePort(), _latency);
// Check for queue.
if( (sWorld.GetNonGmSessionCount() < sWorld.GetPlayerLimit()) || mSession->HasGMPermissions() ) {
Authenticate();
} else {
mSession->deleteMutex.Release();
// Queued, sucker.
uint32 Position = sWorld.AddQueuedSocket(this);
mQueued = true;
sLog.outString("> %s added to queue in position %u", AccountName.c_str(), Position);
// Send packet so we know what we're doing
UpdateQueuePosition(Position);
}
}
void WorldSocket::Authenticate()
{
ASSERT(pAuthenticationPacket);
mQueued = false;
if(!mSession) return;
if(mSession->HasFlag(ACCOUNT_FLAG_XPACK_01))
OutPacket(SMSG_AUTH_RESPONSE, 11, "\x0C\x30\x78\x00\x00\x00\x00\x00\x00\x00\x01");
else
OutPacket(SMSG_AUTH_RESPONSE, 11, "\x0C\x30\x78\x00\x00\x00\x00\x00\x00\x00\x00");
sAddonMgr.SendAddonInfoPacket(pAuthenticationPacket, pAuthenticationPacket->rpos(), mSession);
mSession->_latency = _latency;
delete pAuthenticationPacket;
pAuthenticationPacket = 0;
sLog.outDetail("> Account %u authentication complete, adding session.", mSession->GetAccountId());
sWorld.AddSession(mSession);
sWorld.AddGlobalSession(mSession);
if(mSession->HasFlag(ACCOUNT_FLAG_XTEND_INFO))
sWorld.AddExtendedSession(mSession);
if(mSession->HasGMPermissions())
sWorld.gmList.insert(mSession);
mSession->deleteMutex.Release();
}
void WorldSocket::UpdateQueuePosition(uint32 Position)
{
WorldPacket QueuePacket(SMSG_AUTH_RESPONSE, 15);
QueuePacket << uint8(0x1B) << uint8(0x2C) << uint8(0x73) << uint8(0) << uint8(0);
QueuePacket << uint32(0) << uint8(0);
QueuePacket << Position;
SendPacket(&QueuePacket);
}
void WorldSocket::_HandlePing(WorldPacket* recvPacket)
{
uint32 ping;
if(recvPacket->size() < 4)
{
sLog.outString("Socket closed due to incomplete ping packet.");
Disconnect();
return;
}
*recvPacket >> ping;
if(mSession)
mSession->m_lastPing = time(NULL);
if(recvPacket->size() >= 8 && mSession)
{
*recvPacket >> _latency;
if(!_latency)
_latency = mSession->_latency;
else
mSession->_latency = _latency;
//sLog.outDetail("Got ping packet with latency of %u and seq of %u", mSession->_latency, ping);
}
OutPacket(SMSG_PONG, 4, &ping);
#ifdef WIN32
// Dynamically change nagle buffering status based on latency.
if(_latency >= 250)
{
if(!m_nagleEanbled)
{
u_long arg = 0;
setsockopt(GetFd(), 0x6, 0x1, (const char*)&arg, sizeof(arg));
m_nagleEanbled = true;
}
}
else
{
if(m_nagleEanbled)
{
u_long arg = 1;
setsockopt(GetFd(), 0x6, 0x1, (const char*)&arg, sizeof(arg));
m_nagleEanbled = false;
}
}
#endif
}
void WorldSocket::OnRead()
{
for(;;)
{
// Check for the header if we don't have any bytes to wait for.
if(mRemaining == 0)
{
if(GetReadBufferSize() < 6)
{
// No header in the packet, let's wait.
return;
}
// Copy from packet buffer into header local var
ClientPktHeader Header;
Read(6, (uint8*)&Header);
// Decrypt the header
_crypt.DecryptRecv((uint8*)&Header, 6);
#ifdef USING_BIG_ENDIAN
mRemaining = mSize = Header.size - 4;
mOpcode = swap32(Header.cmd);
#else
mRemaining = mSize = ntohs(Header.size) - 4;
mOpcode = Header.cmd;
#endif
}
WorldPacket * Packet;
if(mRemaining > 0)
{
if( GetReadBufferSize() < mRemaining )
{
// We have a fragmented packet. Wait for the complete one before proceeding.
return;
}
}
Packet = new WorldPacket(mOpcode, mSize);
Packet->resize(mSize);
if(mRemaining > 0)
{
// Copy from packet buffer into our actual buffer.
Read(mRemaining, (uint8*)Packet->contents());
}
sWorldLog.LogPacket(mSize, mOpcode, mSize ? Packet->contents() : NULL, 0);
mRemaining = mSize = mOpcode = 0;
// Check for packets that we handle
switch(Packet->GetOpcode())
{
case CMSG_PING:
{
_HandlePing(Packet);
delete Packet;
}break;
case CMSG_AUTH_SESSION:
{
_HandleAuthSession(Packet);
}break;
default:
{
if(mSession) mSession->QueuePacket(Packet);
else delete Packet;
}break;
}
}
}
#endif
void WorldLog::LogPacket(uint32 len, uint16 opcode, const uint8* data, uint8 direction)
{
#ifdef ECHO_PACKET_LOG_TO_CONSOLE
sLog.outString("[%s]: %s %s (0x%03X) of %u bytes.", direction ? "SERVER" : "CLIENT", direction ? "sent" : "received",
LookupName(opcode, g_worldOpcodeNames), opcode, len);
#endif
if(bEnabled)
{
mutex.Acquire();
unsigned int line = 1;
unsigned int countpos = 0;
uint16 lenght = len;
unsigned int count = 0;
fprintf(m_file, "{%s} Packet: (0x%04X) %s PacketSize = %u\n", (direction ? "SERVER" : "CLIENT"), opcode,
LookupName(opcode, g_worldOpcodeNames), lenght);
fprintf(m_file, "|------------------------------------------------|----------------|\n");
fprintf(m_file, "|00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F |0123456789ABCDEF|\n");
fprintf(m_file, "|------------------------------------------------|----------------|\n");
if(lenght > 0)
{
fprintf(m_file, "|");
for (count = 0 ; count < lenght ; count++)
{
if (countpos == 16)
{
countpos = 0;
fprintf(m_file, "|");
for (unsigned int a = count-16; a < count;a++)
{
if ((data[a] < 32) || (data[a] > 126))
fprintf(m_file, ".");
else
fprintf(m_file, "%c",data[a]);
}
fprintf(m_file, "|\n");
line++;
fprintf(m_file, "|");
}
fprintf(m_file, "%02X ",data[count]);
//FIX TO PARSE PACKETS WITH LENGHT < OR = TO 16 BYTES.
if (count+1 == lenght && lenght <= 16)
{
for (unsigned int b = countpos+1; b < 16;b++)
fprintf(m_file, " ");
fprintf(m_file, "|");
for (unsigned int a = 0; a < lenght;a++)
{
if ((data[a] < 32) || (data[a] > 126))
fprintf(m_file, ".");
else
fprintf(m_file, "%c",data[a]);
}
for (unsigned int c = count; c < 15;c++)
fprintf(m_file, " ");
fprintf(m_file, "|\n");
}
//FIX TO PARSE THE LAST LINE OF THE PACKETS WHEN THE LENGHT IS > 16 AND ITS IN THE LAST LINE.
if (count+1 == lenght && lenght > 16)
{
for (unsigned int b = countpos+1; b < 16;b++)
fprintf(m_file, " ");
fprintf(m_file, "|");
unsigned short print = 0;
for (unsigned int a = line * 16 - 16; a < lenght;a++)
{
if ((data[a] < 32) || (data[a] > 126))
fprintf(m_file, ".");
else
fprintf(m_file, "%c",data[a]);
print++;
}
for (unsigned int c = print; c < 16;c++)
fprintf(m_file, " ");
fprintf(m_file, "|\n");
}
countpos++;
}
}
fprintf(m_file, "-------------------------------------------------------------------\n\n");
fflush(m_file);
mutex.Release();
}
}