[8315] Fixed memory leaks (mostly at server shutdown) and code cleanups.
[getmangos.git] / src / realmd / AuthSocket.cpp
blob5611d85e8a36278a346b92a3c1645677fb967e66
1 /*
2 * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
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.
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.
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
19 /** \file
20 \ingroup realmd
23 #include "Common.h"
24 #include "Database/DatabaseEnv.h"
25 #include "ByteBuffer.h"
26 #include "Config/ConfigEnv.h"
27 #include "Log.h"
28 #include "RealmList.h"
29 #include "AuthSocket.h"
30 #include "AuthCodes.h"
31 #include <openssl/md5.h>
32 #include "Auth/Sha1.h"
33 //#include "Util.h" -- for commented utf8ToUpperOnlyLatin
35 extern RealmList m_realmList;
37 extern DatabaseType loginDatabase;
39 #define ChunkSize 2048
41 enum eAuthCmd
43 //AUTH_NO_CMD = 0xFF,
44 AUTH_LOGON_CHALLENGE = 0x00,
45 AUTH_LOGON_PROOF = 0x01,
46 AUTH_RECONNECT_CHALLENGE = 0x02,
47 AUTH_RECONNECT_PROOF = 0x03,
48 //update srv =4
49 REALM_LIST = 0x10,
50 XFER_INITIATE = 0x30,
51 XFER_DATA = 0x31,
52 XFER_ACCEPT = 0x32,
53 XFER_RESUME = 0x33,
54 XFER_CANCEL = 0x34
57 enum eStatus
59 STATUS_CONNECTED = 0,
60 STATUS_AUTHED
63 // GCC have alternative #pragma pack(N) syntax and old gcc version not support pack(push,N), also any gcc version not support it at some paltform
64 #if defined( __GNUC__ )
65 #pragma pack(1)
66 #else
67 #pragma pack(push,1)
68 #endif
70 typedef struct AUTH_LOGON_CHALLENGE_C
72 uint8 cmd;
73 uint8 error;
74 uint16 size;
75 uint8 gamename[4];
76 uint8 version1;
77 uint8 version2;
78 uint8 version3;
79 uint16 build;
80 uint8 platform[4];
81 uint8 os[4];
82 uint8 country[4];
83 uint32 timezone_bias;
84 uint32 ip;
85 uint8 I_len;
86 uint8 I[1];
87 } sAuthLogonChallenge_C;
89 //typedef sAuthLogonChallenge_C sAuthReconnectChallenge_C;
91 typedef struct
93 uint8 cmd;
94 uint8 error;
95 uint8 unk2;
96 uint8 B[32];
97 uint8 g_len;
98 uint8 g[1];
99 uint8 N_len;
100 uint8 N[32];
101 uint8 s[32];
102 uint8 unk3[16];
103 } sAuthLogonChallenge_S;
106 typedef struct AUTH_LOGON_PROOF_C
108 uint8 cmd;
109 uint8 A[32];
110 uint8 M1[20];
111 uint8 crc_hash[20];
112 uint8 number_of_keys;
113 uint8 securityFlags; // 0x00-0x04
114 } sAuthLogonProof_C;
116 typedef struct
118 uint16 unk1;
119 uint32 unk2;
120 uint8 unk3[4];
121 uint16 unk4[20];
122 } sAuthLogonProofKey_C;
124 typedef struct AUTH_LOGON_PROOF_S
126 uint8 cmd;
127 uint8 error;
128 uint8 M2[20];
129 uint32 unk1;
130 uint32 unk2;
131 uint16 unk3;
132 } sAuthLogonProof_S;
134 typedef struct AUTH_RECONNECT_PROOF_C
136 uint8 cmd;
137 uint8 R1[16];
138 uint8 R2[20];
139 uint8 R3[20];
140 uint8 number_of_keys;
141 } sAuthReconnectProof_C;
143 typedef struct XFER_INIT
145 uint8 cmd; // XFER_INITIATE
146 uint8 fileNameLen; // strlen(fileName);
147 uint8 fileName[5]; // fileName[fileNameLen]
148 uint64 file_size; // file size (bytes)
149 uint8 md5[MD5_DIGEST_LENGTH]; // MD5
150 }XFER_INIT;
152 typedef struct XFER_DATA
154 uint8 opcode;
155 uint16 data_size;
156 uint8 data[ChunkSize];
157 }XFER_DATA_STRUCT;
159 typedef struct AuthHandler
161 eAuthCmd cmd;
162 uint32 status;
163 bool (AuthSocket::*handler)(void);
164 }AuthHandler;
166 // GCC have alternative #pragma pack() syntax and old gcc version not support pack(pop), also any gcc version not support it at some paltform
167 #if defined( __GNUC__ )
168 #pragma pack()
169 #else
170 #pragma pack(pop)
171 #endif
173 /// Launch a thread to transfer a patch to the client
174 class PatcherRunnable: public ACE_Based::Runnable
176 public:
177 PatcherRunnable(class AuthSocket *);
178 void run();
180 private:
181 AuthSocket * mySocket;
184 typedef struct PATCH_INFO
186 uint8 md5[MD5_DIGEST_LENGTH];
187 }PATCH_INFO;
189 /// Caches MD5 hash of client patches present on the server
190 class Patcher
192 public:
193 typedef std::map<std::string, PATCH_INFO*> Patches;
194 ~Patcher();
195 Patcher();
196 Patches::const_iterator begin() const { return _patches.begin(); }
197 Patches::const_iterator end() const { return _patches.end(); }
198 void LoadPatchMD5(char*);
199 bool GetHash(char * pat,uint8 mymd5[16]);
201 private:
202 void LoadPatchesInfo();
203 Patches _patches;
206 const AuthHandler table[] =
208 { AUTH_LOGON_CHALLENGE, STATUS_CONNECTED, &AuthSocket::_HandleLogonChallenge },
209 { AUTH_LOGON_PROOF, STATUS_CONNECTED, &AuthSocket::_HandleLogonProof },
210 { AUTH_RECONNECT_CHALLENGE, STATUS_CONNECTED, &AuthSocket::_HandleReconnectChallenge},
211 { AUTH_RECONNECT_PROOF, STATUS_CONNECTED, &AuthSocket::_HandleReconnectProof },
212 { REALM_LIST, STATUS_AUTHED, &AuthSocket::_HandleRealmList },
213 { XFER_ACCEPT, STATUS_CONNECTED, &AuthSocket::_HandleXferAccept },
214 { XFER_RESUME, STATUS_CONNECTED, &AuthSocket::_HandleXferResume },
215 { XFER_CANCEL, STATUS_CONNECTED, &AuthSocket::_HandleXferCancel }
218 #define AUTH_TOTAL_COMMANDS sizeof(table)/sizeof(AuthHandler)
220 ///Holds the MD5 hash of client patches present on the server
221 Patcher PatchesCache;
223 /// Constructor - set the N and g values for SRP6
224 AuthSocket::AuthSocket(ISocketHandler &h) : TcpSocket(h)
226 N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7");
227 g.SetDword(7);
228 _authed = false;
229 pPatch = NULL;
231 _accountSecurityLevel = SEC_PLAYER;
234 /// Close patch file descriptor before leaving
235 AuthSocket::~AuthSocket()
237 ACE_Guard<ACE_Thread_Mutex> g(patcherLock);
239 if(pPatch)
240 fclose(pPatch);
243 /// Accept the connection and set the s random value for SRP6
244 void AuthSocket::OnAccept()
246 sLog.outBasic("Accepting connection from '%s:%d'",
247 GetRemoteAddress().c_str(), GetRemotePort());
249 s.SetRand(s_BYTE_SIZE * 8);
252 /// Read the packet from the client
253 void AuthSocket::OnRead()
255 ///- Read the packet
256 TcpSocket::OnRead();
257 uint8 _cmd;
258 while (1)
260 if (!ibuf.GetLength())
261 return;
263 ///- Get the command out of it
264 ibuf.SoftRead((char *)&_cmd, 1); // UQ1: No longer exists in new net code ???
266 size_t i;
268 ///- Circle through known commands and call the correct command handler
269 for (i = 0; i < AUTH_TOTAL_COMMANDS; ++i)
271 if ((uint8)table[i].cmd == _cmd &&
272 (table[i].status == STATUS_CONNECTED ||
273 (_authed && table[i].status == STATUS_AUTHED)))
275 DEBUG_LOG("[Auth] got data for cmd %u ibuf length %u", (uint32)_cmd, ibuf.GetLength());
277 if (!(*this.*table[i].handler)())
279 DEBUG_LOG("Command handler failed for cmd %u ibuf length %u", (uint32)_cmd, ibuf.GetLength());
280 return;
282 break;
286 ///- Report unknown commands in the debug log
287 if (i == AUTH_TOTAL_COMMANDS)
289 DEBUG_LOG("[Auth] got unknown packet %u", (uint32)_cmd);
290 return;
295 /// Make the SRP6 calculation from hash in dB
296 void AuthSocket::_SetVSFields(const std::string& rI)
298 BigNumber I;
299 I.SetHexStr(rI.c_str());
301 // In case of leading zeros in the rI hash, restore them
302 uint8 mDigest[SHA_DIGEST_LENGTH];
303 memset(mDigest, 0, SHA_DIGEST_LENGTH);
304 if (I.GetNumBytes() <= SHA_DIGEST_LENGTH)
305 memcpy(mDigest, I.AsByteArray(), I.GetNumBytes());
307 std::reverse(mDigest, mDigest + SHA_DIGEST_LENGTH);
309 Sha1Hash sha;
310 sha.UpdateData(s.AsByteArray(), s.GetNumBytes());
311 sha.UpdateData(mDigest, SHA_DIGEST_LENGTH);
312 sha.Finalize();
313 BigNumber x;
314 x.SetBinary(sha.GetDigest(), sha.GetLength());
315 v = g.ModExp(x, N);
316 // No SQL injection (username escaped)
317 const char *v_hex, *s_hex;
318 v_hex = v.AsHexStr();
319 s_hex = s.AsHexStr();
320 loginDatabase.PExecute("UPDATE account SET v = '%s', s = '%s' WHERE username = '%s'", v_hex, s_hex, _safelogin.c_str() );
321 OPENSSL_free((void*)v_hex);
322 OPENSSL_free((void*)s_hex);
325 /// Logon Challenge command handler
326 bool AuthSocket::_HandleLogonChallenge()
328 DEBUG_LOG("Entering _HandleLogonChallenge");
329 if (ibuf.GetLength() < sizeof(sAuthLogonChallenge_C))
330 return false;
332 ///- Read the first 4 bytes (header) to get the length of the remaining of the packet
333 std::vector<uint8> buf;
334 buf.resize(4);
336 ibuf.Read((char *)&buf[0], 4);
338 EndianConvert(*((uint16*)(buf[0])));
339 uint16 remaining = ((sAuthLogonChallenge_C *)&buf[0])->size;
340 DEBUG_LOG("[AuthChallenge] got header, body is %#04x bytes", remaining);
342 if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (ibuf.GetLength() < remaining))
343 return false;
345 //No big fear of memory outage (size is int16, i.e. < 65536)
346 buf.resize(remaining + buf.size() + 1);
347 buf[buf.size() - 1] = 0;
348 sAuthLogonChallenge_C *ch = (sAuthLogonChallenge_C*)&buf[0];
350 // BigEndian code, nop in little endian case
351 // size already converted
352 EndianConvert(*((uint32*)(&ch->gamename[0])));
353 EndianConvert(ch->build);
354 EndianConvert(*((uint32*)(&ch->platform[0])));
355 EndianConvert(*((uint32*)(&ch->os[0])));
356 EndianConvert(*((uint32*)(&ch->country[0])));
357 EndianConvert(ch->timezone_bias);
358 EndianConvert(ch->ip);
360 ///- Read the remaining of the packet
361 ibuf.Read((char *)&buf[4], remaining);
362 DEBUG_LOG("[AuthChallenge] got full packet, %#04x bytes", ch->size);
363 DEBUG_LOG("[AuthChallenge] name(%d): '%s'", ch->I_len, ch->I);
365 ByteBuffer pkt;
367 _login = (const char*)ch->I;
368 _build = ch->build;
370 ///- Normalize account name
371 //utf8ToUpperOnlyLatin(_login); -- client already send account in expected form
373 //Escape the user login to avoid further SQL injection
374 //Memory will be freed on AuthSocket object destruction
375 _safelogin = _login;
376 loginDatabase.escape_string(_safelogin);
378 pkt << (uint8) AUTH_LOGON_CHALLENGE;
379 pkt << (uint8) 0x00;
381 ///- Verify that this IP is not in the ip_banned table
382 // No SQL injection possible (paste the IP address as passed by the socket)
383 loginDatabase.Execute("DELETE FROM ip_banned WHERE unbandate<=UNIX_TIMESTAMP() AND unbandate<>bandate");
385 std::string address = GetRemoteAddress();
386 loginDatabase.escape_string(address);
387 QueryResult *result = loginDatabase.PQuery( "SELECT * FROM ip_banned WHERE ip = '%s'",address.c_str());
388 if(result)
390 pkt << (uint8)REALM_AUTH_ACCOUNT_BANNED;
391 sLog.outBasic("[AuthChallenge] Banned ip %s tries to login!",GetRemoteAddress().c_str ());
392 delete result;
394 else
396 ///- Get the account details from the account table
397 // No SQL injection (escaped user name)
399 result = loginDatabase.PQuery("SELECT sha_pass_hash,id,locked,last_ip,gmlevel FROM account WHERE username = '%s'",_safelogin.c_str ());
400 if( result )
402 ///- If the IP is 'locked', check that the player comes indeed from the correct IP address
403 bool locked = false;
404 if((*result)[2].GetUInt8() == 1) // if ip is locked
406 DEBUG_LOG("[AuthChallenge] Account '%s' is locked to IP - '%s'", _login.c_str(), (*result)[3].GetString());
407 DEBUG_LOG("[AuthChallenge] Player address is '%s'", GetRemoteAddress().c_str());
408 if ( strcmp((*result)[3].GetString(),GetRemoteAddress().c_str()) )
410 DEBUG_LOG("[AuthChallenge] Account IP differs");
411 pkt << (uint8) REALM_AUTH_ACCOUNT_FREEZED;
412 locked=true;
414 else
416 DEBUG_LOG("[AuthChallenge] Account IP matches");
419 else
421 DEBUG_LOG("[AuthChallenge] Account '%s' is not locked to ip", _login.c_str());
424 if (!locked)
426 //set expired bans to inactive
427 loginDatabase.Execute("UPDATE account_banned SET active = 0 WHERE unbandate<=UNIX_TIMESTAMP() AND unbandate<>bandate");
428 ///- If the account is banned, reject the logon attempt
429 QueryResult *banresult = loginDatabase.PQuery("SELECT bandate,unbandate FROM account_banned WHERE id = %u AND active = 1", (*result)[1].GetUInt32());
430 if(banresult)
432 if((*banresult)[0].GetUInt64() == (*banresult)[1].GetUInt64())
434 pkt << (uint8) REALM_AUTH_ACCOUNT_BANNED;
435 sLog.outBasic("[AuthChallenge] Banned account %s tries to login!",_login.c_str ());
437 else
439 pkt << (uint8) REALM_AUTH_ACCOUNT_FREEZED;
440 sLog.outBasic("[AuthChallenge] Temporarily banned account %s tries to login!",_login.c_str ());
443 delete banresult;
445 else
447 ///- Get the password from the account table, upper it, and make the SRP6 calculation
448 std::string rI = (*result)[0].GetCppString();
449 _SetVSFields(rI);
451 b.SetRand(19 * 8);
452 BigNumber gmod = g.ModExp(b, N);
453 B = ((v * 3) + gmod) % N;
455 ASSERT(gmod.GetNumBytes() <= 32);
457 BigNumber unk3;
458 unk3.SetRand(16 * 8);
460 ///- Fill the response packet with the result
461 pkt << uint8(REALM_AUTH_SUCCESS);
463 // B may be calculated < 32B so we force minimal length to 32B
464 pkt.append(B.AsByteArray(32), 32); // 32 bytes
465 pkt << uint8(1);
466 pkt.append(g.AsByteArray(), 1);
467 pkt << uint8(32);
468 pkt.append(N.AsByteArray(), 32);
469 pkt.append(s.AsByteArray(), s.GetNumBytes());// 32 bytes
470 pkt.append(unk3.AsByteArray(), 16);
471 uint8 securityFlags = 0;
472 pkt << uint8(securityFlags); // security flags (0x0...0x04)
474 if(securityFlags & 0x01) // PIN input
476 pkt << uint32(0);
477 pkt << uint64(0) << uint64(0); // 16 bytes hash?
480 if(securityFlags & 0x02) // Matrix input
482 pkt << uint8(0);
483 pkt << uint8(0);
484 pkt << uint8(0);
485 pkt << uint8(0);
486 pkt << uint64(0);
489 if(securityFlags & 0x04) // Security token input
491 pkt << uint8(1);
494 uint8 secLevel = (*result)[4].GetUInt8();
495 _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes(secLevel) : SEC_ADMINISTRATOR;
497 _localizationName.resize(4);
498 for(int i = 0; i < 4; ++i)
499 _localizationName[i] = ch->country[4-i-1];
501 sLog.outBasic("[AuthChallenge] account %s is using '%c%c%c%c' locale (%u)", _login.c_str (), ch->country[3], ch->country[2], ch->country[1], ch->country[0], GetLocaleByName(_localizationName));
504 delete result;
506 else // no account
508 pkt<< (uint8) REALM_AUTH_NO_MATCH;
511 SendBuf((char const*)pkt.contents(), pkt.size());
512 return true;
515 /// Logon Proof command handler
516 bool AuthSocket::_HandleLogonProof()
518 DEBUG_LOG("Entering _HandleLogonProof");
519 ///- Read the packet
520 if (ibuf.GetLength() < sizeof(sAuthLogonProof_C))
521 return false;
522 sAuthLogonProof_C lp;
523 ibuf.Read((char *)&lp, sizeof(sAuthLogonProof_C));
525 ///- Check if the client has one of the expected version numbers
526 bool valid_version = false;
527 int accepted_versions[] = EXPECTED_MANGOS_CLIENT_BUILD;
528 for(int i = 0; accepted_versions[i]; ++i)
530 if(_build == accepted_versions[i])
532 valid_version = true;
533 break;
537 /// <ul><li> If the client has no valid version
538 if(!valid_version)
540 ///- Check if we have the apropriate patch on the disk
542 // 24 = len("./patches/65535enGB.mpq")+1
543 char tmp[24];
544 // No buffer overflow (fixed length of arguments)
545 sprintf(tmp, "./patches/%d%s.mpq", _build, _localizationName.c_str());
546 // This will be closed at the destruction of the AuthSocket (client disconnection)
547 FILE *pFile = fopen(tmp, "rb");
549 if(!pFile)
551 ByteBuffer pkt;
552 pkt << (uint8) AUTH_LOGON_CHALLENGE;
553 pkt << (uint8) 0x00;
554 pkt << (uint8) REALM_AUTH_WRONG_BUILD_NUMBER;
555 DEBUG_LOG("[AuthChallenge] %u is not a valid client version!", _build);
556 DEBUG_LOG("[AuthChallenge] Patch %s not found", tmp);
557 SendBuf((char const*)pkt.contents(), pkt.size());
558 return true;
560 else // have patch
562 pPatch = pFile;
563 XFER_INIT xferh;
565 ///- Get the MD5 hash of the patch file (get it from preloaded Patcher cache or calculate it)
566 if(PatchesCache.GetHash(tmp, (uint8*)&xferh.md5))
568 DEBUG_LOG("\n[AuthChallenge] Found precached patch info for patch %s", tmp);
570 else
571 { // calculate patch md5
572 printf("\n[AuthChallenge] Patch info for %s was not cached.", tmp);
573 PatchesCache.LoadPatchMD5(tmp);
574 PatchesCache.GetHash(tmp, (uint8*)&xferh.md5);
577 ///- Send a packet to the client with the file length and MD5 hash
578 uint8 data[2] = { AUTH_LOGON_PROOF, REALM_AUTH_UPDATE_CLIENT };
579 SendBuf((const char*)data, sizeof(data));
581 memcpy(&xferh, "0\x05Patch", 7);
582 xferh.cmd = XFER_INITIATE;
583 fseek(pPatch, 0, SEEK_END);
584 xferh.file_size = ftell(pPatch);
586 SendBuf((const char*)&xferh, sizeof(xferh));
587 return true;
590 /// </ul>
592 ///- Continue the SRP6 calculation based on data received from the client
593 BigNumber A;
594 A.SetBinary(lp.A, 32);
596 Sha1Hash sha;
597 sha.UpdateBigNumbers(&A, &B, NULL);
598 sha.Finalize();
599 BigNumber u;
600 u.SetBinary(sha.GetDigest(), 20);
601 BigNumber S = (A * (v.ModExp(u, N))).ModExp(b, N);
603 uint8 t[32];
604 uint8 t1[16];
605 uint8 vK[40];
606 memcpy(t, S.AsByteArray(), 32);
607 for (int i = 0; i < 16; ++i)
609 t1[i] = t[i * 2];
611 sha.Initialize();
612 sha.UpdateData(t1, 16);
613 sha.Finalize();
614 for (int i = 0; i < 20; ++i)
616 vK[i * 2] = sha.GetDigest()[i];
618 for (int i = 0; i < 16; ++i)
620 t1[i] = t[i * 2 + 1];
622 sha.Initialize();
623 sha.UpdateData(t1, 16);
624 sha.Finalize();
625 for (int i = 0; i < 20; ++i)
627 vK[i * 2 + 1] = sha.GetDigest()[i];
629 K.SetBinary(vK, 40);
631 uint8 hash[20];
633 sha.Initialize();
634 sha.UpdateBigNumbers(&N, NULL);
635 sha.Finalize();
636 memcpy(hash, sha.GetDigest(), 20);
637 sha.Initialize();
638 sha.UpdateBigNumbers(&g, NULL);
639 sha.Finalize();
640 for (int i = 0; i < 20; ++i)
642 hash[i] ^= sha.GetDigest()[i];
644 BigNumber t3;
645 t3.SetBinary(hash, 20);
647 sha.Initialize();
648 sha.UpdateData(_login);
649 sha.Finalize();
650 uint8 t4[SHA_DIGEST_LENGTH];
651 memcpy(t4, sha.GetDigest(), SHA_DIGEST_LENGTH);
653 sha.Initialize();
654 sha.UpdateBigNumbers(&t3, NULL);
655 sha.UpdateData(t4, SHA_DIGEST_LENGTH);
656 sha.UpdateBigNumbers(&s, &A, &B, &K, NULL);
657 sha.Finalize();
658 BigNumber M;
659 M.SetBinary(sha.GetDigest(), 20);
661 ///- Check if SRP6 results match (password is correct), else send an error
662 if (!memcmp(M.AsByteArray(), lp.M1, 20))
664 sLog.outBasic("User '%s' successfully authenticated", _login.c_str());
666 ///- Update the sessionkey, last_ip, last login time and reset number of failed logins in the account table for this account
667 // No SQL injection (escaped user name) and IP address as received by socket
668 const char* K_hex = K.AsHexStr();
669 loginDatabase.PExecute("UPDATE account SET sessionkey = '%s', last_ip = '%s', last_login = NOW(), locale = '%u', failed_logins = 0 WHERE username = '%s'", K_hex, GetRemoteAddress().c_str(), GetLocaleByName(_localizationName), _safelogin.c_str() );
670 OPENSSL_free((void*)K_hex);
672 ///- Finish SRP6 and send the final result to the client
673 sha.Initialize();
674 sha.UpdateBigNumbers(&A, &M, &K, NULL);
675 sha.Finalize();
677 sAuthLogonProof_S proof;
678 memcpy(proof.M2, sha.GetDigest(), 20);
679 proof.cmd = AUTH_LOGON_PROOF;
680 proof.error = 0;
681 proof.unk1 = 0x00800000;
682 proof.unk2 = 0x00;
683 proof.unk3 = 0x00;
685 SendBuf((char *)&proof, sizeof(proof));
687 ///- Set _authed to true!
688 _authed = true;
690 else
692 char data[4]= { AUTH_LOGON_PROOF, REALM_AUTH_NO_MATCH, 3, 0};
693 SendBuf(data, sizeof(data));
694 sLog.outBasic("[AuthChallenge] account %s tried to login with wrong password!",_login.c_str ());
696 uint32 MaxWrongPassCount = sConfig.GetIntDefault("WrongPass.MaxCount", 0);
697 if(MaxWrongPassCount > 0)
699 //Increment number of failed logins by one and if it reaches the limit temporarily ban that account or IP
700 loginDatabase.PExecute("UPDATE account SET failed_logins = failed_logins + 1 WHERE username = '%s'",_safelogin.c_str());
702 if(QueryResult *loginfail = loginDatabase.PQuery("SELECT id, failed_logins FROM account WHERE username = '%s'", _safelogin.c_str()))
704 Field* fields = loginfail->Fetch();
705 uint32 failed_logins = fields[1].GetUInt32();
707 if( failed_logins >= MaxWrongPassCount )
709 uint32 WrongPassBanTime = sConfig.GetIntDefault("WrongPass.BanTime", 600);
710 bool WrongPassBanType = sConfig.GetBoolDefault("WrongPass.BanType", false);
712 if(WrongPassBanType)
714 uint32 acc_id = fields[0].GetUInt32();
715 loginDatabase.PExecute("INSERT INTO account_banned VALUES ('%u',UNIX_TIMESTAMP(),UNIX_TIMESTAMP()+'%u','MaNGOS realmd','Failed login autoban',1)",
716 acc_id, WrongPassBanTime);
717 sLog.outBasic("[AuthChallenge] account %s got banned for '%u' seconds because it failed to authenticate '%u' times",
718 _login.c_str(), WrongPassBanTime, failed_logins);
720 else
722 std::string current_ip = GetRemoteAddress();
723 loginDatabase.escape_string(current_ip);
724 loginDatabase.PExecute("INSERT INTO ip_banned VALUES ('%s',UNIX_TIMESTAMP(),UNIX_TIMESTAMP()+'%u','MaNGOS realmd','Failed login autoban')",
725 current_ip.c_str(), WrongPassBanTime);
726 sLog.outBasic("[AuthChallenge] IP %s got banned for '%u' seconds because account %s failed to authenticate '%u' times",
727 current_ip.c_str(), WrongPassBanTime, _login.c_str(), failed_logins);
730 delete loginfail;
734 return true;
737 /// Reconnect Challenge command handler
738 bool AuthSocket::_HandleReconnectChallenge()
740 DEBUG_LOG("Entering _HandleReconnectChallenge");
741 if (ibuf.GetLength() < sizeof(sAuthLogonChallenge_C))
742 return false;
744 ///- Read the first 4 bytes (header) to get the length of the remaining of the packet
745 std::vector<uint8> buf;
746 buf.resize(4);
748 ibuf.Read((char *)&buf[0], 4);
750 EndianConvert(*((uint16*)(buf[0])));
751 uint16 remaining = ((sAuthLogonChallenge_C *)&buf[0])->size;
752 DEBUG_LOG("[ReconnectChallenge] got header, body is %#04x bytes", remaining);
754 if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (ibuf.GetLength() < remaining))
755 return false;
757 //No big fear of memory outage (size is int16, i.e. < 65536)
758 buf.resize(remaining + buf.size() + 1);
759 buf[buf.size() - 1] = 0;
760 sAuthLogonChallenge_C *ch = (sAuthLogonChallenge_C*)&buf[0];
762 ///- Read the remaining of the packet
763 ibuf.Read((char *)&buf[4], remaining);
764 DEBUG_LOG("[ReconnectChallenge] got full packet, %#04x bytes", ch->size);
765 DEBUG_LOG("[ReconnectChallenge] name(%d): '%s'", ch->I_len, ch->I);
767 _login = (const char*)ch->I;
768 _safelogin = _login;
770 QueryResult *result = loginDatabase.PQuery ("SELECT sessionkey FROM account WHERE username = '%s'", _safelogin.c_str ());
772 // Stop if the account is not found
773 if (!result)
775 sLog.outError("[ERROR] user %s tried to login and we cannot find his session key in the database.", _login.c_str());
776 SetCloseAndDelete();
777 return false;
780 Field* fields = result->Fetch ();
781 K.SetHexStr (fields[0].GetString ());
782 delete result;
784 ///- Sending response
785 ByteBuffer pkt;
786 pkt << (uint8) AUTH_RECONNECT_CHALLENGE;
787 pkt << (uint8) 0x00;
788 _reconnectProof.SetRand(16 * 8);
789 pkt.append(_reconnectProof.AsByteBuffer()); // 16 bytes random
790 pkt << (uint64) 0x00 << (uint64) 0x00; // 16 bytes zeros
791 SendBuf((char const*)pkt.contents(), pkt.size());
792 return true;
795 /// Reconnect Proof command handler
796 bool AuthSocket::_HandleReconnectProof()
798 DEBUG_LOG("Entering _HandleReconnectProof");
799 ///- Read the packet
800 if (ibuf.GetLength() < sizeof(sAuthReconnectProof_C))
801 return false;
802 if (_login.empty() || !_reconnectProof.GetNumBytes() || !K.GetNumBytes())
803 return false;
804 sAuthReconnectProof_C lp;
805 ibuf.Read((char *)&lp, sizeof(sAuthReconnectProof_C));
807 BigNumber t1;
808 t1.SetBinary(lp.R1, 16);
810 Sha1Hash sha;
811 sha.Initialize();
812 sha.UpdateData(_login);
813 sha.UpdateBigNumbers(&t1, &_reconnectProof, &K, NULL);
814 sha.Finalize();
816 if (!memcmp(sha.GetDigest(), lp.R2, SHA_DIGEST_LENGTH))
818 ///- Sending response
819 ByteBuffer pkt;
820 pkt << (uint8) AUTH_RECONNECT_PROOF;
821 pkt << (uint8) 0x00;
822 pkt << (uint16) 0x00; // 2 bytes zeros
823 SendBuf((char const*)pkt.contents(), pkt.size());
825 ///- Set _authed to true!
826 _authed = true;
828 return true;
830 else
832 sLog.outError("[ERROR] user %s tried to login, but session invalid.", _login.c_str());
833 SetCloseAndDelete();
834 return false;
838 /// %Realm List command handler
839 bool AuthSocket::_HandleRealmList()
841 DEBUG_LOG("Entering _HandleRealmList");
842 if (ibuf.GetLength() < 5)
843 return false;
845 ibuf.Remove(5);
847 ///- Get the user id (else close the connection)
848 // No SQL injection (escaped user name)
850 QueryResult *result = loginDatabase.PQuery("SELECT id,sha_pass_hash FROM account WHERE username = '%s'",_safelogin.c_str());
851 if(!result)
853 sLog.outError("[ERROR] user %s tried to login and we cannot find him in the database.",_login.c_str());
854 SetCloseAndDelete();
855 return false;
858 uint32 id = (*result)[0].GetUInt32();
859 std::string rI = (*result)[1].GetCppString();
860 delete result;
862 ///- Update realm list if need
863 m_realmList.UpdateIfNeed();
865 ///- Circle through realms in the RealmList and construct the return packet (including # of user characters in each realm)
866 ByteBuffer pkt;
867 pkt << (uint32) 0;
868 pkt << (uint16) m_realmList.size();
869 RealmList::RealmMap::const_iterator i;
870 for( i = m_realmList.begin(); i != m_realmList.end(); ++i )
872 uint8 AmountOfCharacters;
874 // No SQL injection. id of realm is controlled by the database.
875 result = loginDatabase.PQuery( "SELECT numchars FROM realmcharacters WHERE realmid = '%d' AND acctid='%u'",i->second.m_ID,id);
876 if( result )
878 Field *fields = result->Fetch();
879 AmountOfCharacters = fields[0].GetUInt8();
880 delete result;
882 else
883 AmountOfCharacters = 0;
885 uint8 lock = (i->second.allowedSecurityLevel > _accountSecurityLevel) ? 1 : 0;
887 pkt << i->second.icon; // realm type
888 pkt << lock; // if 1, then realm locked
889 pkt << i->second.color; // if 2, then realm is offline
890 pkt << i->first;
891 pkt << i->second.address;
892 pkt << i->second.populationLevel;
893 pkt << AmountOfCharacters;
894 pkt << i->second.timezone; // realm category
895 pkt << (uint8) 0x2C; // unk, may be realm number/id?
897 pkt << (uint8) 0x10;
898 pkt << (uint8) 0x00;
900 ByteBuffer hdr;
901 hdr << (uint8) REALM_LIST;
902 hdr << (uint16)pkt.size();
903 hdr.append(pkt);
905 SendBuf((char const*)hdr.contents(), hdr.size());
907 // Set check field before possible relogin to realm
908 _SetVSFields(rI);
909 return true;
912 /// Resume patch transfer
913 bool AuthSocket::_HandleXferResume()
915 DEBUG_LOG("Entering _HandleXferResume");
916 ///- Check packet length and patch existence
917 if (ibuf.GetLength() < 9 || !pPatch)
919 sLog.outError("Error while resuming patch transfer (wrong packet)");
920 return false;
923 ///- Launch a PatcherRunnable thread starting at given patch file offset
924 uint64 start;
925 ibuf.Remove(1);
926 ibuf.Read((char*)&start,sizeof(start));
927 fseek(pPatch, start, 0);
929 ACE_Based::Thread u(*new PatcherRunnable(this));
930 return true;
933 /// Cancel patch transfer
934 bool AuthSocket::_HandleXferCancel()
936 DEBUG_LOG("Entering _HandleXferCancel");
938 ///- Close and delete the socket
939 ibuf.Remove(1); // clear input buffer
941 SetCloseAndDelete();
943 return true;
946 /// Accept patch transfer
947 bool AuthSocket::_HandleXferAccept()
949 DEBUG_LOG("Entering _HandleXferAccept");
951 ///- Check packet length and patch existence
952 if (!pPatch)
954 sLog.outError("Error while accepting patch transfer (wrong packet)");
955 return false;
958 ///- Launch a PatcherRunnable thread, starting at the beginning of the patch file
959 ibuf.Remove(1); // clear input buffer
960 fseek(pPatch, 0, 0);
962 ACE_Based::Thread u(*new PatcherRunnable(this));
963 return true;
966 /// Check if there is lag on the connection to the client
967 bool AuthSocket::IsLag()
969 return (TCP_BUFSIZE_READ-GetOutputLength() < 2 * ChunkSize);
972 PatcherRunnable::PatcherRunnable(class AuthSocket * as)
974 mySocket = as;
977 /// Send content of patch file to the client
978 void PatcherRunnable::run()
980 ACE_Guard<ACE_Thread_Mutex> g(mySocket->patcherLock);
982 XFER_DATA_STRUCT xfdata;
983 xfdata.opcode = XFER_DATA;
985 while(!feof(mySocket->pPatch) && mySocket->Ready())
987 ///- Wait until output buffer is reasonably empty
988 while(mySocket->Ready() && mySocket->IsLag())
990 ACE_Based::Thread::Sleep(1);
992 ///- And send content of the patch file to the client
993 xfdata.data_size = fread(&xfdata.data, 1, ChunkSize, mySocket->pPatch);
994 mySocket->SendBuf((const char*)&xfdata, xfdata.data_size + (sizeof(XFER_DATA_STRUCT) - ChunkSize));
998 /// Preload MD5 hashes of existing patch files on server
999 #ifndef _WIN32
1000 #include <dirent.h>
1001 #include <errno.h>
1002 void Patcher::LoadPatchesInfo()
1004 DIR * dirp;
1005 //int errno;
1006 struct dirent * dp;
1007 dirp = opendir("./patches/");
1008 if(!dirp)
1009 return;
1010 while (dirp)
1012 errno = 0;
1013 if ((dp = readdir(dirp)) != NULL)
1015 int l = strlen(dp->d_name);
1016 if(l < 8)
1017 continue;
1018 if(!memcmp(&dp->d_name[l-4],".mpq",4))
1019 LoadPatchMD5(dp->d_name);
1021 else
1023 if(errno != 0)
1025 closedir(dirp);
1026 return;
1028 break;
1032 if(dirp)
1033 closedir(dirp);
1036 #else
1037 void Patcher::LoadPatchesInfo()
1039 WIN32_FIND_DATA fil;
1040 HANDLE hFil=FindFirstFile("./patches/*.mpq", &fil);
1041 if(hFil == INVALID_HANDLE_VALUE)
1042 return; // no patches were found
1046 LoadPatchMD5(fil.cFileName);
1048 while(FindNextFile(hFil, &fil));
1050 #endif
1052 /// Calculate and store MD5 hash for a given patch file
1053 void Patcher::LoadPatchMD5(char * szFileName)
1055 ///- Try to open the patch file
1056 std::string path = "./patches/";
1057 path += szFileName;
1058 FILE *pPatch = fopen(path.c_str(), "rb");
1059 sLog.outDebug("Loading patch info from %s\n", path.c_str());
1060 if(!pPatch)
1062 sLog.outError("Error loading patch %s\n", path.c_str());
1063 return;
1066 ///- Calculate the MD5 hash
1067 MD5_CTX ctx;
1068 MD5_Init(&ctx);
1069 uint8* buf = new uint8[512*1024];
1071 while (!feof(pPatch))
1073 size_t read = fread(buf, 1, 512*1024, pPatch);
1074 MD5_Update(&ctx, buf, read);
1076 delete [] buf;
1077 fclose(pPatch);
1079 ///- Store the result in the internal patch hash map
1080 _patches[path] = new PATCH_INFO;
1081 MD5_Final((uint8 *)&_patches[path]->md5, &ctx);
1084 /// Get cached MD5 hash for a given patch file
1085 bool Patcher::GetHash(char * pat, uint8 mymd5[16])
1087 for( Patches::iterator i = _patches.begin(); i != _patches.end(); ++i )
1088 if(!stricmp(pat, i->first.c_str()))
1090 memcpy(mymd5, i->second->md5, 16);
1091 return true;
1094 return false;
1097 /// Launch the patch hashing mechanism on object creation
1098 Patcher::Patcher()
1100 LoadPatchesInfo();
1103 /// Empty and delete the patch map on termination
1104 Patcher::~Patcher()
1106 for(Patches::iterator i = _patches.begin(); i != _patches.end(); ++i )
1107 delete i->second;