[3216] - Applied MaNGOS coding style (see trunk/bcpp.cfg).
[mangos-git.git] / src / realmd / AuthSocket.cpp
blobf8df3cc794ac83dd22129e1acec2b562fecc5352
1 /*
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
19 /** \file
20 \ingroup realmd
23 #include "Common.h"
24 #include "Database/DatabaseEnv.h"
25 #include "ByteBuffer.h"
26 #include "Log.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
38 enum eAuthCmd
40 //AUTH_NO_CMD = 0xFF,
41 AUTH_LOGON_CHALLENGE = 0x00,
42 AUTH_LOGON_PROOF = 0x01,
43 //AUTH_RECONNECT_CHALLENGE = 0x02,
44 //AUTH_RECONNECT_PROOF = 0x03,
45 //update srv =4
46 REALM_LIST = 0x10,
47 XFER_INITIATE = 0x30,
48 XFER_DATA = 0x31,
49 XFER_ACCEPT = 0x32,
50 XFER_RESUME = 0x33,
51 XFER_CANCEL = 0x34
54 enum eStatus
56 STATUS_CONNECTED = 0,
57 STATUS_AUTHED
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)
62 #pragma pack(1)
63 #else
64 #pragma pack(push,1)
65 #endif
67 typedef struct
69 uint8 cmd;
70 uint8 error;
71 uint16 size;
72 uint8 gamename[4];
73 uint8 version1;
74 uint8 version2;
75 uint8 version3;
76 uint16 build;
77 uint8 platform[4];
78 uint8 os[4];
79 uint8 country[4];
80 uint32 timezone_bias;
81 uint32 ip;
82 uint8 I_len;
83 uint8 I[1];
84 } sAuthLogonChallenge_C;
86 //typedef sAuthLogonChallenge_C sAuthReconnectChallenge_C;
88 typedef struct
90 uint8 cmd;
91 uint8 error;
92 uint8 unk2;
93 uint8 B[32];
94 uint8 g_len;
95 uint8 g[1];
96 uint8 N_len;
97 uint8 N[32];
98 uint8 s[32];
99 uint8 unk3[16];
100 } sAuthLogonChallenge_S;
103 typedef struct
105 uint8 cmd;
106 uint8 A[32];
107 uint8 M1[20];
108 uint8 crc_hash[20];
109 uint8 number_of_keys;
110 uint8 unk; // Added in 1.12.x client branch
111 } sAuthLogonProof_C;
113 typedef struct
115 uint16 unk1;
116 uint32 unk2;
117 uint8 unk3[4];
118 uint16 unk4[20];
119 } sAuthLogonProofKey_C;
121 typedef struct
123 uint8 cmd;
124 uint8 error;
125 uint8 M2[20];
126 uint32 unk2;
127 } sAuthLogonProof_S;
129 typedef struct
131 uint8 cmd; //XFER_INITIATE
132 uint8 size; //strlen("Patch");
133 uint8 name[5];
134 uint64 file_size;
135 uint8 md5[MD5_DIGEST_LENGTH];
136 }XFER_INIT;
138 typedef struct
140 uint8 opcode;
141 uint16 data_size;
142 uint8 data[ChunkSize];
143 }XFER_DATA_STRUCT;
145 typedef struct
147 eAuthCmd cmd;
148 uint32 status;
149 bool (AuthSocket::*handler)(void);
150 }AuthHandler;
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)
154 #pragma pack()
155 #else
156 #pragma pack(pop)
157 #endif
159 /// Launch a thread to transfer a patch to the client
160 class PatcherRunnable: public ZThread::Runnable
162 public:
163 PatcherRunnable(class AuthSocket *);
164 void run();
166 private:
167 AuthSocket * mySocket;
170 typedef struct
172 uint8 md5[MD5_DIGEST_LENGTH];
173 }PATCH_INFO;
175 /// Caches MD5 hash of client patches present on the server
176 class Patcher
178 public:
179 typedef std::map<std::string, PATCH_INFO*> Patches;
180 ~Patcher();
181 Patcher();
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]);
187 private:
188 void LoadPatchesInfo();
189 Patches _patches;
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");
211 g.SetDword(7);
212 _authed = false;
213 pPatch=NULL;
216 /// Close patch file descriptor before leaving
217 AuthSocket::~AuthSocket()
219 if(pPatch)
220 fclose(pPatch);
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()
235 ///- Read the packet
236 TcpSocket::OnRead();
237 uint8 _cmd;
238 while (1)
240 if (!ibuf.GetLength())
241 return;
243 ///- Get the command out of it
244 ibuf.SoftRead((char *)&_cmd, 1);
245 size_t i;
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());
259 return;
261 break;
265 ///- Report unknown commands in the debug log
266 if (i==AUTH_TOTAL_COMMANDS)
268 DEBUG_LOG("[Auth] got unknown packet %u", (uint32)_cmd);
269 return;
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);
279 Sha1Hash I;
280 std::string sI = _login + ":" + password;
281 I.UpdateData(sI);
282 I.Finalize();
283 Sha1Hash sha;
284 sha.UpdateData(s.AsByteArray(), s.GetNumBytes());
285 sha.UpdateData(I.GetDigest(), 20);
286 sha.Finalize();
287 BigNumber x;
288 x.SetBinary(sha.GetDigest(), sha.GetLength());
289 v = g.ModExp(x, N);
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))
299 return false;
301 ///- Read the first 4 bytes (header) to get the length of the remaining of the packet
302 std::vector<uint8> buf;
303 buf.resize(4);
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))
310 return false;
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);
322 ByteBuffer pkt;
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
328 _safelogin=_login;
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])
337 valid_version=true;
338 break;
341 /// <ul><li> if this is a valid version
342 if(valid_version)
344 pkt << (uint8) AUTH_LOGON_CHALLENGE;
345 pkt << (uint8) 0x00;
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());
350 if(result)
352 pkt << (uint8)REALM_AUTH_ACCOUNT_BANNED;
353 sLog.outBasic("[AuthChallenge] Banned ip %s tries to login!",GetRemoteAddress().c_str ());
354 delete result;
356 else
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 ());
361 if( result )
363 ///- If the IP is 'locked', check that the player comes indeed from the correct IP address
364 bool locked = false;
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;
373 locked=true;
375 else
377 DEBUG_LOG("[AuthChallenge] Account IP matches");
380 else
382 DEBUG_LOG("[AuthChallenge] Account '%s' is not locked to ip", _login.c_str());
385 if (!locked)
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 ());
393 else
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;
400 //else
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);
406 b.SetRand(19 * 8);
407 BigNumber gmod=g.ModExp(b, N);
408 B = ((v * 3) + gmod) % N;
409 ASSERT(gmod.GetNumBytes() <= 32);
411 BigNumber unk3;
412 unk3.SetRand(16*8);
414 ///- Fill the response packet with the result
415 pkt << (uint8)REALM_AUTH_SUCCESS;
416 pkt.append(B.AsByteArray(), 32);
417 pkt << (uint8)1;
418 pkt.append(g.AsByteArray(), 1);
419 pkt << (uint8)32;
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
427 delete result;
429 else //no account
431 pkt<< (uint8) REALM_AUTH_NO_MATCH;
434 } //valid version
435 else
436 ///<li> else
438 ///- Check if we have the apropriate patch on the disk
439 char tmp[64];
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");
445 if(!pFile)
447 pkt << (uint8) AUTH_LOGON_CHALLENGE;
448 pkt << (uint8) 0x00;
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);
452 }else
453 { //have patch
454 pPatch=pFile;
455 XFER_INIT xferh;
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);
462 else
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));
479 return true;
482 /// </ul>
483 SendBuf((char *)pkt.contents(), pkt.size());
484 return true;
487 /// Logon Proof command handler
488 bool AuthSocket::_HandleLogonProof()
490 DEBUG_LOG("Entering _HandleLogonProof");
491 ///- Read the packet
492 if (ibuf.GetLength() < sizeof(sAuthLogonProof_C))
493 return false;
495 sAuthLogonProof_C lp;
496 ibuf.Read((char *)&lp, sizeof(sAuthLogonProof_C));
498 ///- Continue the SRP6 calculation based on data received from the client
499 BigNumber A;
500 A.SetBinary(lp.A, 32);
502 Sha1Hash sha;
503 sha.UpdateBigNumbers(&A, &B, NULL);
504 sha.Finalize();
505 BigNumber u;
506 u.SetBinary(sha.GetDigest(), 20);
507 BigNumber S = (A * (v.ModExp(u, N))).ModExp(b, N);
509 uint8 t[32];
510 uint8 t1[16];
511 uint8 vK[40];
512 memcpy(t, S.AsByteArray(), 32);
513 for (int i = 0; i < 16; i++)
515 t1[i] = t[i*2];
517 sha.Initialize();
518 sha.UpdateData(t1, 16);
519 sha.Finalize();
520 for (int i = 0; i < 20; i++)
522 vK[i*2] = sha.GetDigest()[i];
524 for (int i = 0; i < 16; i++)
526 t1[i] = t[i*2+1];
528 sha.Initialize();
529 sha.UpdateData(t1, 16);
530 sha.Finalize();
531 for (int i = 0; i < 20; i++)
533 vK[i*2+1] = sha.GetDigest()[i];
535 K.SetBinary(vK, 40);
537 uint8 hash[20];
539 sha.Initialize();
540 sha.UpdateBigNumbers(&N, NULL);
541 sha.Finalize();
542 memcpy(hash, sha.GetDigest(), 20);
543 sha.Initialize();
544 sha.UpdateBigNumbers(&g, NULL);
545 sha.Finalize();
546 for (int i = 0; i < 20; i++)
548 hash[i] ^= sha.GetDigest()[i];
550 BigNumber t3;
551 t3.SetBinary(hash, 20);
553 sha.Initialize();
554 sha.UpdateData(_login);
555 sha.Finalize();
556 BigNumber t4;
557 t4.SetBinary(sha.GetDigest(), 20);
559 sha.Initialize();
560 sha.UpdateBigNumbers(&t3, &t4, &s, &A, &B, &K, NULL);
561 sha.Finalize();
562 BigNumber M;
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
575 sha.Initialize();
576 sha.UpdateBigNumbers(&A, &M, &K, NULL);
577 sha.Finalize();
579 sAuthLogonProof_S proof;
580 memcpy(proof.M2, sha.GetDigest(), 20);
581 proof.cmd = AUTH_LOGON_PROOF;
582 proof.error = 0;
583 proof.unk2 = 0;
585 SendBuf((char *)&proof, sizeof(proof));
587 ///- Set _authed to true!
588 _authed = true;
590 else
592 char data[2]={AUTH_LOGON_PROOF,REALM_AUTH_NO_MATCH};
593 SendBuf(data,sizeof(data));
595 return true;
598 /// %Realm List command handler
599 bool AuthSocket::_HandleRealmList()
601 DEBUG_LOG("Entering _HandleRealmList");
602 if (ibuf.GetLength() < 5)
603 return false;
605 ibuf.Remove(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());
610 if(!result)
612 sLog.outError("[ERROR] user %s tried to login and we cannot find him in the database.",_login.c_str());
613 this->Close();
614 return false;
617 uint32 id = (*result)[0].GetUInt32();
618 std::string password = (*result)[1].GetCppString();
619 delete result;
621 ///- Circle through realms in the RealmList and construct the return packet (including # of user characters in each realm)
622 uint8 AmountOfCharacters = 0;
624 ByteBuffer pkt;
625 pkt << (uint32) 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;
632 pkt << i->first;
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);
638 if( result )
640 Field *fields = result->Fetch();
641 AmountOfCharacters = fields[0].GetUInt8();
642 delete result;
644 else
646 AmountOfCharacters = 0;
648 pkt << (uint8) AmountOfCharacters;
649 pkt << (uint8) i->second->timezone;
650 pkt << (uint8) 0;
652 pkt << (uint8) 0x0;
653 pkt << (uint8) 0x2;
655 ByteBuffer hdr;
656 hdr << (uint8) REALM_LIST;
657 hdr << (uint16)pkt.size();
658 hdr.append(pkt);
660 SendBuf((char *)hdr.contents(), hdr.size());
662 // Set check field before possable reloagin to realm
663 _SetVSFields(password);
664 return true;
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)");
675 return false;
678 ///- Launch a PatcherRunnable thread starting at given patch file offset
679 uint64 start;
680 ibuf.Remove(1);
681 ibuf.Read((char*)&start,sizeof(start));
682 fseek(pPatch,start,0);
684 ZThread::Thread u(new PatcherRunnable(this));
685 return true;
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?
698 SetCloseAndDelete();
700 return true;
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
709 fseek(pPatch,0,0);
711 ZThread::Thread u(new PatcherRunnable(this));
713 return true;
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)
724 mySocket=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
747 #ifndef _WIN32
748 #include <sys/dir.h>
749 #include <errno.h>
750 void Patcher::LoadPatchesInfo()
752 DIR * dirp;
753 //int errno;
754 struct dirent * dp;
755 dirp = opendir("./patches/");
756 if(!dirp)
757 return;
758 while (dirp)
760 errno = 0;
761 if ((dp = readdir(dirp)) != NULL)
763 int l=strlen(dp->d_name);
764 if(l<8)continue;
765 if(!memcmp(&dp->d_name[l-4],".mpq",4))
766 LoadPatchMD5(dp->d_name);
768 else
770 if(errno != 0)
772 closedir(dirp);
773 return;
775 break;
779 if(dirp)
780 closedir(dirp);
783 #else
784 void Patcher::LoadPatchesInfo()
786 WIN32_FIND_DATA fil;
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);
796 #endif
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/";
803 path += szFileName;
804 FILE * pPatch=fopen(path.c_str(),"rb");
805 sLog.outDebug("Loading patch info from %s\n",path.c_str());
806 if(!pPatch)
808 sLog.outError("Error loading patch %s\n",path.c_str());
809 return;
812 ///- Calculate the MD5 hash
813 MD5_CTX ctx;
814 MD5_Init(&ctx);
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);
822 delete [] buf;
823 fclose(pPatch);
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);
837 return true;
840 return false;
843 /// Launch the patch hashing mechanism on object creation
844 Patcher::Patcher()
846 LoadPatchesInfo();
849 /// Empty and delete the patch map on termination
850 Patcher::~Patcher()
852 for(Patches::iterator i = _patches.begin(); i != _patches.end(); i++ )
853 delete i->second;