root/trunk/src/game/WorldSocket.cpp @ 2

Revision 2, 20.8 kB (checked in by yumileroy, 17 years ago)

[svn] * Proper SVN structure

Original author: Neo2003
Date: 2008-10-02 16:23:55-05:00

Line 
1/*
2 * Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.org/>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 */
18
19/** \file
20    \ingroup u2w
21*/
22
23#include "Common.h"
24#include "Log.h"
25#include "Opcodes.h"
26#include "Database/DatabaseEnv.h"
27#include "Auth/Sha1.h"
28#include "WorldPacket.h"
29#include "WorldSocket.h"
30#include "WorldSession.h"
31#include "World.h"
32#include "WorldSocketMgr.h"
33#include "Policies/SingletonImp.h"
34#include "WorldLog.h"
35#include "AddonHandler.h"
36#include "sockets/Utility.h"
37#include "Util.h"
38
39// 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
40#if defined( __GNUC__ )
41#pragma pack(1)
42#else
43#pragma pack(push,1)
44#endif
45
46/// Client Packet Header
47struct ClientPktHeader
48{
49    uint16 size;
50    uint32 cmd;
51};
52
53/// Server Packet Header
54struct ServerPktHeader
55{
56    uint16 size;
57    uint16 cmd;
58};
59
60// GCC have alternative #pragma pack() syntax and old gcc version not support pack(pop), also any gcc version not support it at some platform
61#if defined( __GNUC__ )
62#pragma pack()
63#else
64#pragma pack(pop)
65#endif
66
67#define SOCKET_CHECK_PACKET_SIZE(P,S) if((P).size() < (S)) return SizeError((P),(S));
68
69/// WorldSocket construction and initialization.
70WorldSocket::WorldSocket(ISocketHandler &sh): TcpSocket(sh), _cmd(0), _remaining(0), _session(NULL)
71{
72    _seed = static_cast<uint32>(rand32());
73    m_LastPingMSTime = 0;                                   // first time it will counted as overspeed maybe, but this is not important
74    m_OverSpeedPings = 0;
75
76    if (sWorld.getConfig(CONFIG_TCP_NO_DELAY))
77        SetTcpNodelay(true);
78}
79
80/// WorldSocket destructor
81WorldSocket::~WorldSocket()
82{
83    if(_session)
84        _session->SetSocket(0);
85
86    WorldPacket *packet;
87
88    ///- Go through the to-be-sent queue and delete remaining packets
89    while(!_sendQueue.empty())
90    {
91        packet = _sendQueue.next();
92        delete packet;
93    }
94}
95
96/// Copy the packet to the to-be-sent queue
97void WorldSocket::SendPacket(WorldPacket const* packet)
98{
99    WorldPacket *pck = new WorldPacket(*packet);
100    ASSERT(pck);
101    _sendQueue.add(pck);
102}
103
104/// On client connection
105void WorldSocket::OnAccept()
106{
107    ///- Add the current socket to the list of sockets to be managed (WorldSocketMgr)
108    sWorldSocketMgr.AddSocket(this);
109    Utility::ResolveLocal();
110
111    ///- Send a AUTH_CHALLENGE packet
112    WorldPacket packet( SMSG_AUTH_CHALLENGE, 4 );
113    packet << _seed;
114
115    SendPacket(&packet);
116}
117
118/// Read the client transmitted data
119void WorldSocket::OnRead()
120{
121    TcpSocket::OnRead();
122
123    while(1)
124    {
125        ///- Read the packet header and decipher it (if needed)
126        if (!_remaining)
127        {
128            if (ibuf.GetLength() < 6)
129                break;
130
131            ClientPktHeader hdr;
132
133            ibuf.Read((char *)&hdr, 6);
134            _crypt.DecryptRecv((uint8 *)&hdr, 6);
135
136            _remaining = ntohs(hdr.size) - 4;
137            _cmd = hdr.cmd;
138        }
139
140        if (ibuf.GetLength() < _remaining)
141            break;
142
143        ///- Read the remaining of the packet
144        WorldPacket packet((uint16)_cmd, _remaining);
145
146        packet.resize(_remaining);
147        if(_remaining) ibuf.Read((char*)packet.contents(), _remaining);
148        _remaining = 0;
149
150        ///- If log of world packets is enable, log the incoming packet
151        if( sWorldLog.LogWorld() )
152        {
153            sWorldLog.Log("CLIENT:\nSOCKET: %u\nLENGTH: %u\nOPCODE: %s (0x%.4X)\nDATA:\n",
154                (uint32)GetSocket(),
155                packet.size(),
156                LookupOpcodeName(packet.GetOpcode()),
157                packet.GetOpcode());
158
159            uint32 p = 0;
160            while (p < packet.size())
161            {
162                for (uint32 j = 0; j < 16 && p < packet.size(); j++)
163                    sWorldLog.Log("%.2X ", packet[p++]);
164                sWorldLog.Log("\n");
165            }
166            sWorldLog.Log("\n\n");
167        }
168
169        ///- If the packet is PING, KEEP_ALIVE or AUTH_SESSION, handle immediately
170        switch (_cmd)
171        {
172            case CMSG_KEEP_ALIVE:
173                break;                                      // just ignore, network connectivity timeout preventing
174            case CMSG_PING:
175            {
176                _HandlePing(packet);
177                break;
178            }
179            case CMSG_AUTH_SESSION:
180            {
181                _HandleAuthSession(packet);
182                break;
183            }
184            default:
185            {
186                ///- Else, put it in the world session queue for this user (need to be already authenticated)
187                if (_session)
188                    _session->QueuePacket(packet);
189                else
190                    sLog.outDetail("Received out of place packet with cmdid 0x%.4X", _cmd);
191                break;
192            }
193        }
194    }
195}
196
197/// On socket closing
198void WorldSocket::CloseSocket()
199{
200    ///- Set CloseAndDelete flag for TcpSocket class
201    SetCloseAndDelete(true);
202
203    ///- Set _session to NULL. Prevent crashes
204    _session = NULL;
205}
206
207/// On socket deleting
208void WorldSocket::OnDelete()
209{
210    ///- Stop sending remaining data through this socket
211    if (_session)
212    {
213        _session->SetSocket(NULL);
214        // Session deleted from World session list at socket==0, This is only back reference from socket to session.
215        _session = NULL;
216    }
217
218    ///- Remove the socket from the WorldSocketMgr list
219    sWorldSocketMgr.RemoveSocket(this);
220
221    ///- Removes socket from player queue
222    sWorld.RemoveQueuedPlayer(this);
223}
224
225/// Handle the client authentication packet
226void WorldSocket::_HandleAuthSession(WorldPacket& recvPacket)
227{
228    uint8 digest[20];
229    uint32 clientSeed;
230    uint32 unk2;
231    uint32 BuiltNumberClient;
232    uint32 id, security;
233    bool tbc = false;
234    std::string account;
235    Sha1Hash sha1;
236    BigNumber v, s, g, N, x, I;
237    WorldPacket packet, SendAddonPacked;
238
239    BigNumber K;
240
241    SOCKET_CHECK_PACKET_SIZE(recvPacket,4+4+1+4+20);
242
243    ///- Read the content of the packet
244    recvPacket >> BuiltNumberClient;                        // for now no use
245    recvPacket >> unk2;
246    recvPacket >> account;
247
248    // recheck size
249    SOCKET_CHECK_PACKET_SIZE(recvPacket,4+4+(account.size()+1)+4+20);
250
251    recvPacket >> clientSeed;
252    recvPacket.read(digest, 20);
253
254    sLog.outDebug("Auth: client %u, unk2 %u, account %s, clientseed %u", BuiltNumberClient, unk2, account.c_str(), clientSeed);
255
256    ///- Normalize account name
257    //utf8ToUpperOnlyLatin(account); -- client already send account in expected form
258
259    ///- Get the account information from the realmd database
260    std::string safe_account = account;                     // Duplicate, else will screw the SHA hash verification below
261    loginDatabase.escape_string(safe_account);
262    //No SQL injection, username escaped.
263    //                                                 0   1        2           3        4       5              6  7  8    9         10
264    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());
265
266    ///- Stop if the account is not found
267    if ( !result )
268    {
269        packet.Initialize( SMSG_AUTH_RESPONSE, 1 );
270        packet << uint8( AUTH_UNKNOWN_ACCOUNT );
271        SendPacket( &packet );
272        sLog.outDetail( "SOCKET: Sent Auth Response (unknown account)." );
273        return;
274    }
275
276    Field* fields = result->Fetch();
277
278    tbc = fields[8].GetUInt8() && sWorld.getConfig(CONFIG_EXPANSION) > 0;
279
280    N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7");
281    g.SetDword(7);
282    I.SetHexStr(fields[5].GetString());
283
284    //In case of leading zeros in the I hash, restore them
285    uint8 mDigest[SHA_DIGEST_LENGTH];
286    memset(mDigest,0,SHA_DIGEST_LENGTH);
287    if (I.GetNumBytes() <= SHA_DIGEST_LENGTH)
288        memcpy(mDigest,I.AsByteArray(),I.GetNumBytes());
289
290    std::reverse(mDigest,mDigest+SHA_DIGEST_LENGTH);
291
292    s.SetHexStr(fields[7].GetString());
293    sha1.UpdateData(s.AsByteArray(), s.GetNumBytes());
294    sha1.UpdateData(mDigest, SHA_DIGEST_LENGTH);
295    sha1.Finalize();
296    x.SetBinary(sha1.GetDigest(), sha1.GetLength());
297    v = g.ModExp(x, N);
298
299    const char* sStr = s.AsHexStr();                        //Must be freed by OPENSSL_free()
300    const char* vStr = v.AsHexStr();                        //Must be freed by OPENSSL_free()
301    const char* vold = fields[6].GetString();
302    sLog.outDebug("SOCKET: (s,v) check s: %s v_old: %s v_new: %s", sStr, vold, vStr );
303    loginDatabase.PExecute("UPDATE account SET v = '0', s = '0' WHERE username = '%s'", safe_account.c_str());
304    if ( !vold || strcmp( vStr, vold ) )
305    {
306        packet.Initialize( SMSG_AUTH_RESPONSE, 1 );
307        packet << uint8( AUTH_UNKNOWN_ACCOUNT );
308        SendPacket( &packet );
309        sLog.outDetail( "SOCKET: User not logged.");
310        delete result;
311        OPENSSL_free((void*)sStr);
312        OPENSSL_free((void*)vStr);
313        return;
314    }
315    OPENSSL_free((void*)sStr);
316    OPENSSL_free((void*)vStr);
317
318    ///- Re-check ip locking (same check as in realmd).
319    if(fields[4].GetUInt8() == 1)                           // if ip is locked
320    {
321        if ( strcmp(fields[3].GetString(),GetRemoteAddress().c_str()) )
322        {
323            packet.Initialize( SMSG_AUTH_RESPONSE, 1 );
324            packet << uint8( AUTH_FAILED );
325            SendPacket( &packet );
326
327            sLog.outDetail( "SOCKET: Sent Auth Response (Account IP differs)." );
328            delete result;
329            return;
330        }
331    }
332
333    id = fields[0].GetUInt32();
334    security = fields[1].GetUInt16();
335    K.SetHexStr(fields[2].GetString());
336    time_t mutetime = time_t(fields[9].GetUInt64());
337
338    LocaleConstant locale = LocaleConstant(fields[10].GetUInt8());
339    if (locale>=MAX_LOCALE)
340        locale=LOCALE_enUS;
341
342    delete result;
343
344    ///- Re-check account ban (same check as in realmd) /// TO DO: why on earth do 2 checks for same thing?
345    QueryResult *banresult = loginDatabase.PQuery("SELECT bandate,unbandate FROM account_banned WHERE id = '%u' AND active = 1", id);
346    if(banresult)                                           // if account banned
347    {
348        packet.Initialize( SMSG_AUTH_RESPONSE, 1 );
349        packet << uint8( AUTH_BANNED );
350        SendPacket( &packet );
351
352        sLog.outDetail( "SOCKET: Sent Auth Response (Account banned)." );
353        delete banresult;
354        return;
355    }
356
357    ///- Check locked state for server
358    AccountTypes allowedAccountType = sWorld.GetPlayerSecurityLimit();
359    if( allowedAccountType > SEC_PLAYER && security < allowedAccountType)
360    {
361        WorldPacket Packet(SMSG_AUTH_RESPONSE, 1);
362        Packet << uint8(AUTH_UNAVAILABLE);
363        SendPacket(&Packet);
364        return;
365    }
366
367    ///- kick already loaded player with same account (if any) and remove session
368    ///- if player is in loading and want to load again, return
369    if(!sWorld.RemoveSession(id))
370    {
371        return;
372    }
373
374    ///- Check that Key and account name are the same on client and server
375    Sha1Hash sha;
376
377    uint32 t = 0;
378    uint32 seed = _seed;
379
380    sha.UpdateData(account);
381    sha.UpdateData((uint8 *)&t, 4);
382    sha.UpdateData((uint8 *)&clientSeed, 4);
383    sha.UpdateData((uint8 *)&seed, 4);
384    sha.UpdateBigNumbers(&K, NULL);
385    sha.Finalize();
386
387    if (memcmp(sha.GetDigest(), digest, 20))
388    {
389        packet.Initialize( SMSG_AUTH_RESPONSE, 1 );
390        packet << uint8( AUTH_FAILED );
391        SendPacket( &packet );
392
393        sLog.outDetail( "SOCKET: Sent Auth Response (authentication failed)." );
394        return;
395    }
396
397    ///- Initialize the encryption with the Key
398    _crypt.SetKey(&K);
399    _crypt.Init();
400
401    ///- Send 'Auth is ok'
402    packet.Initialize( SMSG_AUTH_RESPONSE, 1+4+1+4+1 );
403    packet << uint8( AUTH_OK );
404    packet << uint32(0);                                    // unknown random value...
405    packet << uint8(0);                                     // can be 0 and 2
406    packet << uint32(0);                                    // const 0
407    packet << uint8(tbc ? 1 : 0);                           // 0 - normal, 1 - TBC, must be set in database manually for each account
408    SendPacket(&packet);
409
410    ///- Create a new WorldSession for the player and add it to the World
411    _session = new WorldSession(id, this,security,tbc,mutetime,locale);
412    sWorld.AddSession(_session);
413
414    if(sLog.IsOutDebug())                                   // optimize disabled debug output
415    {
416        sLog.outDebug( "SOCKET: Client '%s' authenticated successfully.", account.c_str() );
417        sLog.outDebug( "Account: '%s' Logged in from IP %s.", account.c_str(), GetRemoteAddress().c_str());
418    }
419
420    ///- Update the last_ip in the database
421    //No SQL injection, username escaped.
422    std::string address = GetRemoteAddress();
423    loginDatabase.escape_string(address);
424    loginDatabase.PExecute("UPDATE account SET last_ip = '%s' WHERE username = '%s'",address.c_str(), safe_account.c_str());
425
426    // do small delay (10ms) at accepting successful authed connection to prevent dropping packets by client
427    // don't must harm anyone (let login ~100 accounts in 1 sec ;) )
428    #ifdef WIN32
429    Sleep(10);
430    #else
431    ZThread::Thread::sleep(10);
432    #endif
433
434    ///- Check that we do not exceed the maximum number of online players in the realm
435    uint32 Sessions  = sWorld.GetActiveAndQueuedSessionCount();
436    uint32 pLimit    = sWorld.GetPlayerAmountLimit();
437    uint32 QueueSize = sWorld.GetQueueSize();               //number of players in the queue
438    bool   inQueue   = false;
439    --Sessions;                                             //so we don't count the user trying to login as a session and queue the socket that we are using
440
441    if( pLimit >  0 && Sessions >= pLimit && security == SEC_PLAYER )
442    {
443        sWorld.AddQueuedPlayer(this);
444        SendAuthWaitQue(sWorld.GetQueuePos(this));
445        sWorld.UpdateMaxSessionCounters();
446        sLog.outDetail( "PlayerQueue: %s is in Queue Position (%u).",safe_account.c_str(),++QueueSize);
447        inQueue = true;
448    }
449
450    ///- Create and send the Addon packet
451    if(sAddOnHandler.BuildAddonPacket(&recvPacket, &SendAddonPacked))
452        SendPacket(&SendAddonPacked);
453
454    if(inQueue)
455        return;
456
457    sWorld.UpdateMaxSessionCounters();
458
459    // Updates the population
460    if (pLimit > 0)
461    {
462        float popu = sWorld.GetActiveSessionCount();        //updated number of users on the server
463        popu /= pLimit;
464        popu *= 2;
465        loginDatabase.PExecute("UPDATE realmlist SET population = '%f' WHERE id = '%d'",popu,realmID);
466        sLog.outDetail( "Server Population (%f).",popu);
467    }
468
469    return;
470}
471
472/// Handle the Ping packet
473void WorldSocket::_HandlePing(WorldPacket& recvPacket)
474{
475    uint32 ping;
476    uint32 latency;
477
478    CHECK_PACKET_SIZE(recvPacket,8);
479
480    ///- Get the ping packet content
481    recvPacket >> ping;
482    recvPacket >> latency;
483
484    if (_session )
485        _session->SetLatency(latency);
486
487    ///- check ping speed for players
488    if(_session && _session->GetSecurity() == SEC_PLAYER)
489    {
490        uint32 cur_mstime = getMSTime();
491
492        // can overflow and start from 0
493        uint32 diff_mstime = getMSTimeDiff(m_LastPingMSTime,cur_mstime);
494        m_LastPingMSTime = cur_mstime;
495        if(diff_mstime < 27000)                             // should be 30000 (=30 secs), add little tolerance
496        {
497            ++m_OverSpeedPings;
498
499            uint32 max_count = sWorld.getConfig(CONFIG_MAX_OVERSPEED_PINGS);
500            if(max_count && m_OverSpeedPings > max_count)
501            {
502                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());
503                _session->KickPlayer();
504                return;
505            }
506        }
507        else
508            m_OverSpeedPings = 0;
509
510    }
511
512    ///- And put the pong answer in the to-be-sent queue
513    WorldPacket packet( SMSG_PONG, 4 );
514    packet << ping;
515    SendPacket(&packet);
516
517    return;
518}
519
520/// Handle the update order for the socket
521void WorldSocket::SendSinglePacket()
522{
523    WorldPacket *packet;
524    ServerPktHeader hdr;
525
526    ///- If we have packet to send
527    if (!_sendQueue.empty())
528    {
529        packet = _sendQueue.next();
530
531        hdr.size = ntohs((uint16)packet->size() + 2);
532        hdr.cmd = packet->GetOpcode();
533
534        if( sWorldLog.LogWorld() )
535        {
536            sWorldLog.Log("SERVER:\nSOCKET: %u\nLENGTH: %u\nOPCODE: %s (0x%.4X)\nDATA:\n",
537                (uint32)GetSocket(),
538                packet->size(),
539                LookupOpcodeName(packet->GetOpcode()),
540                packet->GetOpcode());
541
542            uint32 p = 0;
543            while (p < packet->size())
544            {
545                for (uint32 j = 0; j < 16 && p < packet->size(); j++)
546                    sWorldLog.Log("%.2X ", (*packet)[p++]);
547
548                sWorldLog.Log("\n");
549            }
550
551            sWorldLog.Log("\n\n");
552        }
553
554        ///- Encrypt (if needed) the header
555        _crypt.EncryptSend((uint8*)&hdr, 4);
556
557        ///- Send the header and body to the client
558        TcpSocket::SendBuf((char*)&hdr, 4);
559        if(!packet->empty()) TcpSocket::SendBuf((char*)packet->contents(), packet->size());
560
561        delete packet;
562    }
563}
564
565void WorldSocket::Update(time_t diff)
566{
567    const uint32 SEND_PACKETS_MAX = 100;
568    const uint32 SEND_BUFFER_SIZE = 1024;
569
570    uint8 sendBuffer[SEND_BUFFER_SIZE];
571
572    while (!_sendQueue.empty())
573    {
574        bool haveBigPacket = false;
575        uint32 bufferSize = 0;
576
577        ///- While we have packets to send
578        for (uint32 packetCount = 0; (packetCount < SEND_PACKETS_MAX) && !_sendQueue.empty(); packetCount++)
579        {
580            ServerPktHeader *hdr = (ServerPktHeader*)&sendBuffer[bufferSize];
581
582            // check merge possibility.
583            WorldPacket *front = _sendQueue.front();
584            uint32 packetSize = front->size();
585
586            if ((sizeof(*hdr) + packetSize) > SEND_BUFFER_SIZE)
587            {
588                haveBigPacket = true;
589                break;
590            }
591
592            if ((bufferSize + sizeof(*hdr) + packetSize) > sizeof(sendBuffer))
593                break;
594
595            // can be merged
596            WorldPacket *packet = _sendQueue.next();
597
598            hdr->size = ntohs((uint16)packetSize + 2);
599            hdr->cmd = packet->GetOpcode();
600
601            if( sWorldLog.LogWorld() )
602            {
603                sWorldLog.Log("SERVER:\nSOCKET: %u\nLENGTH: %u\nOPCODE: %s (0x%.4X)\nDATA:\n",
604                    (uint32)GetSocket(),
605                    packetSize,
606                    LookupOpcodeName(packet->GetOpcode()),
607                    packet->GetOpcode());
608
609                uint32 p = 0;
610                while (p < packetSize)
611                {
612                    for (uint32 j = 0; j < 16 && p < packetSize; j++)
613                        sWorldLog.Log("%.2X ", (*packet)[p++]);
614
615                    sWorldLog.Log("\n");
616                }
617
618                sWorldLog.Log("\n\n");
619            }
620
621            ///- Encrypt (if needed) the header
622            _crypt.EncryptSend((uint8*)hdr, sizeof(*hdr));
623            bufferSize += sizeof(*hdr);
624
625            if (packetSize)
626            {
627                memcpy(&sendBuffer[bufferSize], packet->contents(), packetSize);
628                bufferSize += packetSize;
629            }
630
631            ///- Send the header and body to the client
632            delete packet;
633        }
634
635        // send merged packets
636        if (bufferSize) TcpSocket::SendBuf((char*)sendBuffer, bufferSize);
637        // send too big non-merged packet
638        if (haveBigPacket) SendSinglePacket();
639    }
640}
641
642/// Handle the authentication waiting queue (to be completed)
643void WorldSocket::SendAuthWaitQue(uint32 position)
644{
645    if(position == 0)
646    {
647        WorldPacket packet( SMSG_AUTH_RESPONSE, 1 );
648        packet << uint8( AUTH_OK );
649        SendPacket(&packet);
650    }
651    else
652    {
653        WorldPacket packet( SMSG_AUTH_RESPONSE, 5 );
654        packet << uint8( AUTH_WAIT_QUEUE );
655        packet << uint32 (position);                        //amount of players in queue
656        SendPacket(&packet);
657    }
658}
659
660void WorldSocket::SizeError(WorldPacket const& packet, uint32 size) const
661{
662    sLog.outError("Client send packet %s (%u) with size %u but expected %u (attempt crash server?), skipped",
663        LookupOpcodeName(packet.GetOpcode()),packet.GetOpcode(),packet.size(),size);
664}
Note: See TracBrowser for help on using the browser.