mangosd是MaNGOS-Zero項(xiàng)目中的游戲邏輯進(jìn)程,玩家一旦與realmd的keyexchange過(guò)程完成后(詳細(xì)內(nèi)容見(jiàn)《
realmd認(rèn)證登錄服務(wù)器(一):認(rèn)證登錄基本流程》),便只與mangosd進(jìn)行交互。而客戶(hù)端與realmd的連接也會(huì)在客戶(hù)端向mangosd發(fā)送enterworld之后斷開(kāi)。
本文將介紹客戶(hù)端連接到mangosd后,mangosd認(rèn)證客戶(hù)端合法性并最終建立RC4流加密的過(guò)程。具體過(guò)程如下:
(1) 客戶(hù)端與mangosd建立TCP連接后,mangosd會(huì)向客戶(hù)端發(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是一個(gè)隨機(jī)數(shù),每次客戶(hù)端連接上來(lái)的時(shí)生成一個(gè)新的隨機(jī)數(shù)(隨著WorldSocket的創(chuàng)建而初始化)。
(2)客戶(hù)端收到SMSG_AUTH_CHALLENGE消息后,知道服務(wù)器要求其提供身份認(rèn)證信息,于是開(kāi)始構(gòu)造CMSG_AUTH_SESSION消息。(以下代碼并非客戶(hù)端真實(shí)代碼)
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: }
其中最為關(guān)鍵的是構(gòu)造20位的sha驗(yàn)證密文M:
M = sha(t, account, clientSeed, serverSeed, K);
t為0;account是明文的用戶(hù)名;clientSeed是由客戶(hù)端生成的隨機(jī)數(shù),用于本次連接游戲session;serverSeed是SMSG_AUTH_CHALLENGE消息發(fā)過(guò)來(lái)的服務(wù)器隨機(jī)數(shù);K是之前和realmd交互做keyexchange時(shí)生成的,由服務(wù)器和客戶(hù)端分別進(jìn)行計(jì)算,SRP6算法要求(保證)兩邊的計(jì)算結(jié)果一致,服務(wù)器端保存在realmd.account.sessionkey字段。
(3)服務(wù)器收到客戶(hù)端發(fā)來(lái)的CMSG_AUTH_SESSION,首先對(duì)收到的數(shù)據(jù)包進(jìn)行分析,客戶(hù)端發(fā)來(lái)的數(shù)據(jù)包的包頭如下:
1: struct ClientPktHeader
2: {
3: uint16 size; //packet_size except itself
4: uint32 cmd; //opCode
5: };
收到客戶(hù)端發(fā)來(lái)的data,處理流程可以簡(jiǎn)化為如下代碼:
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把客戶(hù)端發(fā)過(guò)來(lái)的不同的opcode定位到不同的處理函數(shù)中,而登錄認(rèn)證過(guò)程需要定位到int WorldSocket::HandleAuthSession (WorldPacket& recvPacket)函數(shù)。
在HandleAuthSession()函數(shù)中,服務(wù)器以客戶(hù)端相同的方式計(jì)算sha密文,并和客戶(hù)端傳來(lái)的做比較,如果相同則認(rèn)證通過(guò),然后創(chuàng)建WorldSession實(shí)例,初始化m_Crypt成員,以便以后服務(wù)器和客戶(hù)端之間交互的RC4對(duì)稱(chēng)加密使用。最后把新創(chuàng)建的WorldSession對(duì)象的m_Session添加到游戲世界中,添加完畢后,在游戲世界的主線(xiàn)程(Update線(xiàn)程)可以對(duì)該客戶(hù)端做相應(yīng)的處理。
(4)HandleAuthSession()處理的最后會(huì)使用下面的代碼,進(jìn)行判斷:如果session可以作為normal_session的而不是queue_session則發(fā)送SMSG_AUTH_RESPONSE消息,至此所有發(fā)送的消息都將進(jìn)行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: }
總結(jié):
(1)realmd和mangosd在登錄認(rèn)證過(guò)程中,相互之間基本不通信,通過(guò)MySQL來(lái)傳遞client認(rèn)證所需的sessionkey。
(2)每次客戶(hù)端和mangosd之間認(rèn)證時(shí),各自生成一個(gè)隨機(jī)數(shù)Seed,保證在傳輸過(guò)程中隱藏sessionkey。