2 * Copyright (C) 2005-2010 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
24 #include "Database/DatabaseEnv.h"
25 #include "Config/ConfigEnv.h"
27 #include "RealmList.h"
28 #include "AuthSocket.h"
29 #include "AuthCodes.h"
30 #include <openssl/md5.h>
31 //#include "Util.h" -- for commented utf8ToUpperOnlyLatin
33 extern DatabaseType loginDatabase
;
35 #define ChunkSize 2048
39 AUTH_LOGON_CHALLENGE
= 0x00,
40 AUTH_LOGON_PROOF
= 0x01,
41 AUTH_RECONNECT_CHALLENGE
= 0x02,
42 AUTH_RECONNECT_PROOF
= 0x03,
58 // 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
59 #if defined( __GNUC__ )
65 typedef struct AUTH_LOGON_CHALLENGE_C
82 } sAuthLogonChallenge_C
;
84 //typedef sAuthLogonChallenge_C sAuthReconnectChallenge_C;
98 } sAuthLogonChallenge_S;
101 typedef struct AUTH_LOGON_PROOF_C
107 uint8 number_of_keys
;
108 uint8 securityFlags
; // 0x00-0x04
117 } sAuthLogonProofKey_C;
119 typedef struct AUTH_LOGON_PROOF_S
129 typedef struct AUTH_LOGON_PROOF_S_BUILD_6005
137 } sAuthLogonProof_S_BUILD_6005
;
139 typedef struct AUTH_RECONNECT_PROOF_C
145 uint8 number_of_keys
;
146 } sAuthReconnectProof_C
;
148 typedef struct XFER_INIT
150 uint8 cmd
; // XFER_INITIATE
151 uint8 fileNameLen
; // strlen(fileName);
152 uint8 fileName
[5]; // fileName[fileNameLen]
153 uint64 file_size
; // file size (bytes)
154 uint8 md5
[MD5_DIGEST_LENGTH
]; // MD5
157 typedef struct XFER_DATA
161 uint8 data
[ChunkSize
];
164 typedef struct AuthHandler
168 bool (AuthSocket::*handler
)(void);
171 // GCC have alternative #pragma pack() syntax and old gcc version not support pack(pop), also any gcc version not support it at some paltform
172 #if defined( __GNUC__ )
178 /// Launch a thread to transfer a patch to the client
179 class PatcherRunnable
: public ACE_Based::Runnable
182 PatcherRunnable(class AuthSocket
*);
186 AuthSocket
* mySocket
;
189 typedef struct PATCH_INFO
191 uint8 md5
[MD5_DIGEST_LENGTH
];
194 /// Caches MD5 hash of client patches present on the server
198 typedef std::map
<std::string
, PATCH_INFO
*> Patches
;
201 Patches::const_iterator
begin() const { return _patches
.begin(); }
202 Patches::const_iterator
end() const { return _patches
.end(); }
203 void LoadPatchMD5(char*);
204 bool GetHash(char * pat
,uint8 mymd5
[16]);
207 void LoadPatchesInfo();
211 const AuthHandler table
[] =
213 { AUTH_LOGON_CHALLENGE
, STATUS_CONNECTED
, &AuthSocket::_HandleLogonChallenge
},
214 { AUTH_LOGON_PROOF
, STATUS_CONNECTED
, &AuthSocket::_HandleLogonProof
},
215 { AUTH_RECONNECT_CHALLENGE
, STATUS_CONNECTED
, &AuthSocket::_HandleReconnectChallenge
},
216 { AUTH_RECONNECT_PROOF
, STATUS_CONNECTED
, &AuthSocket::_HandleReconnectProof
},
217 { REALM_LIST
, STATUS_AUTHED
, &AuthSocket::_HandleRealmList
},
218 { XFER_ACCEPT
, STATUS_CONNECTED
, &AuthSocket::_HandleXferAccept
},
219 { XFER_RESUME
, STATUS_CONNECTED
, &AuthSocket::_HandleXferResume
},
220 { XFER_CANCEL
, STATUS_CONNECTED
, &AuthSocket::_HandleXferCancel
}
223 #define AUTH_TOTAL_COMMANDS sizeof(table)/sizeof(AuthHandler)
225 ///Holds the MD5 hash of client patches present on the server
226 Patcher PatchesCache
;
228 /// Constructor - set the N and g values for SRP6
229 AuthSocket::AuthSocket(ISocketHandler
&h
) : TcpSocket(h
)
231 N
.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7");
236 _accountSecurityLevel
= SEC_PLAYER
;
239 /// Close patch file descriptor before leaving
240 AuthSocket::~AuthSocket()
242 ACE_Guard
<ACE_Thread_Mutex
> g(patcherLock
);
248 /// Accept the connection and set the s random value for SRP6
249 void AuthSocket::OnAccept()
251 sLog
.outBasic("Accepting connection from '%s:%d'",
252 GetRemoteAddress().c_str(), GetRemotePort());
256 /// Read the packet from the client
257 void AuthSocket::OnRead()
264 if (!ibuf
.GetLength())
267 ///- Get the command out of it
268 ibuf
.SoftRead((char *)&_cmd
, 1); // UQ1: No longer exists in new net code ???
272 ///- Circle through known commands and call the correct command handler
273 for (i
= 0; i
< AUTH_TOTAL_COMMANDS
; ++i
)
275 if ((uint8
)table
[i
].cmd
== _cmd
&&
276 (table
[i
].status
== STATUS_CONNECTED
||
277 (_authed
&& table
[i
].status
== STATUS_AUTHED
)))
279 DEBUG_LOG("[Auth] got data for cmd %u ibuf length %u", (uint32
)_cmd
, ibuf
.GetLength());
281 if (!(*this.*table
[i
].handler
)())
283 DEBUG_LOG("Command handler failed for cmd %u ibuf length %u", (uint32
)_cmd
, ibuf
.GetLength());
290 ///- Report unknown commands in the debug log
291 if (i
== AUTH_TOTAL_COMMANDS
)
293 DEBUG_LOG("[Auth] got unknown packet %u", (uint32
)_cmd
);
299 /// Make the SRP6 calculation from hash in dB
300 void AuthSocket::_SetVSFields(const std::string
& rI
)
302 s
.SetRand(s_BYTE_SIZE
* 8);
305 I
.SetHexStr(rI
.c_str());
307 // In case of leading zeros in the rI hash, restore them
308 uint8 mDigest
[SHA_DIGEST_LENGTH
];
309 memset(mDigest
, 0, SHA_DIGEST_LENGTH
);
310 if (I
.GetNumBytes() <= SHA_DIGEST_LENGTH
)
311 memcpy(mDigest
, I
.AsByteArray(), I
.GetNumBytes());
313 std::reverse(mDigest
, mDigest
+ SHA_DIGEST_LENGTH
);
316 sha
.UpdateData(s
.AsByteArray(), s
.GetNumBytes());
317 sha
.UpdateData(mDigest
, SHA_DIGEST_LENGTH
);
320 x
.SetBinary(sha
.GetDigest(), sha
.GetLength());
322 // No SQL injection (username escaped)
323 const char *v_hex
, *s_hex
;
324 v_hex
= v
.AsHexStr();
325 s_hex
= s
.AsHexStr();
326 loginDatabase
.PExecute("UPDATE account SET v = '%s', s = '%s' WHERE username = '%s'", v_hex
, s_hex
, _safelogin
.c_str() );
327 OPENSSL_free((void*)v_hex
);
328 OPENSSL_free((void*)s_hex
);
331 void AuthSocket::SendProof(Sha1Hash sha
)
338 sAuthLogonProof_S_BUILD_6005 proof
;
339 memcpy(proof
.M2
, sha
.GetDigest(), 20);
340 proof
.cmd
= AUTH_LOGON_PROOF
;
344 SendBuf((char *)&proof
, sizeof(proof
));
348 case 10505: // 3.2.2a
349 case 11159: // 3.3.0a
352 sAuthLogonProof_S proof
;
353 memcpy(proof
.M2
, sha
.GetDigest(), 20);
354 proof
.cmd
= AUTH_LOGON_PROOF
;
356 proof
.unk1
= 0x00800000;
360 SendBuf((char *)&proof
, sizeof(proof
));
366 /// Logon Challenge command handler
367 bool AuthSocket::_HandleLogonChallenge()
369 DEBUG_LOG("Entering _HandleLogonChallenge");
370 if (ibuf
.GetLength() < sizeof(sAuthLogonChallenge_C
))
373 ///- Read the first 4 bytes (header) to get the length of the remaining of the packet
374 std::vector
<uint8
> buf
;
377 ibuf
.Read((char *)&buf
[0], 4);
379 EndianConvert(*((uint16
*)(buf
[0])));
380 uint16 remaining
= ((sAuthLogonChallenge_C
*)&buf
[0])->size
;
381 DEBUG_LOG("[AuthChallenge] got header, body is %#04x bytes", remaining
);
383 if ((remaining
< sizeof(sAuthLogonChallenge_C
) - buf
.size()) || (ibuf
.GetLength() < remaining
))
386 //No big fear of memory outage (size is int16, i.e. < 65536)
387 buf
.resize(remaining
+ buf
.size() + 1);
388 buf
[buf
.size() - 1] = 0;
389 sAuthLogonChallenge_C
*ch
= (sAuthLogonChallenge_C
*)&buf
[0];
391 ///- Read the remaining of the packet
392 ibuf
.Read((char *)&buf
[4], remaining
);
393 DEBUG_LOG("[AuthChallenge] got full packet, %#04x bytes", ch
->size
);
394 DEBUG_LOG("[AuthChallenge] name(%d): '%s'", ch
->I_len
, ch
->I
);
396 // BigEndian code, nop in little endian case
397 // size already converted
398 EndianConvert(*((uint32
*)(&ch
->gamename
[0])));
399 EndianConvert(ch
->build
);
400 EndianConvert(*((uint32
*)(&ch
->platform
[0])));
401 EndianConvert(*((uint32
*)(&ch
->os
[0])));
402 EndianConvert(*((uint32
*)(&ch
->country
[0])));
403 EndianConvert(ch
->timezone_bias
);
404 EndianConvert(ch
->ip
);
408 _login
= (const char*)ch
->I
;
411 ///- Normalize account name
412 //utf8ToUpperOnlyLatin(_login); -- client already send account in expected form
414 //Escape the user login to avoid further SQL injection
415 //Memory will be freed on AuthSocket object destruction
417 loginDatabase
.escape_string(_safelogin
);
419 pkt
<< (uint8
) AUTH_LOGON_CHALLENGE
;
422 ///- Verify that this IP is not in the ip_banned table
423 // No SQL injection possible (paste the IP address as passed by the socket)
424 std::string address
= GetRemoteAddress();
425 loginDatabase
.escape_string(address
);
426 QueryResult
*result
= loginDatabase
.PQuery("SELECT unbandate FROM ip_banned WHERE "
427 // permanent still banned
428 "(unbandate = bandate OR unbandate > UNIX_TIMESTAMP()) AND ip = '%s'", address
.c_str());
431 pkt
<< (uint8
)REALM_AUTH_ACCOUNT_BANNED
;
432 sLog
.outBasic("[AuthChallenge] Banned ip %s tries to login!", GetRemoteAddress().c_str());
437 ///- Get the account details from the account table
438 // No SQL injection (escaped user name)
440 result
= loginDatabase
.PQuery("SELECT sha_pass_hash,id,locked,last_ip,gmlevel,v,s FROM account WHERE username = '%s'",_safelogin
.c_str ());
443 ///- If the IP is 'locked', check that the player comes indeed from the correct IP address
445 if((*result
)[2].GetUInt8() == 1) // if ip is locked
447 DEBUG_LOG("[AuthChallenge] Account '%s' is locked to IP - '%s'", _login
.c_str(), (*result
)[3].GetString());
448 DEBUG_LOG("[AuthChallenge] Player address is '%s'", GetRemoteAddress().c_str());
449 if ( strcmp((*result
)[3].GetString(),GetRemoteAddress().c_str()) )
451 DEBUG_LOG("[AuthChallenge] Account IP differs");
452 pkt
<< (uint8
) REALM_AUTH_ACCOUNT_FREEZED
;
457 DEBUG_LOG("[AuthChallenge] Account IP matches");
462 DEBUG_LOG("[AuthChallenge] Account '%s' is not locked to ip", _login
.c_str());
467 ///- If the account is banned, reject the logon attempt
468 QueryResult
*banresult
= loginDatabase
.PQuery("SELECT bandate,unbandate FROM account_banned WHERE "
469 "id = %u AND active = 1 AND (unbandate > UNIX_TIMESTAMP() OR unbandate = bandate)", (*result
)[1].GetUInt32());
472 if((*banresult
)[0].GetUInt64() != (*banresult
)[1].GetUInt64())
474 pkt
<< (uint8
) REALM_AUTH_ACCOUNT_BANNED
;
475 sLog
.outBasic("[AuthChallenge] Banned account %s tries to login!",_login
.c_str ());
479 pkt
<< (uint8
) REALM_AUTH_ACCOUNT_FREEZED
;
480 sLog
.outBasic("[AuthChallenge] Temporarily banned account %s tries to login!",_login
.c_str ());
487 ///- Get the password from the account table, upper it, and make the SRP6 calculation
488 std::string rI
= (*result
)[0].GetCppString();
490 ///- Don't calculate (v, s) if there are already some in the database
491 std::string databaseV
= (*result
)[5].GetCppString();
492 std::string databaseS
= (*result
)[6].GetCppString();
494 sLog
.outDebug("database authentication values: v='%s' s='%s'", databaseV
.c_str(), databaseS
.c_str());
496 // multiply with 2, bytes are stored as hexstring
497 if(databaseV
.size() != s_BYTE_SIZE
*2 || databaseS
.size() != s_BYTE_SIZE
*2)
501 s
.SetHexStr(databaseS
.c_str());
502 v
.SetHexStr(databaseV
.c_str());
506 BigNumber gmod
= g
.ModExp(b
, N
);
507 B
= ((v
* 3) + gmod
) % N
;
509 ASSERT(gmod
.GetNumBytes() <= 32);
512 unk3
.SetRand(16 * 8);
514 ///- Fill the response packet with the result
515 pkt
<< uint8(REALM_AUTH_SUCCESS
);
517 // B may be calculated < 32B so we force minimal length to 32B
518 pkt
.append(B
.AsByteArray(32), 32); // 32 bytes
520 pkt
.append(g
.AsByteArray(), 1);
522 pkt
.append(N
.AsByteArray(32), 32);
523 pkt
.append(s
.AsByteArray(), s
.GetNumBytes());// 32 bytes
524 pkt
.append(unk3
.AsByteArray(16), 16);
525 uint8 securityFlags
= 0;
526 pkt
<< uint8(securityFlags
); // security flags (0x0...0x04)
528 if(securityFlags
& 0x01) // PIN input
531 pkt
<< uint64(0) << uint64(0); // 16 bytes hash?
534 if(securityFlags
& 0x02) // Matrix input
543 if(securityFlags
& 0x04) // Security token input
548 uint8 secLevel
= (*result
)[4].GetUInt8();
549 _accountSecurityLevel
= secLevel
<= SEC_ADMINISTRATOR
? AccountTypes(secLevel
) : SEC_ADMINISTRATOR
;
551 _localizationName
.resize(4);
552 for(int i
= 0; i
< 4; ++i
)
553 _localizationName
[i
] = ch
->country
[4-i
-1];
555 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
));
562 pkt
<< (uint8
) REALM_AUTH_NO_MATCH
;
565 SendBuf((char const*)pkt
.contents(), pkt
.size());
569 /// Logon Proof command handler
570 bool AuthSocket::_HandleLogonProof()
572 DEBUG_LOG("Entering _HandleLogonProof");
574 if (ibuf
.GetLength() < sizeof(sAuthLogonProof_C
))
576 sAuthLogonProof_C lp
;
577 ibuf
.Read((char *)&lp
, sizeof(sAuthLogonProof_C
));
579 ///- Check if the client has one of the expected version numbers
580 bool valid_version
= false;
581 int accepted_versions
[] = EXPECTED_REALMD_CLIENT_BUILD
;
582 if (_build
>= accepted_versions
[0]) // first build is low bound of always accepted range
583 valid_version
= true;
586 // continue from 1 with explict equal check
587 for(int i
= 1; accepted_versions
[i
]; ++i
)
589 if(_build
== accepted_versions
[i
])
591 valid_version
= true;
597 /// <ul><li> If the client has no valid version
600 ///- Check if we have the apropriate patch on the disk
602 // 24 = len("./patches/65535enGB.mpq")+1
604 // No buffer overflow (fixed length of arguments)
605 sprintf(tmp
, "./patches/%d%s.mpq", _build
, _localizationName
.c_str());
606 // This will be closed at the destruction of the AuthSocket (client disconnection)
607 FILE *pFile
= fopen(tmp
, "rb");
612 pkt
<< (uint8
) AUTH_LOGON_CHALLENGE
;
614 pkt
<< (uint8
) REALM_AUTH_WRONG_BUILD_NUMBER
;
615 DEBUG_LOG("[AuthChallenge] %u is not a valid client version!", _build
);
616 DEBUG_LOG("[AuthChallenge] Patch %s not found", tmp
);
617 SendBuf((char const*)pkt
.contents(), pkt
.size());
625 ///- Get the MD5 hash of the patch file (get it from preloaded Patcher cache or calculate it)
626 if(PatchesCache
.GetHash(tmp
, (uint8
*)&xferh
.md5
))
628 DEBUG_LOG("\n[AuthChallenge] Found precached patch info for patch %s", tmp
);
631 { // calculate patch md5
632 printf("\n[AuthChallenge] Patch info for %s was not cached.", tmp
);
633 PatchesCache
.LoadPatchMD5(tmp
);
634 PatchesCache
.GetHash(tmp
, (uint8
*)&xferh
.md5
);
637 ///- Send a packet to the client with the file length and MD5 hash
638 uint8 data
[2] = { AUTH_LOGON_PROOF
, REALM_AUTH_UPDATE_CLIENT
};
639 SendBuf((const char*)data
, sizeof(data
));
641 memcpy(&xferh
, "0\x05Patch", 7);
642 xferh
.cmd
= XFER_INITIATE
;
643 fseek(pPatch
, 0, SEEK_END
);
644 xferh
.file_size
= ftell(pPatch
);
646 SendBuf((const char*)&xferh
, sizeof(xferh
));
652 ///- Continue the SRP6 calculation based on data received from the client
655 A
.SetBinary(lp
.A
, 32);
657 // SRP safeguard: abort if A==0
662 sha
.UpdateBigNumbers(&A
, &B
, NULL
);
665 u
.SetBinary(sha
.GetDigest(), 20);
666 BigNumber S
= (A
* (v
.ModExp(u
, N
))).ModExp(b
, N
);
671 memcpy(t
, S
.AsByteArray(32), 32);
672 for (int i
= 0; i
< 16; ++i
)
677 sha
.UpdateData(t1
, 16);
679 for (int i
= 0; i
< 20; ++i
)
681 vK
[i
* 2] = sha
.GetDigest()[i
];
683 for (int i
= 0; i
< 16; ++i
)
685 t1
[i
] = t
[i
* 2 + 1];
688 sha
.UpdateData(t1
, 16);
690 for (int i
= 0; i
< 20; ++i
)
692 vK
[i
* 2 + 1] = sha
.GetDigest()[i
];
699 sha
.UpdateBigNumbers(&N
, NULL
);
701 memcpy(hash
, sha
.GetDigest(), 20);
703 sha
.UpdateBigNumbers(&g
, NULL
);
705 for (int i
= 0; i
< 20; ++i
)
707 hash
[i
] ^= sha
.GetDigest()[i
];
710 t3
.SetBinary(hash
, 20);
713 sha
.UpdateData(_login
);
715 uint8 t4
[SHA_DIGEST_LENGTH
];
716 memcpy(t4
, sha
.GetDigest(), SHA_DIGEST_LENGTH
);
719 sha
.UpdateBigNumbers(&t3
, NULL
);
720 sha
.UpdateData(t4
, SHA_DIGEST_LENGTH
);
721 sha
.UpdateBigNumbers(&s
, &A
, &B
, &K
, NULL
);
724 M
.SetBinary(sha
.GetDigest(), 20);
726 ///- Check if SRP6 results match (password is correct), else send an error
727 if (!memcmp(M
.AsByteArray(), lp
.M1
, 20))
729 sLog
.outBasic("User '%s' successfully authenticated", _login
.c_str());
731 ///- Update the sessionkey, last_ip, last login time and reset number of failed logins in the account table for this account
732 // No SQL injection (escaped user name) and IP address as received by socket
733 const char* K_hex
= K
.AsHexStr();
734 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() );
735 OPENSSL_free((void*)K_hex
);
737 ///- Finish SRP6 and send the final result to the client
739 sha
.UpdateBigNumbers(&A
, &M
, &K
, NULL
);
744 ///- Set _authed to true!
749 char data
[4]= { AUTH_LOGON_PROOF
, REALM_AUTH_NO_MATCH
, 3, 0};
750 SendBuf(data
, sizeof(data
));
751 sLog
.outBasic("[AuthChallenge] account %s tried to login with wrong password!",_login
.c_str ());
753 uint32 MaxWrongPassCount
= sConfig
.GetIntDefault("WrongPass.MaxCount", 0);
754 if(MaxWrongPassCount
> 0)
756 //Increment number of failed logins by one and if it reaches the limit temporarily ban that account or IP
757 loginDatabase
.PExecute("UPDATE account SET failed_logins = failed_logins + 1 WHERE username = '%s'",_safelogin
.c_str());
759 if(QueryResult
*loginfail
= loginDatabase
.PQuery("SELECT id, failed_logins FROM account WHERE username = '%s'", _safelogin
.c_str()))
761 Field
* fields
= loginfail
->Fetch();
762 uint32 failed_logins
= fields
[1].GetUInt32();
764 if( failed_logins
>= MaxWrongPassCount
)
766 uint32 WrongPassBanTime
= sConfig
.GetIntDefault("WrongPass.BanTime", 600);
767 bool WrongPassBanType
= sConfig
.GetBoolDefault("WrongPass.BanType", false);
771 uint32 acc_id
= fields
[0].GetUInt32();
772 loginDatabase
.PExecute("INSERT INTO account_banned VALUES ('%u',UNIX_TIMESTAMP(),UNIX_TIMESTAMP()+'%u','MaNGOS realmd','Failed login autoban',1)",
773 acc_id
, WrongPassBanTime
);
774 sLog
.outBasic("[AuthChallenge] account %s got banned for '%u' seconds because it failed to authenticate '%u' times",
775 _login
.c_str(), WrongPassBanTime
, failed_logins
);
779 std::string current_ip
= GetRemoteAddress();
780 loginDatabase
.escape_string(current_ip
);
781 loginDatabase
.PExecute("INSERT INTO ip_banned VALUES ('%s',UNIX_TIMESTAMP(),UNIX_TIMESTAMP()+'%u','MaNGOS realmd','Failed login autoban')",
782 current_ip
.c_str(), WrongPassBanTime
);
783 sLog
.outBasic("[AuthChallenge] IP %s got banned for '%u' seconds because account %s failed to authenticate '%u' times",
784 current_ip
.c_str(), WrongPassBanTime
, _login
.c_str(), failed_logins
);
794 /// Reconnect Challenge command handler
795 bool AuthSocket::_HandleReconnectChallenge()
797 DEBUG_LOG("Entering _HandleReconnectChallenge");
798 if (ibuf
.GetLength() < sizeof(sAuthLogonChallenge_C
))
801 ///- Read the first 4 bytes (header) to get the length of the remaining of the packet
802 std::vector
<uint8
> buf
;
805 ibuf
.Read((char *)&buf
[0], 4);
807 EndianConvert(*((uint16
*)(buf
[0])));
808 uint16 remaining
= ((sAuthLogonChallenge_C
*)&buf
[0])->size
;
809 DEBUG_LOG("[ReconnectChallenge] got header, body is %#04x bytes", remaining
);
811 if ((remaining
< sizeof(sAuthLogonChallenge_C
) - buf
.size()) || (ibuf
.GetLength() < remaining
))
814 //No big fear of memory outage (size is int16, i.e. < 65536)
815 buf
.resize(remaining
+ buf
.size() + 1);
816 buf
[buf
.size() - 1] = 0;
817 sAuthLogonChallenge_C
*ch
= (sAuthLogonChallenge_C
*)&buf
[0];
819 ///- Read the remaining of the packet
820 ibuf
.Read((char *)&buf
[4], remaining
);
821 DEBUG_LOG("[ReconnectChallenge] got full packet, %#04x bytes", ch
->size
);
822 DEBUG_LOG("[ReconnectChallenge] name(%d): '%s'", ch
->I_len
, ch
->I
);
824 _login
= (const char*)ch
->I
;
826 loginDatabase
.escape_string(_safelogin
);
828 QueryResult
*result
= loginDatabase
.PQuery ("SELECT sessionkey FROM account WHERE username = '%s'", _safelogin
.c_str ());
830 // Stop if the account is not found
833 sLog
.outError("[ERROR] user %s tried to login and we cannot find his session key in the database.", _login
.c_str());
838 Field
* fields
= result
->Fetch ();
839 K
.SetHexStr (fields
[0].GetString ());
842 ///- Sending response
844 pkt
<< (uint8
) AUTH_RECONNECT_CHALLENGE
;
846 _reconnectProof
.SetRand(16 * 8);
847 pkt
.append(_reconnectProof
.AsByteArray(16),16); // 16 bytes random
848 pkt
<< (uint64
) 0x00 << (uint64
) 0x00; // 16 bytes zeros
849 SendBuf((char const*)pkt
.contents(), pkt
.size());
853 /// Reconnect Proof command handler
854 bool AuthSocket::_HandleReconnectProof()
856 DEBUG_LOG("Entering _HandleReconnectProof");
858 if (ibuf
.GetLength() < sizeof(sAuthReconnectProof_C
))
860 if (_login
.empty() || !_reconnectProof
.GetNumBytes() || !K
.GetNumBytes())
862 sAuthReconnectProof_C lp
;
863 ibuf
.Read((char *)&lp
, sizeof(sAuthReconnectProof_C
));
866 t1
.SetBinary(lp
.R1
, 16);
870 sha
.UpdateData(_login
);
871 sha
.UpdateBigNumbers(&t1
, &_reconnectProof
, &K
, NULL
);
874 if (!memcmp(sha
.GetDigest(), lp
.R2
, SHA_DIGEST_LENGTH
))
876 ///- Sending response
878 pkt
<< (uint8
) AUTH_RECONNECT_PROOF
;
880 pkt
<< (uint16
) 0x00; // 2 bytes zeros
881 SendBuf((char const*)pkt
.contents(), pkt
.size());
883 ///- Set _authed to true!
890 sLog
.outError("[ERROR] user %s tried to login, but session invalid.", _login
.c_str());
896 /// %Realm List command handler
897 bool AuthSocket::_HandleRealmList()
899 DEBUG_LOG("Entering _HandleRealmList");
900 if (ibuf
.GetLength() < 5)
905 ///- Get the user id (else close the connection)
906 // No SQL injection (escaped user name)
908 QueryResult
*result
= loginDatabase
.PQuery("SELECT id,sha_pass_hash FROM account WHERE username = '%s'",_safelogin
.c_str());
911 sLog
.outError("[ERROR] user %s tried to login and we cannot find him in the database.",_login
.c_str());
916 uint32 id
= (*result
)[0].GetUInt32();
917 std::string rI
= (*result
)[1].GetCppString();
920 ///- Update realm list if need
921 sRealmList
.UpdateIfNeed();
923 ///- Circle through realms in the RealmList and construct the return packet (including # of user characters in each realm)
925 LoadRealmlist(pkt
, id
);
928 hdr
<< (uint8
) REALM_LIST
;
929 hdr
<< (uint16
)pkt
.size();
932 SendBuf((char const*)hdr
.contents(), hdr
.size());
937 void AuthSocket::LoadRealmlist(ByteBuffer
&pkt
, uint32 acctid
)
945 pkt
<< uint8(sRealmList
.size());
947 for(RealmList::RealmMap::const_iterator i
= sRealmList
.begin(); i
!= sRealmList
.end(); ++i
)
949 uint8 AmountOfCharacters
;
951 // No SQL injection. id of realm is controlled by the database.
952 QueryResult
*result
= loginDatabase
.PQuery( "SELECT numchars FROM realmcharacters WHERE realmid = '%d' AND acctid='%u'", i
->second
.m_ID
, acctid
);
955 Field
*fields
= result
->Fetch();
956 AmountOfCharacters
= fields
[0].GetUInt8();
960 AmountOfCharacters
= 0;
962 // Show offline state for unsupported client builds
963 uint8 color
= (std::find(i
->second
.realmbuilds
.begin(), i
->second
.realmbuilds
.end(), _build
) != i
->second
.realmbuilds
.end()) ? i
->second
.color
: 2;
964 color
= (i
->second
.allowedSecurityLevel
> _accountSecurityLevel
) ? 2 : color
;
966 pkt
<< uint32(i
->second
.icon
); // realm type
967 pkt
<< uint8(color
); // if 2, then realm is offline
968 pkt
<< i
->first
; // name
969 pkt
<< i
->second
.address
; // address
970 pkt
<< float(i
->second
.populationLevel
);
971 pkt
<< uint8(AmountOfCharacters
);
972 pkt
<< uint8(i
->second
.timezone
); // realm category
973 pkt
<< uint8(0x00); // unk, may be realm number/id?
982 case 10505: // 3.2.2a
983 case 11159: // 3.3.0a
984 default: // and later
987 pkt
<< uint16(sRealmList
.size());
989 for(RealmList::RealmMap::const_iterator i
= sRealmList
.begin(); i
!= sRealmList
.end(); ++i
)
991 uint8 AmountOfCharacters
;
993 // No SQL injection. id of realm is controlled by the database.
994 QueryResult
*result
= loginDatabase
.PQuery( "SELECT numchars FROM realmcharacters WHERE realmid = '%d' AND acctid='%u'", i
->second
.m_ID
, acctid
);
997 Field
*fields
= result
->Fetch();
998 AmountOfCharacters
= fields
[0].GetUInt8();
1002 AmountOfCharacters
= 0;
1004 uint8 lock
= (i
->second
.allowedSecurityLevel
> _accountSecurityLevel
) ? 1 : 0;
1006 // Show offline state for unsupported client builds
1007 uint8 color
= (std::find(i
->second
.realmbuilds
.begin(), i
->second
.realmbuilds
.end(), _build
) != i
->second
.realmbuilds
.end()) ? i
->second
.color
: 2;
1009 pkt
<< uint8(i
->second
.icon
); // realm type
1010 pkt
<< uint8(lock
); // if 1, then realm locked
1011 pkt
<< uint8(color
); // if 2, then realm is offline
1012 pkt
<< i
->first
; // name
1013 pkt
<< i
->second
.address
; // address
1014 pkt
<< float(i
->second
.populationLevel
);
1015 pkt
<< uint8(AmountOfCharacters
);
1016 pkt
<< uint8(i
->second
.timezone
); // realm category
1017 pkt
<< uint8(0x2C); // unk, may be realm number/id?
1027 /// Resume patch transfer
1028 bool AuthSocket::_HandleXferResume()
1030 DEBUG_LOG("Entering _HandleXferResume");
1031 ///- Check packet length and patch existence
1032 if (ibuf
.GetLength() < 9 || !pPatch
)
1034 sLog
.outError("Error while resuming patch transfer (wrong packet)");
1038 ///- Launch a PatcherRunnable thread starting at given patch file offset
1041 ibuf
.Read((char*)&start
,sizeof(start
));
1042 fseek(pPatch
, start
, 0);
1044 ACE_Based::Thread
u(new PatcherRunnable(this));
1048 /// Cancel patch transfer
1049 bool AuthSocket::_HandleXferCancel()
1051 DEBUG_LOG("Entering _HandleXferCancel");
1053 ///- Close and delete the socket
1054 ibuf
.Remove(1); // clear input buffer
1056 SetCloseAndDelete();
1061 /// Accept patch transfer
1062 bool AuthSocket::_HandleXferAccept()
1064 DEBUG_LOG("Entering _HandleXferAccept");
1066 ///- Check packet length and patch existence
1069 sLog
.outError("Error while accepting patch transfer (wrong packet)");
1073 ///- Launch a PatcherRunnable thread, starting at the beginning of the patch file
1074 ibuf
.Remove(1); // clear input buffer
1075 fseek(pPatch
, 0, 0);
1077 ACE_Based::Thread
u(new PatcherRunnable(this));
1081 /// Check if there is lag on the connection to the client
1082 bool AuthSocket::IsLag()
1084 return (TCP_BUFSIZE_READ
-GetOutputLength() < 2 * ChunkSize
);
1087 PatcherRunnable::PatcherRunnable(class AuthSocket
* as
)
1092 /// Send content of patch file to the client
1093 void PatcherRunnable::run()
1095 ACE_Guard
<ACE_Thread_Mutex
> g(mySocket
->patcherLock
);
1097 XFER_DATA_STRUCT xfdata
;
1098 xfdata
.opcode
= XFER_DATA
;
1100 while(!feof(mySocket
->pPatch
) && mySocket
->Ready())
1102 ///- Wait until output buffer is reasonably empty
1103 while(mySocket
->Ready() && mySocket
->IsLag())
1105 ACE_Based::Thread::Sleep(1);
1107 ///- And send content of the patch file to the client
1108 xfdata
.data_size
= fread(&xfdata
.data
, 1, ChunkSize
, mySocket
->pPatch
);
1109 mySocket
->SendBuf((const char*)&xfdata
, xfdata
.data_size
+ (sizeof(XFER_DATA_STRUCT
) - ChunkSize
));
1113 /// Preload MD5 hashes of existing patch files on server
1117 void Patcher::LoadPatchesInfo()
1122 dirp
= opendir("./patches/");
1128 if ((dp
= readdir(dirp
)) != NULL
)
1130 int l
= strlen(dp
->d_name
);
1133 if(!memcmp(&dp
->d_name
[l
-4],".mpq",4))
1134 LoadPatchMD5(dp
->d_name
);
1152 void Patcher::LoadPatchesInfo()
1154 WIN32_FIND_DATA fil
;
1155 HANDLE hFil
=FindFirstFile("./patches/*.mpq", &fil
);
1156 if(hFil
== INVALID_HANDLE_VALUE
)
1157 return; // no patches were found
1161 LoadPatchMD5(fil
.cFileName
);
1163 while(FindNextFile(hFil
, &fil
));
1167 /// Calculate and store MD5 hash for a given patch file
1168 void Patcher::LoadPatchMD5(char * szFileName
)
1170 ///- Try to open the patch file
1171 std::string path
= "./patches/";
1173 FILE *pPatch
= fopen(path
.c_str(), "rb");
1174 sLog
.outDebug("Loading patch info from %s\n", path
.c_str());
1177 sLog
.outError("Error loading patch %s\n", path
.c_str());
1181 ///- Calculate the MD5 hash
1184 uint8
* buf
= new uint8
[512*1024];
1186 while (!feof(pPatch
))
1188 size_t read
= fread(buf
, 1, 512*1024, pPatch
);
1189 MD5_Update(&ctx
, buf
, read
);
1194 ///- Store the result in the internal patch hash map
1195 _patches
[path
] = new PATCH_INFO
;
1196 MD5_Final((uint8
*)&_patches
[path
]->md5
, &ctx
);
1199 /// Get cached MD5 hash for a given patch file
1200 bool Patcher::GetHash(char * pat
, uint8 mymd5
[16])
1202 for( Patches::iterator i
= _patches
.begin(); i
!= _patches
.end(); ++i
)
1203 if(!stricmp(pat
, i
->first
.c_str()))
1205 memcpy(mymd5
, i
->second
->md5
, 16);
1212 /// Launch the patch hashing mechanism on object creation
1218 /// Empty and delete the patch map on termination
1221 for(Patches::iterator i
= _patches
.begin(); i
!= _patches
.end(); ++i
)