mangosd是MaNGOS-Zero項目中的游戲邏輯進程,玩家一旦與realmd的keyexchange過程完成后(詳細內容見《
realmd認證登錄服務器(一):認證登錄基本流程》),便只與mangosd進行交互。而客戶端與realmd的連接也會在客戶端向mangosd發(fā)送enterworld之后斷開。
本文將介紹客戶端連接到mangosd后,mangosd認證客戶端合法性并最終建立RC4流加密的過程。具體過程如下:
(1) 客戶端與mangosd建立TCP連接后,mangosd會向客戶端發(fā)送消息SMSG_AUTH_CHALLENGE
1: int WorldSocket::open (void *a)
2: {
3: ........
4:
5: // Send startup packet.
6: WorldPacket packet (SMSG_AUTH_CHALLENGE, 4);
7: packet << m_Seed;
8: if (SendPacket (packet) == -1)
9: return -1;
10:
11: ........
12: }
m_Seed是一個隨機數(shù),每次客戶端連接上來的時生成一個新的隨機數(shù)(隨著WorldSocket的創(chuàng)建而初始化)。
(2)客戶端收到SMSG_AUTH_CHALLENGE消息后,知道服務器要求其提供身份認證信息,于是開始構造CMSG_AUTH_SESSION消息。(以下代碼并非客戶端真實代碼)
1: //client do auth
2: {
3: BigNumber clientSeed;
4: clientSeed.SetRand(4 * 8);
5: sha.Initialize();
6: sha.UpdateData("abu");
7: uint32 t = 0;
8: sha.UpdateData((uint8 *)&t, 4);
9: sha.UpdateBigNumbers(&clientSend, NULL);
10: sha.UpdateData((uint8 *)&serverSeed, 4);
11: sha.UpdateBigNumbers(&K, NULL);
12: sha.Finalize();
13:
14: uint32 unk2;
15: ByteBuffer pktbuf;
16: string account = "abu";
17: uint16 pktbuf_size = 4+4+4+account.length()+4+20;
18: EndianConvertReverse(pktbuf_size);
19: pktbuf << uint16(pktbuf_size);
20: pktbuf << uint32(CMSG_AUTH_SESSION);
21: pktbuf << uint32(5875); //build version
22: pktbuf << unk2;
23: pktbuf << account;
24: pktbuf.append(clientSeed.AsByteArray(4), 4);
25: pktbuf.append(sha.GetDigest(), 20);
26:
27: send((char const*)pktbuf.contents(), pktbuf.size());
28: }
其中最為關鍵的是構造20位的sha驗證密文M:
M = sha(t, account, clientSeed, serverSeed, K);
t為0;account是明文的用戶名;clientSeed是由客戶端生成的隨機數(shù),用于本次連接游戲session;serverSeed是SMSG_AUTH_CHALLENGE消息發(fā)過來的服務器隨機數(shù);K是之前和realmd交互做keyexchange時生成的,由服務器和客戶端分別進行計算,SRP6算法要求(保證)兩邊的計算結果一致,服務器端保存在realmd.account.sessionkey字段。
(3)服務器收到客戶端發(fā)來的CMSG_AUTH_SESSION,首先對收到的數(shù)據(jù)包進行分析,客戶端發(fā)來的數(shù)據(jù)包的包頭如下:
1: struct ClientPktHeader
2: {
3: uint16 size; //packet_size except itself
4: uint32 cmd; //opCode
5: };
收到客戶端發(fā)來的data,處理流程可以簡化為如下代碼:
int WorldSocket::handle_input (ACE_HANDLE)
{
……………
handle_input_missing_data()
{
handle_input_header();
handle_input_payload()
{
const int ret = ProcessIncoming (m_RecvWPct);
}
}
}
在ProcessIncoming()函數(shù)中使用switch case把客戶端發(fā)過來的不同的opcode定位到不同的處理函數(shù)中,而登錄認證過程需要定位到int WorldSocket::HandleAuthSession (WorldPacket& recvPacket)函數(shù)。
在HandleAuthSession()函數(shù)中,服務器以客戶端相同的方式計算sha密文,并和客戶端傳來的做比較,如果相同則認證通過,然后創(chuàng)建WorldSession實例,初始化m_Crypt成員,以便以后服務器和客戶端之間交互的RC4對稱加密使用。最后把新創(chuàng)建的WorldSession對象的m_Session添加到游戲世界中,添加完畢后,在游戲世界的主線程(Update線程)可以對該客戶端做相應的處理。
(4)HandleAuthSession()處理的最后會使用下面的代碼,進行判斷:如果session可以作為normal_session的而不是queue_session則發(fā)送SMSG_AUTH_RESPONSE消息,至此所有發(fā)送的消息都將進行RC4的流加密。
1: void World::AddSession_ (WorldSession* s)
2: {
3: ........
4:
5: if (pLimit > 0 && Sessions >= pLimit && s->GetSecurity () == SEC_PLAYER )
6: {
7: AddQueuedSession(s);
8: UpdateMaxSessionCounters();
9: DETAIL_LOG("PlayerQueue: Account id %u is in Queue Position (%u).", s->GetAccountId (), ++QueueSize);
10: return;
11: }
12:
13: // Checked for 1.12.2
14: WorldPacket packet(SMSG_AUTH_RESPONSE, 1 + 4 + 1 + 4);
15: packet << uint8 (AUTH_OK);
16: packet << uint32 (0); // BillingTimeRemaining
17: packet << uint8 (0); // BillingPlanFlags
18: packet << uint32 (0); // BillingTimeRested
19: s->SendPacket (&packet);
20:
21: ........
22: }
總結:
(1)realmd和mangosd在登錄認證過程中,相互之間基本不通信,通過MySQL來傳遞client認證所需的sessionkey。
(2)每次客戶端和mangosd之間認證時,各自生成一個隨機數(shù)Seed,保證在傳輸過程中隱藏sessionkey。