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
24 #include "Database/DatabaseEnv.h"
25 #include "ByteBuffer.h"
26 #include "Config/ConfigEnv.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
44 AUTH_LOGON_CHALLENGE
= 0x00,
45 AUTH_LOGON_PROOF
= 0x01,
46 AUTH_RECONNECT_CHALLENGE
= 0x02,
47 AUTH_RECONNECT_PROOF
= 0x03,
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__ )
70 typedef struct AUTH_LOGON_CHALLENGE_C
87 } sAuthLogonChallenge_C
;
89 //typedef sAuthLogonChallenge_C sAuthReconnectChallenge_C;
103 } sAuthLogonChallenge_S;
106 typedef struct AUTH_LOGON_PROOF_C
112 uint8 number_of_keys
;
113 uint8 securityFlags
; // 0x00-0x04
122 } sAuthLogonProofKey_C;
124 typedef struct AUTH_LOGON_PROOF_S
134 typedef struct AUTH_RECONNECT_PROOF_C
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
152 typedef struct XFER_DATA
156 uint8 data
[ChunkSize
];
159 typedef struct AuthHandler
163 bool (AuthSocket::*handler
)(void);
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__ )
173 /// Launch a thread to transfer a patch to the client
174 class PatcherRunnable
: public ACE_Based::Runnable
177 PatcherRunnable(class AuthSocket
*);
181 AuthSocket
* mySocket
;
184 typedef struct PATCH_INFO
186 uint8 md5
[MD5_DIGEST_LENGTH
];
189 /// Caches MD5 hash of client patches present on the server
193 typedef std::map
<std::string
, PATCH_INFO
*> Patches
;
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]);
202 void LoadPatchesInfo();
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");
231 _accountSecurityLevel
= SEC_PLAYER
;
234 /// Close patch file descriptor before leaving
235 AuthSocket::~AuthSocket()
237 ACE_Guard
<ACE_Thread_Mutex
> g(patcherLock
);
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()
260 if (!ibuf
.GetLength())
263 ///- Get the command out of it
264 ibuf
.SoftRead((char *)&_cmd
, 1); // UQ1: No longer exists in new net code ???
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());
286 ///- Report unknown commands in the debug log
287 if (i
== AUTH_TOTAL_COMMANDS
)
289 DEBUG_LOG("[Auth] got unknown packet %u", (uint32
)_cmd
);
295 /// Make the SRP6 calculation from hash in dB
296 void AuthSocket::_SetVSFields(const std::string
& rI
)
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
);
310 sha
.UpdateData(s
.AsByteArray(), s
.GetNumBytes());
311 sha
.UpdateData(mDigest
, SHA_DIGEST_LENGTH
);
314 x
.SetBinary(sha
.GetDigest(), sha
.GetLength());
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
))
332 ///- Read the first 4 bytes (header) to get the length of the remaining of the packet
333 std::vector
<uint8
> buf
;
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
))
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
);
367 _login
= (const char*)ch
->I
;
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
376 loginDatabase
.escape_string(_safelogin
);
378 pkt
<< (uint8
) AUTH_LOGON_CHALLENGE
;
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());
390 pkt
<< (uint8
)REALM_AUTH_ACCOUNT_BANNED
;
391 sLog
.outBasic("[AuthChallenge] Banned ip %s tries to login!",GetRemoteAddress().c_str ());
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 ());
402 ///- If the IP is 'locked', check that the player comes indeed from the correct IP address
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
;
416 DEBUG_LOG("[AuthChallenge] Account IP matches");
421 DEBUG_LOG("[AuthChallenge] Account '%s' is not locked to ip", _login
.c_str());
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());
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 ());
439 pkt
<< (uint8
) REALM_AUTH_ACCOUNT_FREEZED
;
440 sLog
.outBasic("[AuthChallenge] Temporarily banned account %s tries to login!",_login
.c_str ());
447 ///- Get the password from the account table, upper it, and make the SRP6 calculation
448 std::string rI
= (*result
)[0].GetCppString();
452 BigNumber gmod
= g
.ModExp(b
, N
);
453 B
= ((v
* 3) + gmod
) % N
;
455 ASSERT(gmod
.GetNumBytes() <= 32);
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
466 pkt
.append(g
.AsByteArray(), 1);
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
477 pkt
<< uint64(0) << uint64(0); // 16 bytes hash?
480 if(securityFlags
& 0x02) // Matrix input
489 if(securityFlags
& 0x04) // Security token input
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
));
508 pkt
<< (uint8
) REALM_AUTH_NO_MATCH
;
511 SendBuf((char const*)pkt
.contents(), pkt
.size());
515 /// Logon Proof command handler
516 bool AuthSocket::_HandleLogonProof()
518 DEBUG_LOG("Entering _HandleLogonProof");
520 if (ibuf
.GetLength() < sizeof(sAuthLogonProof_C
))
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;
537 /// <ul><li> If the client has no valid version
540 ///- Check if we have the apropriate patch on the disk
542 // 24 = len("./patches/65535enGB.mpq")+1
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");
552 pkt
<< (uint8
) AUTH_LOGON_CHALLENGE
;
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());
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
);
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
));
592 ///- Continue the SRP6 calculation based on data received from the client
594 A
.SetBinary(lp
.A
, 32);
597 sha
.UpdateBigNumbers(&A
, &B
, NULL
);
600 u
.SetBinary(sha
.GetDigest(), 20);
601 BigNumber S
= (A
* (v
.ModExp(u
, N
))).ModExp(b
, N
);
606 memcpy(t
, S
.AsByteArray(), 32);
607 for (int i
= 0; i
< 16; ++i
)
612 sha
.UpdateData(t1
, 16);
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];
623 sha
.UpdateData(t1
, 16);
625 for (int i
= 0; i
< 20; ++i
)
627 vK
[i
* 2 + 1] = sha
.GetDigest()[i
];
634 sha
.UpdateBigNumbers(&N
, NULL
);
636 memcpy(hash
, sha
.GetDigest(), 20);
638 sha
.UpdateBigNumbers(&g
, NULL
);
640 for (int i
= 0; i
< 20; ++i
)
642 hash
[i
] ^= sha
.GetDigest()[i
];
645 t3
.SetBinary(hash
, 20);
648 sha
.UpdateData(_login
);
650 uint8 t4
[SHA_DIGEST_LENGTH
];
651 memcpy(t4
, sha
.GetDigest(), SHA_DIGEST_LENGTH
);
654 sha
.UpdateBigNumbers(&t3
, NULL
);
655 sha
.UpdateData(t4
, SHA_DIGEST_LENGTH
);
656 sha
.UpdateBigNumbers(&s
, &A
, &B
, &K
, NULL
);
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
674 sha
.UpdateBigNumbers(&A
, &M
, &K
, NULL
);
677 sAuthLogonProof_S proof
;
678 memcpy(proof
.M2
, sha
.GetDigest(), 20);
679 proof
.cmd
= AUTH_LOGON_PROOF
;
681 proof
.unk1
= 0x00800000;
685 SendBuf((char *)&proof
, sizeof(proof
));
687 ///- Set _authed to true!
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);
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
);
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
);
737 /// Reconnect Challenge command handler
738 bool AuthSocket::_HandleReconnectChallenge()
740 DEBUG_LOG("Entering _HandleReconnectChallenge");
741 if (ibuf
.GetLength() < sizeof(sAuthLogonChallenge_C
))
744 ///- Read the first 4 bytes (header) to get the length of the remaining of the packet
745 std::vector
<uint8
> buf
;
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
))
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
;
770 QueryResult
*result
= loginDatabase
.PQuery ("SELECT sessionkey FROM account WHERE username = '%s'", _safelogin
.c_str ());
772 // Stop if the account is not found
775 sLog
.outError("[ERROR] user %s tried to login and we cannot find his session key in the database.", _login
.c_str());
780 Field
* fields
= result
->Fetch ();
781 K
.SetHexStr (fields
[0].GetString ());
784 ///- Sending response
786 pkt
<< (uint8
) AUTH_RECONNECT_CHALLENGE
;
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());
795 /// Reconnect Proof command handler
796 bool AuthSocket::_HandleReconnectProof()
798 DEBUG_LOG("Entering _HandleReconnectProof");
800 if (ibuf
.GetLength() < sizeof(sAuthReconnectProof_C
))
802 if (_login
.empty() || !_reconnectProof
.GetNumBytes() || !K
.GetNumBytes())
804 sAuthReconnectProof_C lp
;
805 ibuf
.Read((char *)&lp
, sizeof(sAuthReconnectProof_C
));
808 t1
.SetBinary(lp
.R1
, 16);
812 sha
.UpdateData(_login
);
813 sha
.UpdateBigNumbers(&t1
, &_reconnectProof
, &K
, NULL
);
816 if (!memcmp(sha
.GetDigest(), lp
.R2
, SHA_DIGEST_LENGTH
))
818 ///- Sending response
820 pkt
<< (uint8
) AUTH_RECONNECT_PROOF
;
822 pkt
<< (uint16
) 0x00; // 2 bytes zeros
823 SendBuf((char const*)pkt
.contents(), pkt
.size());
825 ///- Set _authed to true!
832 sLog
.outError("[ERROR] user %s tried to login, but session invalid.", _login
.c_str());
838 /// %Realm List command handler
839 bool AuthSocket::_HandleRealmList()
841 DEBUG_LOG("Entering _HandleRealmList");
842 if (ibuf
.GetLength() < 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());
853 sLog
.outError("[ERROR] user %s tried to login and we cannot find him in the database.",_login
.c_str());
858 uint32 id
= (*result
)[0].GetUInt32();
859 std::string rI
= (*result
)[1].GetCppString();
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)
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
);
878 Field
*fields
= result
->Fetch();
879 AmountOfCharacters
= fields
[0].GetUInt8();
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
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?
901 hdr
<< (uint8
) REALM_LIST
;
902 hdr
<< (uint16
)pkt
.size();
905 SendBuf((char const*)hdr
.contents(), hdr
.size());
907 // Set check field before possible relogin to realm
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)");
923 ///- Launch a PatcherRunnable thread starting at given patch file offset
926 ibuf
.Read((char*)&start
,sizeof(start
));
927 fseek(pPatch
, start
, 0);
929 ACE_Based::Thread
u(*new PatcherRunnable(this));
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
946 /// Accept patch transfer
947 bool AuthSocket::_HandleXferAccept()
949 DEBUG_LOG("Entering _HandleXferAccept");
951 ///- Check packet length and patch existence
954 sLog
.outError("Error while accepting patch transfer (wrong packet)");
958 ///- Launch a PatcherRunnable thread, starting at the beginning of the patch file
959 ibuf
.Remove(1); // clear input buffer
962 ACE_Based::Thread
u(*new PatcherRunnable(this));
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
)
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
1002 void Patcher::LoadPatchesInfo()
1007 dirp
= opendir("./patches/");
1013 if ((dp
= readdir(dirp
)) != NULL
)
1015 int l
= strlen(dp
->d_name
);
1018 if(!memcmp(&dp
->d_name
[l
-4],".mpq",4))
1019 LoadPatchMD5(dp
->d_name
);
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
));
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/";
1058 FILE *pPatch
= fopen(path
.c_str(), "rb");
1059 sLog
.outDebug("Loading patch info from %s\n", path
.c_str());
1062 sLog
.outError("Error loading patch %s\n", path
.c_str());
1066 ///- Calculate the MD5 hash
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
);
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);
1097 /// Launch the patch hashing mechanism on object creation
1103 /// Empty and delete the patch map on termination
1106 for(Patches::iterator i
= _patches
.begin(); i
!= _patches
.end(); ++i
)