2121*/
2222
2323#include " Common.h"
24+ #include " Auth/HMACSHA1.h"
25+ #include " Auth/base32.h"
2426#include " Database/DatabaseEnv.h"
2527#include " Config/Config.h"
2628#include " Log.h"
2931#include " AuthCodes.h"
3032
3133#include < openssl/md5.h>
34+ #include < ctime>
35+
3236// #include "Util.h" -- for commented utf8ToUpperOnlyLatin
3337
3438extern DatabaseType LoginDatabase;
@@ -40,6 +44,14 @@ enum AccountFlags
4044 ACCOUNT_FLAG_PROPASS = 0x00800000 ,
4145};
4246
47+ enum SecurityFlags
48+ {
49+ SECURITY_FLAG_NONE = 0x00 ,
50+ SECURITY_FLAG_PIN = 0x01 ,
51+ SECURITY_FLAG_UNK = 0x02 ,
52+ SECURITY_FLAG_AUTHENTICATOR = 0x04
53+ };
54+
4355// 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
4456#if defined( __GNUC__ )
4557#pragma pack(1)
@@ -66,6 +78,18 @@ typedef struct AUTH_LOGON_CHALLENGE_C
6678 uint8 I[1 ];
6779} sAuthLogonChallenge_C ;
6880
81+ typedef struct AUTH_LOGON_PIN_DATA_C
82+ {
83+ uint8 salt[16 ];
84+ uint8 hash[20 ];
85+ } sAuthLogonPinData_C ;
86+
87+ typedef struct AUTH_LOGON_AUTHENTICATOR_DATA_C
88+ {
89+ uint8 unk; // Has to be 0x01
90+ uint8 keys[6 ]; // Valid code must be 6 digits
91+ } sAuthLogonAuthenticatorData_C ;
92+
6993// typedef sAuthLogonChallenge_C sAuthReconnectChallenge_C;
7094/*
7195typedef struct
@@ -265,7 +289,7 @@ void AuthSocket::SendProof(Sha1Hash sha)
265289 case 6005 : // 1.12.2
266290 case 6141 : // 1.12.3
267291 {
268- sAuthLogonProof_S_BUILD_6005 proof;
292+ sAuthLogonProof_S_BUILD_6005 proof{} ;
269293 memcpy (proof.M2 , sha.GetDigest (), 20 );
270294 proof.cmd = CMD_AUTH_LOGON_PROOF;
271295 proof.error = 0 ;
@@ -282,7 +306,7 @@ void AuthSocket::SendProof(Sha1Hash sha)
282306 case 12340 : // 3.3.5a
283307 default : // or later
284308 {
285- sAuthLogonProof_S proof;
309+ sAuthLogonProof_S proof{} ;
286310 memcpy (proof.M2 , sha.GetDigest (), 20 );
287311 proof.cmd = CMD_AUTH_LOGON_PROOF;
288312 proof.error = 0 ;
@@ -358,30 +382,35 @@ bool AuthSocket::_HandleLogonChallenge()
358382
359383 // /- Verify that this IP is not in the ip_banned table
360384 // No SQL injection possible (paste the IP address as passed by the socket)
361- QueryResult* result = LoginDatabase.PQuery (" SELECT unbandate FROM ip_banned WHERE "
362- // permanent still banned
363- " (unbandate = bandate OR unbandate > UNIX_TIMESTAMP()) AND ip = '%s'" , m_address.c_str ());
364- if (result)
385+ std::unique_ptr<QueryResult> ip_banned_result (LoginDatabase.PQuery (" SELECT unbandate FROM ip_banned "
386+ " WHERE (unbandate = bandate OR unbandate > UNIX_TIMESTAMP()) AND ip = '%s'" , m_address.c_str ()));
387+
388+ std::unique_ptr<QueryResult> account_banned_result (LoginDatabase.PQuery (
389+ " SELECT ab.unbandate FROM account_banned ab LEFT JOIN account a ON a.id = ab.id "
390+ " WHERE active = 1 AND a.username = '%s' AND (ab.unbandate = ab.bandate OR ab.unbandate > UNIX_TIMESTAMP())" , _safelogin.c_str ()));
391+
392+ if (ip_banned_result || account_banned_result)
365393 {
366394 pkt << (uint8)WOW_FAIL_BANNED;
367395 BASIC_LOG (" [AuthChallenge] Banned ip %s tries to login!" , m_address.c_str ());
368- delete result;
369396 }
370397 else
371398 {
372399 // /- Get the account details from the account table
373400 // No SQL injection (escaped user name)
374401
375- result = LoginDatabase.PQuery (" SELECT sha_pass_hash,id,locked,last_ip,gmlevel,v,s FROM account WHERE username = '%s'" , _safelogin.c_str ());
402+ QueryResult* result = LoginDatabase.PQuery (" SELECT sha_pass_hash,id,locked,last_ip,gmlevel,v,s,token FROM account WHERE username = '%s'" , _safelogin.c_str ());
376403 if (result)
377404 {
405+ Field* fields = result->Fetch ();
406+
378407 // /- If the IP is 'locked', check that the player comes indeed from the correct IP address
379408 bool locked = false ;
380- if ((*result) [2 ].GetUInt8 () == 1 ) // if ip is locked
409+ if (fields [2 ].GetUInt8 () == 1 ) // if ip is locked
381410 {
382- DEBUG_LOG (" [AuthChallenge] Account '%s' is locked to IP - '%s'" , _login.c_str (), (*result) [3 ].GetString ());
411+ DEBUG_LOG (" [AuthChallenge] Account '%s' is locked to IP - '%s'" , _login.c_str (), fields [3 ].GetString ());
383412 DEBUG_LOG (" [AuthChallenge] Player address is '%s'" , m_address.c_str ());
384- if (strcmp ((*result) [3 ].GetString (), m_address.c_str ()))
413+ if (strcmp (fields [3 ].GetString (), m_address.c_str ()))
385414 {
386415 DEBUG_LOG (" [AuthChallenge] Account IP differs" );
387416 pkt << (uint8) WOW_FAIL_SUSPENDED;
@@ -401,7 +430,7 @@ bool AuthSocket::_HandleLogonChallenge()
401430 {
402431 // /- If the account is banned, reject the logon attempt
403432 QueryResult* banresult = LoginDatabase.PQuery (" SELECT bandate,unbandate FROM account_banned WHERE "
404- " id = %u AND active = 1 AND (unbandate > UNIX_TIMESTAMP() OR unbandate = bandate)" , (*result) [1 ].GetUInt32 ());
433+ " id = %u AND active = 1 AND (unbandate > UNIX_TIMESTAMP() OR unbandate = bandate)" , fields [1 ].GetUInt32 ());
405434 if (banresult)
406435 {
407436 if ((*banresult)[0 ].GetUInt64 () == (*banresult)[1 ].GetUInt64 ())
@@ -420,11 +449,11 @@ bool AuthSocket::_HandleLogonChallenge()
420449 else
421450 {
422451 // /- Get the password from the account table, upper it, and make the SRP6 calculation
423- std::string rI = (*result) [0 ].GetCppString ();
452+ std::string rI = fields [0 ].GetCppString ();
424453
425454 // /- Don't calculate (v, s) if there are already some in the database
426- std::string databaseV = (*result) [5 ].GetCppString ();
427- std::string databaseS = (*result) [6 ].GetCppString ();
455+ std::string databaseV = fields [5 ].GetCppString ();
456+ std::string databaseS = fields [6 ].GetCppString ();
428457
429458 DEBUG_LOG (" database authentication values: v='%s' s='%s'" , databaseV.c_str (), databaseS.c_str ());
430459
@@ -458,15 +487,21 @@ bool AuthSocket::_HandleLogonChallenge()
458487 pkt.append (s.AsByteArray (), s.GetNumBytes ());// 32 bytes
459488 pkt.append (unk3.AsByteArray (16 ), 16 );
460489 uint8 securityFlags = 0 ;
461- pkt << uint8 (securityFlags); // security flags (0x0...0x04)
462490
463- if (securityFlags & 0x01 ) // PIN input
491+ _token = fields[7 ].GetCppString ();
492+ if (!_token.empty () && _build >= 8606 ) // authenticator was added in 2.4.3
493+ securityFlags = SECURITY_FLAG_AUTHENTICATOR;
494+
495+ pkt << uint8 (securityFlags); // security flags (0x0...0x04)
496+
497+ if (securityFlags & SECURITY_FLAG_PIN) // PIN input
464498 {
465499 pkt << uint32 (0 );
466- pkt << uint64 (0 ) << uint64 (0 ); // 16 bytes hash?
500+ pkt << uint64 (0 );
501+ pkt << uint64 (0 );
467502 }
468503
469- if (securityFlags & 0x02 ) // Matrix input
504+ if (securityFlags & SECURITY_FLAG_UNK) // Matrix input
470505 {
471506 pkt << uint8 (0 );
472507 pkt << uint8 (0 );
@@ -475,12 +510,10 @@ bool AuthSocket::_HandleLogonChallenge()
475510 pkt << uint64 (0 );
476511 }
477512
478- if (securityFlags & 0x04 ) // Security token input
479- {
513+ if (securityFlags & SECURITY_FLAG_AUTHENTICATOR) // Authenticator input
480514 pkt << uint8 (1 );
481- }
482515
483- uint8 secLevel = (*result) [4 ].GetUInt8 ();
516+ uint8 secLevel = fields [4 ].GetUInt8 ();
484517 _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes (secLevel) : SEC_ADMINISTRATOR;
485518
486519 _localizationName.resize (4 );
@@ -496,10 +529,9 @@ bool AuthSocket::_HandleLogonChallenge()
496529 delete result;
497530 }
498531 else // no account
499- {
500532 pkt << (uint8) WOW_FAIL_UNKNOWN_ACCOUNT;
501- }
502533 }
534+
503535 Write ((const char *)pkt.contents (), pkt.size ());
504536 return true ;
505537}
@@ -509,7 +541,7 @@ bool AuthSocket::_HandleLogonProof()
509541{
510542 DEBUG_LOG (" Entering _HandleLogonProof" );
511543 // /- Read the packet
512- sAuthLogonProof_C lp;
544+ sAuthLogonProof_C lp{} ;
513545 if (!Read ((char *)&lp, sizeof (sAuthLogonProof_C )))
514546 return false ;
515547
@@ -519,7 +551,6 @@ bool AuthSocket::_HandleLogonProof()
519551 // / <ul><li> If the client has no valid version
520552 if (!FindBuildInfo (_build))
521553 {
522-
523554 // no patch found
524555 ByteBuffer pkt;
525556 pkt << (uint8) CMD_AUTH_LOGON_CHALLENGE;
@@ -533,7 +564,6 @@ bool AuthSocket::_HandleLogonProof()
533564
534565 // /- Continue the SRP6 calculation based on data received from the client
535566 BigNumber A;
536-
537567 A.SetBinary (lp.A , 32 );
538568
539569 // SRP safeguard: abort if A==0
@@ -611,6 +641,28 @@ bool AuthSocket::_HandleLogonProof()
611641 // /- Check if SRP6 results match (password is correct), else send an error
612642 if (!memcmp (M.AsByteArray (), lp.M1 , 20 ))
613643 {
644+ if (lp.securityFlags & SECURITY_FLAG_AUTHENTICATOR || !_token.empty ())
645+ {
646+ sAuthLogonAuthenticatorData_C authData{};
647+ if (!Read ((char *) &authData, sizeof (sAuthLogonAuthenticatorData_C )))
648+ {
649+ const char data[4 ] = {CMD_AUTH_LOGON_PROOF, WOW_FAIL_UNKNOWN_ACCOUNT, 3 , 0 };
650+ Write (data, sizeof (data));
651+ return true ;
652+ }
653+
654+ auto ServerToken = generateToken (_token.c_str ());
655+ auto clientToken = atoi ((const char *) authData.keys );
656+ if (ServerToken != clientToken)
657+ {
658+ BASIC_LOG (" [AuthChallenge] Account %s tried to login with wrong pincode! Given %u Expected %u" , _login.c_str (), clientToken, ServerToken);
659+
660+ const char data[4 ] = { CMD_AUTH_LOGON_PROOF, WOW_FAIL_UNKNOWN_ACCOUNT, 3 , 0 };
661+ Write (data, sizeof (data));
662+ return true ;
663+ }
664+ }
665+
614666 BASIC_LOG (" User '%s' successfully authenticated" , _login.c_str ());
615667
616668 // /- Update the sessionkey, last_ip, last login time and reset number of failed logins in the account table for this account
@@ -839,7 +891,6 @@ bool AuthSocket::_HandleRealmList()
839891 hdr.append (pkt);
840892
841893 Write ((const char *)hdr.contents (), hdr.size ());
842-
843894 return true ;
844895}
845896
@@ -1004,4 +1055,29 @@ bool AuthSocket::_HandleXferAccept()
10041055 ReadSkip (1 );
10051056
10061057 return true ;
1058+ }
1059+
1060+ int32 AuthSocket::generateToken (char const * b32key)
1061+ {
1062+ size_t keySize = strlen (b32key);
1063+ size_t bufSize = (keySize + 7 ) / 8 * 5 ;
1064+ char * encoded = new char [bufSize];
1065+ memset (encoded, 0 , bufSize);
1066+ unsigned int hmac_result_size = HMAC_RES_SIZE;
1067+ unsigned char hmac_result[HMAC_RES_SIZE];
1068+ unsigned long timestamp = time (nullptr )/30 ;
1069+ unsigned char challenge[8 ];
1070+
1071+ for (int i = 8 ; i--; timestamp >>= 8 )
1072+ challenge[i] = timestamp;
1073+
1074+ base32_decode (b32key, encoded, bufSize);
1075+ HMAC (EVP_sha1 (), encoded, bufSize, challenge, 8 , hmac_result, &hmac_result_size);
1076+ unsigned int offset = hmac_result[19 ] & 0xF ;
1077+ unsigned int truncHash = (hmac_result[offset] << 24 ) | (hmac_result[offset + 1 ] << 16 ) | (hmac_result[offset + 2 ] << 8 ) | (hmac_result[offset + 3 ]);
1078+ truncHash &= 0x7FFFFFFF ;
1079+
1080+ delete[] encoded;
1081+
1082+ return truncHash % 1000000 ;
10071083}
0 commit comments