2 * Copyright (C) 2005,2006,2007 MaNGOS <http://www.mangosproject.org/>
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"
27 #include "RealmList.h"
28 #include "AuthSocket.h"
29 #include "AuthCodes.h"
30 #include <cwctype> // needs for towupper
31 #include "openssl/md5.h"
32 #include "Auth/Sha1.h"
34 extern RealmList m_realmList
;
35 extern DatabaseMysql dbRealmServer
;
36 #define ChunkSize 2048
41 AUTH_LOGON_CHALLENGE
= 0x00,
42 AUTH_LOGON_PROOF
= 0x01,
43 //AUTH_RECONNECT_CHALLENGE = 0x02,
44 //AUTH_RECONNECT_PROOF = 0x03,
60 // Only GCC 4.1.0 and later support #pragma pack(push,1) syntax
61 #if __GNUC__ && (GCC_MAJOR < 4 || GCC_MAJOR == 4 && GCC_MINOR < 1)
84 } sAuthLogonChallenge_C
;
86 //typedef sAuthLogonChallenge_C sAuthReconnectChallenge_C;
100 } sAuthLogonChallenge_S;
109 uint8 number_of_keys
;
110 uint8 unk
; // Added in 1.12.x client branch
119 } sAuthLogonProofKey_C;
131 uint8 cmd
; //XFER_INITIATE
132 uint8 size
; //strlen("Patch");
135 uint8 md5
[MD5_DIGEST_LENGTH
];
142 uint8 data
[ChunkSize
];
149 bool (AuthSocket::*handler
)(void);
152 // Only GCC 4.1.0 and later support #pragma pack(pop) syntax
153 #if __GNUC__ && (GCC_MAJOR < 4 || GCC_MAJOR == 4 && GCC_MINOR < 1)
159 /// Launch a thread to transfer a patch to the client
160 class PatcherRunnable
: public ZThread::Runnable
163 PatcherRunnable(class AuthSocket
*);
167 AuthSocket
* mySocket
;
172 uint8 md5
[MD5_DIGEST_LENGTH
];
175 /// Caches MD5 hash of client patches present on the server
179 typedef std::map
<std::string
, PATCH_INFO
*> Patches
;
182 Patches::const_iterator
begin() const { return _patches
.begin(); }
183 Patches::const_iterator
end() const { return _patches
.end(); }
184 void LoadPatchMD5(char*);
185 bool GetHash(char * pat
,uint8 mymd5
[16]);
188 void LoadPatchesInfo();
192 const AuthHandler table
[] =
194 { AUTH_LOGON_CHALLENGE
, STATUS_CONNECTED
, &AuthSocket::_HandleLogonChallenge
},
195 { AUTH_LOGON_PROOF
, STATUS_CONNECTED
, &AuthSocket::_HandleLogonProof
},
196 { REALM_LIST
, STATUS_AUTHED
, &AuthSocket::_HandleRealmList
},
197 { XFER_ACCEPT
, STATUS_CONNECTED
, &AuthSocket::_HandleXferAccept
},
198 { XFER_RESUME
, STATUS_CONNECTED
, &AuthSocket::_HandleXferResume
},
199 { XFER_CANCEL
, STATUS_CONNECTED
, &AuthSocket::_HandleXferCancel
}
202 #define AUTH_TOTAL_COMMANDS sizeof(table)/sizeof(AuthHandler)
204 ///Holds the MD5 hash of client patches present on the server
205 Patcher PatchesCache
;
207 /// Constructor - set the N and g values for SRP6
208 AuthSocket::AuthSocket(SocketHandler
&h
) : TcpSocket(h
)
210 N
.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7");
216 /// Close patch file descriptor before leaving
217 AuthSocket::~AuthSocket()
223 /// Accept the connection and set the s random value for SRP6
224 void AuthSocket::OnAccept()
226 sLog
.outBasic("Accepting connection from '%s:%d'",
227 GetRemoteAddress().c_str(), GetRemotePort());
229 s
.SetRand(s_BYTE_SIZE
* 8);
232 /// Read the packet from the client
233 void AuthSocket::OnRead()
240 if (!ibuf
.GetLength())
243 ///- Get the command out of it
244 ibuf
.SoftRead((char *)&_cmd
, 1);
247 ///- Circle through known commands and call the correct command handler
248 for (i
=0;i
<AUTH_TOTAL_COMMANDS
; i
++)
250 if ((uint8
)table
[i
].cmd
== _cmd
&&
251 (table
[i
].status
== STATUS_CONNECTED
||
252 (_authed
&& table
[i
].status
== STATUS_AUTHED
)))
254 DEBUG_LOG("[Auth] got data for cmd %u ibuf length %u", (uint32
)_cmd
, ibuf
.GetLength());
256 if (!(*this.*table
[i
].handler
)())
258 DEBUG_LOG("Command handler failed for cmd %u ibuf length %u", (uint32
)_cmd
, ibuf
.GetLength());
265 ///- Report unknown commands in the debug log
266 if (i
==AUTH_TOTAL_COMMANDS
)
268 DEBUG_LOG("[Auth] got unknown packet %u", (uint32
)_cmd
);
274 /// Upper password, and make the SRP6 calculation
275 void AuthSocket::_SetVSFields(std::string password
)
277 std::transform(password
.begin(), password
.end(), password
.begin(), std::towupper
);
280 std::string sI
= _login
+ ":" + password
;
284 sha
.UpdateData(s
.AsByteArray(), s
.GetNumBytes());
285 sha
.UpdateData(I
.GetDigest(), 20);
288 x
.SetBinary(sha
.GetDigest(), sha
.GetLength());
290 // No SQL injection (username escaped)
291 dbRealmServer
.PExecute("UPDATE `account` SET `v` = '%s', `s` = '%s' WHERE `username` = '%s'",v
.AsHexStr(),s
.AsHexStr(), _safelogin
.c_str() );
294 /// Logon Challenge command handler
295 bool AuthSocket::_HandleLogonChallenge()
297 DEBUG_LOG("Entering _HandleLogonChallenge");
298 if (ibuf
.GetLength() < sizeof(sAuthLogonChallenge_C
))
301 ///- Read the first 4 bytes (header) to get the length of the remaining of the packet
302 std::vector
<uint8
> buf
;
305 ibuf
.Read((char *)&buf
[0], 4);
306 uint16 remaining
= ((sAuthLogonChallenge_C
*)&buf
[0])->size
;
307 DEBUG_LOG("[AuthChallenge] got header, body is %#04x bytes", remaining
);
309 if ((remaining
< sizeof(sAuthLogonChallenge_C
) - buf
.size()) || (ibuf
.GetLength() < remaining
))
312 //No big fear of memory outage (size is int16, i.e. < 65536)
313 buf
.resize(remaining
+ buf
.size() + 1);
314 buf
[buf
.size() - 1] = 0;
315 sAuthLogonChallenge_C
*ch
= (sAuthLogonChallenge_C
*)&buf
[0];
317 ///- Read the remaining of the packet
318 ibuf
.Read((char *)&buf
[4], remaining
);
319 DEBUG_LOG("[AuthChallenge] got full packet, %#04x bytes", ch
->size
);
320 DEBUG_LOG("[AuthChallenge] name(%d): '%s'", ch
->I_len
, ch
->I
);
324 _login
= (const char*)ch
->I
;
326 //Escape the user login to avoid further SQL injection
327 //Memory will be freed on AuthSocket object destruction
329 dbRealmServer
.escape_string(_safelogin
);
331 ///- Check if the client has one of the expected version numbers
332 bool valid_version
=false;
333 int accepted_versions
[]=EXPECTED_MANGOS_CLIENT_BUILD
;
334 for(int i
=0;accepted_versions
[i
];i
++)
335 if(ch
->build
==accepted_versions
[i
])
341 /// <ul><li> if this is a valid version
344 pkt
<< (uint8
) AUTH_LOGON_CHALLENGE
;
347 ///- Verify that this IP is not in the ip_banned table
348 // No SQL injection possible (paste the IP address as passed by the socket)
349 QueryResult
*result
= dbRealmServer
.PQuery( "SELECT * FROM `ip_banned` WHERE `ip` = '%s';",GetRemoteAddress().c_str());
352 pkt
<< (uint8
)REALM_AUTH_ACCOUNT_BANNED
;
353 sLog
.outBasic("[AuthChallenge] Banned ip %s tries to login!",GetRemoteAddress().c_str ());
358 ///- Get the account details from the account table
359 // No SQL injection (escaped user name)
360 QueryResult
*result
= dbRealmServer
.PQuery("SELECT `password`,`banned`,`locked`,`last_ip`,`online` FROM `account` WHERE `username` = '%s'",_safelogin
.c_str ());
363 ///- If the IP is 'locked', check that the player comes indeed from the correct IP address
365 if((*result
)[2].GetUInt8() == 1) // if ip is locked
367 DEBUG_LOG("[AuthChallenge] Account '%s' is locked to IP - '%s'", _login
.c_str(), (*result
)[3].GetString());
368 DEBUG_LOG("[AuthChallenge] Player address is '%s'", GetRemoteAddress().c_str());
369 if ( strcmp((*result
)[3].GetString(),GetRemoteAddress().c_str()) )
371 DEBUG_LOG("[AuthChallenge] Account IP differs");
372 pkt
<< (uint8
) REALM_AUTH_ACCOUNT_FREEZED
;
377 DEBUG_LOG("[AuthChallenge] Account IP matches");
382 DEBUG_LOG("[AuthChallenge] Account '%s' is not locked to ip", _login
.c_str());
387 ///- If the account is banned, reject the logon attempt
388 if((*result
)[1].GetUInt8())
390 pkt
<< (uint8
) REALM_AUTH_ACCOUNT_BANNED
;
391 sLog
.outBasic("[AuthChallenge] Banned account %s tries to login!",_login
.c_str ());
395 ///- If the user is already logged in, reject the logon attempt
396 //if((*result)[4].GetUInt8() == 1)
398 // pkt << (uint8)REALM_AUTH_ACCOUNT_IN_USE;
402 ///- Get the password from the account table, upper it, and make the SRP6 calculation
403 std::string password
= (*result
)[0].GetCppString();
404 _SetVSFields(password
);
407 BigNumber gmod
=g
.ModExp(b
, N
);
408 B
= ((v
* 3) + gmod
) % N
;
409 ASSERT(gmod
.GetNumBytes() <= 32);
414 ///- Fill the response packet with the result
415 pkt
<< (uint8
)REALM_AUTH_SUCCESS
;
416 pkt
.append(B
.AsByteArray(), 32);
418 pkt
.append(g
.AsByteArray(), 1);
420 pkt
.append(N
.AsByteArray(), 32);
421 pkt
.append(s
.AsByteArray(), s
.GetNumBytes());
422 pkt
.append(unk3
.AsByteArray(), 16);
423 pkt
<< (uint8
)0; // Added in 1.12.x client branch
431 pkt
<< (uint8
) REALM_AUTH_NO_MATCH
;
438 ///- Check if we have the apropriate patch on the disk
440 // No buffer overflow (fixed length of arguments)
441 sprintf(tmp
,"./patches/%d%c%c%c%c.mpq",ch
->build
,ch
->country
[3],
442 ch
->country
[2],ch
->country
[1],ch
->country
[0]);
443 // This will be closed at the destruction of the AuthSocket (client deconnection)
444 FILE *pFile
=fopen(tmp
,"rb");
447 pkt
<< (uint8
) AUTH_LOGON_CHALLENGE
;
449 pkt
<< (uint8
) REALM_AUTH_WRONG_BUILD_NUMBER
;
450 DEBUG_LOG("[AuthChallenge] %u is not a valid client version!", ch
->build
);
451 DEBUG_LOG("[AuthChallenge] Patch %s not found",tmp
);
457 ///- Get the MD5 hash of the patch file (get it from preloaded Patcher cache or calculate it)
458 if(PatchesCache
.GetHash(tmp
,(uint8
*)&xferh
.md5
))
460 DEBUG_LOG("\n[AuthChallenge] Found precached patch info for patch %s",tmp
);
463 { //calculate patch md5
464 printf("\n[AuthChallenge] Patch info for %s was not cached.",tmp
);
465 PatchesCache
.LoadPatchMD5(tmp
);
466 PatchesCache
.GetHash(tmp
,(uint8
*)&xferh
.md5
);
469 ///- Send a packet to the client with the file length and MD5 hash
470 uint8 data
[2]={AUTH_LOGON_PROOF
,CSTATUS_NEGOTIATION_FAILED
};
471 SendBuf((const char*)data
,sizeof(data
));
473 memcpy(&xferh
,"0\x05Patch",7);
474 xferh
.cmd
=XFER_INITIATE
;
475 fseek(pPatch
,0,SEEK_END
);
476 xferh
.file_size
=ftell(pPatch
);
478 SendBuf((const char*)&xferh
,sizeof(xferh
));
483 SendBuf((char *)pkt
.contents(), pkt
.size());
487 /// Logon Proof command handler
488 bool AuthSocket::_HandleLogonProof()
490 DEBUG_LOG("Entering _HandleLogonProof");
492 if (ibuf
.GetLength() < sizeof(sAuthLogonProof_C
))
495 sAuthLogonProof_C lp
;
496 ibuf
.Read((char *)&lp
, sizeof(sAuthLogonProof_C
));
498 ///- Continue the SRP6 calculation based on data received from the client
500 A
.SetBinary(lp
.A
, 32);
503 sha
.UpdateBigNumbers(&A
, &B
, NULL
);
506 u
.SetBinary(sha
.GetDigest(), 20);
507 BigNumber S
= (A
* (v
.ModExp(u
, N
))).ModExp(b
, N
);
512 memcpy(t
, S
.AsByteArray(), 32);
513 for (int i
= 0; i
< 16; i
++)
518 sha
.UpdateData(t1
, 16);
520 for (int i
= 0; i
< 20; i
++)
522 vK
[i
*2] = sha
.GetDigest()[i
];
524 for (int i
= 0; i
< 16; i
++)
529 sha
.UpdateData(t1
, 16);
531 for (int i
= 0; i
< 20; i
++)
533 vK
[i
*2+1] = sha
.GetDigest()[i
];
540 sha
.UpdateBigNumbers(&N
, NULL
);
542 memcpy(hash
, sha
.GetDigest(), 20);
544 sha
.UpdateBigNumbers(&g
, NULL
);
546 for (int i
= 0; i
< 20; i
++)
548 hash
[i
] ^= sha
.GetDigest()[i
];
551 t3
.SetBinary(hash
, 20);
554 sha
.UpdateData(_login
);
557 t4
.SetBinary(sha
.GetDigest(), 20);
560 sha
.UpdateBigNumbers(&t3
, &t4
, &s
, &A
, &B
, &K
, NULL
);
563 M
.SetBinary(sha
.GetDigest(), 20);
565 ///- Check if SRP6 results match (password is correct), else send an error
566 if (!memcmp(M
.AsByteArray(), lp
.M1
, 20))
568 sLog
.outBasic("User '%s' successfully authenticated", _login
.c_str());
570 ///- Update the sessionkey, last_ip and last login time in the account table for this account
571 // No SQL injection (escaped user name) and IP address as received by socket
572 dbRealmServer
.PExecute("UPDATE `account` SET `sessionkey` = '%s', `last_ip` = '%s', `last_login` = NOW() WHERE `username` = '%s'",K
.AsHexStr(), GetRemoteAddress().c_str(), _safelogin
.c_str() );
574 ///- Finish SRP6 and send the final result to the client
576 sha
.UpdateBigNumbers(&A
, &M
, &K
, NULL
);
579 sAuthLogonProof_S proof
;
580 memcpy(proof
.M2
, sha
.GetDigest(), 20);
581 proof
.cmd
= AUTH_LOGON_PROOF
;
585 SendBuf((char *)&proof
, sizeof(proof
));
587 ///- Set _authed to true!
592 char data
[2]={AUTH_LOGON_PROOF
,REALM_AUTH_NO_MATCH
};
593 SendBuf(data
,sizeof(data
));
598 /// %Realm List command handler
599 bool AuthSocket::_HandleRealmList()
601 DEBUG_LOG("Entering _HandleRealmList");
602 if (ibuf
.GetLength() < 5)
607 ///- Get the user id (else close the connection)
608 // No SQL injection (escaped user name)
609 QueryResult
*result
= dbRealmServer
.PQuery("SELECT `id`,`password` FROM `account` WHERE `username` = '%s'",_safelogin
.c_str());
612 sLog
.outError("[ERROR] user %s tried to login and we cannot find him in the database.",_login
.c_str());
617 uint32 id
= (*result
)[0].GetUInt32();
618 std::string password
= (*result
)[1].GetCppString();
621 ///- Circle through realms in the RealmList and construct the return packet (including # of user characters in each realm)
622 uint8 AmountOfCharacters
= 0;
626 pkt
<< (uint8
) m_realmList
.size();
627 RealmList::RealmMap::const_iterator i
;
628 for( i
= m_realmList
.begin(); i
!= m_realmList
.end(); i
++ )
630 pkt
<< (uint32
) i
->second
->icon
;
631 pkt
<< (uint8
) i
->second
->color
;
633 pkt
<< i
->second
->address
;
634 /// \todo Fix realm population
635 pkt
<< (float) 0.0; //this is population 0.5 = low 1.0 = medium 2.0 high (float)(maxplayers / players)*2
636 // No SQL injection. id of realm is controlled by the database.
637 result
= dbRealmServer
.PQuery( "SELECT `numchars` FROM `realmcharacters` WHERE `realmid` = '%d' AND `acctid`='%u'",i
->second
->m_ID
,id
);
640 Field
*fields
= result
->Fetch();
641 AmountOfCharacters
= fields
[0].GetUInt8();
646 AmountOfCharacters
= 0;
648 pkt
<< (uint8
) AmountOfCharacters
;
649 pkt
<< (uint8
) i
->second
->timezone
;
656 hdr
<< (uint8
) REALM_LIST
;
657 hdr
<< (uint16
)pkt
.size();
660 SendBuf((char *)hdr
.contents(), hdr
.size());
662 // Set check field before possable reloagin to realm
663 _SetVSFields(password
);
667 /// Resume patch transfer
668 bool AuthSocket::_HandleXferResume()
670 DEBUG_LOG("Entering _HandleXferResume");
671 ///- Check packet length
672 if (ibuf
.GetLength()<9)
674 sLog
.outError("Error while resuming patch transfer (wrong packet)");
678 ///- Launch a PatcherRunnable thread starting at given patch file offset
681 ibuf
.Read((char*)&start
,sizeof(start
));
682 fseek(pPatch
,start
,0);
684 ZThread::Thread
u(new PatcherRunnable(this));
688 /// Cancel patch transfer
689 bool AuthSocket::_HandleXferCancel()
691 DEBUG_LOG("Entering _HandleXferCancel");
693 ///- Close and delete the socket
694 ibuf
.Remove(1); //clear input buffer
696 //ZThread::Thread::sleep(15);
697 /// \todo What is the difference between SetCloseAndDelete() and the this->Close() higher?
703 /// Accept patch transfer
704 bool AuthSocket::_HandleXferAccept()
706 DEBUG_LOG("Entering _HandleXferAccept");
707 ///- Launch a PatcherRunnable thread, starting at the begining of the patch file
708 ibuf
.Remove(1); //clear input buffer
711 ZThread::Thread
u(new PatcherRunnable(this));
716 /// Check if there is lag on the connection to the client
717 bool AuthSocket::IsLag()
719 return (TCP_BUFSIZE_READ
-obuf
.GetLength()< 2*ChunkSize
);
722 PatcherRunnable::PatcherRunnable(class AuthSocket
* as
)
727 /// Send content of patch file to the client
728 void PatcherRunnable::run()
730 XFER_DATA_STRUCT xfdata
;
731 xfdata
.opcode
= XFER_DATA
;
733 while(!feof(mySocket
->pPatch
) && mySocket
->Ready())
735 ///- Wait until output buffer is reasonably empty
736 while(mySocket
->Ready() && mySocket
->IsLag())
738 ZThread::Thread::sleep(1);
740 ///- And send content of the patch file to the client
741 xfdata
.data_size
=fread(&xfdata
.data
,1,ChunkSize
,mySocket
->pPatch
);
742 mySocket
->SendBuf((const char*)&xfdata
,xfdata
.data_size
+(sizeof(XFER_DATA_STRUCT
)-ChunkSize
));
746 /// Preload MD5 hashes of existing patch files on server
750 void Patcher::LoadPatchesInfo()
755 dirp
= opendir("./patches/");
761 if ((dp
= readdir(dirp
)) != NULL
)
763 int l
=strlen(dp
->d_name
);
765 if(!memcmp(&dp
->d_name
[l
-4],".mpq",4))
766 LoadPatchMD5(dp
->d_name
);
784 void Patcher::LoadPatchesInfo()
787 HANDLE hFil
=FindFirstFile("./patches/*.mpq",&fil
);
788 if(hFil
==INVALID_HANDLE_VALUE
)
789 return; //no patches were found
791 LoadPatchMD5(fil
.cFileName
);
793 while(FindNextFile(hFil
,&fil
))
794 LoadPatchMD5(fil
.cFileName
);
798 /// Calculate and store MD5 hash for a given patch file
799 void Patcher::LoadPatchMD5(char * szFileName
)
801 ///- Try to open the patch file
802 std::string path
= "./patches/";
804 FILE * pPatch
=fopen(path
.c_str(),"rb");
805 sLog
.outDebug("Loading patch info from %s\n",path
.c_str());
808 sLog
.outError("Error loading patch %s\n",path
.c_str());
812 ///- Calculate the MD5 hash
815 uint8
* buf
= new uint8
[512*1024];
817 while (!feof(pPatch
))
819 size_t read
= fread(buf
, 1, 512*1024, pPatch
);
820 MD5_Update(&ctx
, buf
, read
);
825 ///- Store the result in the internal patch hash map
826 _patches
[path
] = new PATCH_INFO
;
827 MD5_Final((uint8
*)&_patches
[path
]->md5
, &ctx
);
830 /// Get cached MD5 hash for a given patch file
831 bool Patcher::GetHash(char * pat
,uint8 mymd5
[16])
833 for( Patches::iterator i
= _patches
.begin(); i
!= _patches
.end(); i
++ )
834 if(!stricmp(pat
,i
->first
.c_str () ))
836 memcpy(mymd5
,i
->second
->md5
,16);
843 /// Launch the patch hashing mechanism on object creation
849 /// Empty and delete the patch map on termination
852 for(Patches::iterator i
= _patches
.begin(); i
!= _patches
.end(); i
++ )