📄 usersession.cpp
字号:
//=========== (C) Copyright 2000 Valve, L.L.C. All rights reserved. ===========
//
// The copyright to the contents herein is the property of Valve, L.L.C.
// The contents may be used and/or copied only with the written permission of
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// Purpose:
//=============================================================================
#include "UserSession.h"
#include "Random.h"
#include "SessionManager.h"
#include "TrackerDatabaseManager.h"
#include "../public/ITrackerUserDatabase.h"
#include "../TrackerNET/TrackerNET_Interface.h"
#include "../TrackerNET/ReceiveMessage.h"
#include "../TrackerNET/SendMessage.h"
#include "DebugConsole_Interface.h"
#include "TrackerMessageFlags.h"
#include "TrackerProtocol.h"
#include "UtlMsgBuffer.h"
#include "TopologyManager.h"
#include "MemPool.h"
#include <assert.h>
#include <stdlib.h>
#include <time.h>
extern IDebugConsole *g_pConsole;
#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0]))
#ifndef min
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif
static const int STATUS_AWAY = 3;
static const int STATUS_INGAME = 4;
static const int STATUS_SNOOZE = 5;
static const int STATUS_OFFLINE = 0;
void v_strncpy(char *dest, const char *src, int bufsize);
// minimum build number of Client to be allowed to connect to server
static const int MINIMUM_BUILD_NUMBER = 1957;
// build numbers used for compatibility with old clients
static const int COMPATILIBITY_SERVERID_SUPPORT_VERSION_MIN = 1920;
// the number of seconds after missed heartbeat the user will be disconnected
static const float USER_TIMEOUT_SLOP = 30.0f;
// the minimum duration a firewall must be open to be an acceptable Client
static const float MINIMUM_FIRWALL_WINDOW_DURATION = 15.0f;
enum EAuthLevels
{
AUTHLEVEL_NONE = 0,
AUTHLEVEL_REQUESTINGAUTH = 3,
AUTHLEVEL_FULLAUTH = 10,
};
//-----------------------------------------------------------------------------
// Purpose: State list
//-----------------------------------------------------------------------------
enum UserState_t
{
STATE_NORMAL,
STATE_REQUESTINGAUTH,
STATE_GETFRIENDINFO,
STATE_SENDINGSTATUS,
STATE_EXCHANGESTATUS,
STATE_DISCONNECTINGUSER,
STATE_ADDWATCH,
STATE_CHECKMESSAGES,
STATE_SENDINGOFFLINESTATUS,
};
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CUserSession::CUserSession()
{
m_iSessionID = 0;
m_pMsgQueueFirst = NULL;
m_pMsgQueueLast = NULL;
Clear();
}
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CUserSession::~CUserSession()
{
}
//-----------------------------------------------------------------------------
// Purpose: Clears the session between user connections
//-----------------------------------------------------------------------------
void CUserSession::Clear()
{
m_iUserID = 0;
m_iStatus = 0;
m_iUserSessionState = USERSESSION_FREE;
m_pDB = NULL;
m_iBuildNum = 0;
m_iGameIP = 0;
m_iGamePort = 0;
m_szGameType[0] = 0;
m_szPassword[0] = 0;
m_szUserName[0] = 0;
m_flLastAccess = 0.0f;
m_flFirewallWindow = 9999999.0f;
m_bUpdateFirewallWindowToClient = false;
m_flTimeoutDelay = 0.0f;
FlushMessageQueue();
}
//-----------------------------------------------------------------------------
// Purpose: Wakes up the main thread
//-----------------------------------------------------------------------------
void CUserSession::WakeUp()
{
sessionmanager()->WakeUp();
}
//-----------------------------------------------------------------------------
// Purpose: called after a succesful login
//-----------------------------------------------------------------------------
void CUserSession::PostLogin()
{
g_pConsole->Print(4, "Logged in: %d\n", m_iUserID);
// set watches on all the servers that our friend resides
// get a list of all the friends we need to watch - this will initiate setting watches
m_pDB->User_GetFriendList(this, m_iUserID);
// tell other users that the friend has logged on
UpdateStatusToFriends();
// check to see if we have any messages waiting
CheckForMessages();
}
//-----------------------------------------------------------------------------
// Purpose: Checks to see if the current user session should be checked for timeout
//-----------------------------------------------------------------------------
bool CUserSession::CheckForTimeout(float currentTime)
{
if (m_iUserSessionState == USERSESSION_ACTIVE)
{
if ((m_flLastAccess + m_flTimeoutDelay) < currentTime && m_iStatus > 0)
{
// time elapsed between received notification from user too long, log them off
Logoff(true, true, false);
return true;
}
}
else
{
float timeoutTime = 0.0f;
if (m_iUserSessionState == USERSESSION_CONNECTING)
{
// timeout a connection after 20 seconds
timeoutTime = 20.0f;
}
/* unnecessary:
else if (m_iUserSessionState == USERSESSION_CONNECTFAILED)
{
timeoutTime = 0.0f;
}
*/
if (m_iUserSessionState == USERSESSION_FREE)
{
g_pConsole->Print(8, "!! Timing out already freed session\n");
}
if ((m_flLastAccess + timeoutTime) < currentTime)
{
// kill the user, no need to log them off since they were never logged in
sessionmanager()->FreeUserSession(this);
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Creates a message to be sent to the current user
//-----------------------------------------------------------------------------
ISendMessage *CUserSession::CreateUserMessage(int msgID)
{
ISendMessage *msg = net()->CreateMessage(msgID);
msg->SetSessionID(m_iSessionID);
msg->SetNetAddress(m_NetAddress);
msg->SetEncrypted(true);
return msg;
}
// message dispatch table
typedef void (CUserSession::*funcPtr)( IReceiveMessage * );
struct UserMsgDispatch_t
{
int msgName;
funcPtr msgFunc;
};
// map of networking messages -> functions
// ordered from most called to least called
static UserMsgDispatch_t g_UserMsgDispatch[] =
{
{ TCLS_HEARTBEAT, CUserSession::ReceivedMsg_Heartbeat },
{ TCLS_ROUTETOFRIEND, CUserSession::ReceivedMsg_RouteToFriend },
{ TCLS_MESSAGE, CUserSession::ReceivedMsg_Message },
{ TCLS_RESPONSE, CUserSession::ReceivedMsg_Response },
{ TCLS_FRIENDINFO, CUserSession::ReceivedMsg_FriendInfo },
{ TCLS_AUTHUSER, CUserSession::ReceivedMsg_AuthUser },
{ TCLS_REQAUTH, CUserSession::ReceivedMsg_ReqAuth },
{ TCLS_FRIENDSEARCH, CUserSession::ReceivedMsg_FindUser },
{ TCLS_SETINFO, CUserSession::ReceivedMsg_SetInfo },
};
// SQL DB Reply table
typedef void (CUserSession::*dbFuncPtr)(int , void *);
struct DBReplyMapItem_t
{
int cmdID;
int returnState;
dbFuncPtr msgFunc;
};
static DBReplyMapItem_t g_DBMsgDispatch[] =
{
{ CMD_LOGIN, STATE_NORMAL, CUserSession::DBMsg_Login },
{ CMD_LOGOFF, STATE_NORMAL, CUserSession::DBMsg_Logoff },
{ CMD_GETSESSIONINFO, STATE_SENDINGSTATUS, CUserSession::DBMsg_GetSessionInfo_SendingStatus },
{ CMD_GETSESSIONINFO, STATE_EXCHANGESTATUS, CUserSession::DBMsg_GetSessionInfo_ExchangeStatus },
{ CMD_GETSESSIONINFO, STATE_SENDINGOFFLINESTATUS, CUserSession::DBMsg_GetSessionInfo_SendingOfflineStatus },
{ CMD_GETSESSIONINFO, STATE_ADDWATCH, CUserSession::DBMsg_GetSessionInfo_AddWatch },
{ CMD_GETSESSIONINFO, STATE_DISCONNECTINGUSER,CUserSession::DBMsg_GetSessionInfo_DisconnectingUser },
{ CMD_GETSESSIONINFO, STATE_CHECKMESSAGES, CUserSession::DBMsg_GetSessionInfo_CheckMessages },
{ CMD_FINDUSERS, STATE_NORMAL, CUserSession::DBMsg_FindUsers },
{ CMD_GETINFO, STATE_NORMAL, CUserSession::DBMsg_GetInfo },
{ CMD_ISAUTHED, STATE_GETFRIENDINFO, CUserSession::DBMsg_GetFriendInfo_IsAuthed },
{ CMD_ISAUTHED, STATE_REQUESTINGAUTH, CUserSession::DBMsg_RequestAuth_IsAuthed },
{ CMD_GETWATCHERS, STATE_NORMAL, CUserSession::DBMsg_GetWatchers },
{ CMD_GETFRIENDLIST, STATE_NORMAL, CUserSession::DBMsg_GetFriendList },
{ CMD_GETFRIENDSTATUS, STATE_NORMAL, CUserSession::DBMsg_GetFriendStatus },
{ CMD_GETFRIENDSGAMESTATUS, STATE_NORMAL, CUserSession::DBMsg_GetFriendsGameStatus },
{ CMD_GETMESSAGE, STATE_NORMAL, CUserSession::DBMsg_GetMessage },
{ CMD_DELETEMESSAGE, STATE_NORMAL, CUserSession::DBMsg_DeleteMessage },
};
//-----------------------------------------------------------------------------
// Purpose: Handles a user session receiving a network message
//-----------------------------------------------------------------------------
void CUserSession::ReceivedMessage(IReceiveMessage *userMsg, float currentTime)
{
// mark the new access
m_flLastAccess = currentTime;
// update the user port
if (userMsg->NetAddress().Port() != m_NetAddress.Port())
{
g_pConsole->Print(9, "++++ User port changed, %s -> %d\n", m_NetAddress.ToStaticString(), userMsg->NetAddress().Port());
m_NetAddress.SetPort(userMsg->NetAddress().Port());
}
// send all the queued messages
SendQueuedMessages();
// simple dispatch
// loop through the array and find the right message
int arraySize = ARRAYSIZE(g_UserMsgDispatch);
int msgName = userMsg->GetMsgID();
for (int i = 0; i < arraySize; i++)
{
if (msgName == g_UserMsgDispatch[i].msgName)
{
(this->*g_UserMsgDispatch[i].msgFunc)( userMsg );
return;
}
}
// invalid message
g_pConsole->Print(5, "* Unhandled message '%d'\n", msgName);
}
//-----------------------------------------------------------------------------
// Purpose: Increments the sessionID stamp
// Input : baseSessionID - basic session id that occupies the top 16 bits
// of the sessionID number
//-----------------------------------------------------------------------------
void CUserSession::CreateNewSessionID(int baseSessionID)
{
// mask off the base id
unsigned int numericID = (m_iSessionID & 0x0000FFFF);
// incremement
numericID++;
// mask again (to remove any overflow)
numericID &= 0x0000FFFF;
// reset the base id
m_iSessionID = numericID | (baseSessionID << 16);
}
//-----------------------------------------------------------------------------
// LOGGING INTO A SESSION
/*
Client -> sends a message requesting a log in
+ 'login'
- 'uid'
- 'password'
- 'email'
server -> allocates an new session
-> generates new sessionID and random challenge key
-> sends Client a reply challenging them to respond
+ 'challenge'
- 'sessionID'
- 'challenge key'
Client -> replies to the challenge
+ 'response'
- 'sessionID'
- 'challenge key'
server -> validates the player in the database to see if they exist
-> sends back a login ack if successful
+ 'LoginOK'
- 'sessionID'
-> sends status of friends to Client
-> sends Client status to friends
Client -> enters logged in mode
-> displays friends status & messages
*/
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: Attempts to log a user into the server
//-----------------------------------------------------------------------------
bool CUserSession::ReceivedMsg_Login(IReceiveMessage *loginMsg)
{
unsigned int userID;
char szEmail[128];
char szPassword[32];
int iStatus;
int iFirewallWindow = 0;
loginMsg->ReadUInt("uid", userID);
loginMsg->ReadString("email", szEmail, sizeof(szEmail));
loginMsg->ReadString("password", szPassword, sizeof(szPassword));
loginMsg->ReadInt("status", iStatus);
loginMsg->ReadInt("FirewallWindow", iFirewallWindow);
if (userID < 1)
{
g_pConsole->Print(4, "User (%s) with bad userID %d attempted to log into server\n", loginMsg->NetAddress().ToStaticString(), userID);
return false;
}
// get our database handle
m_pDB = g_pDataManager->TrackerUser(userID);
// make sure this is a valid userID
if (!m_pDB)
{
// bad user ID
g_pConsole->Print(4, "User (%s) with bad userID %d attempted to log into server\n", loginMsg->NetAddress().ToStaticString(), m_iUserID);
return false;
}
// if this user is already logged in to the server from this IP, override previous session
CUserSession *oldUserSession = sessionmanager()->GetUserSessionByID(userID);
if (oldUserSession)
{
if (oldUserSession->IP() == loginMsg->NetAddress().IP() || oldUserSession->State() != USERSESSION_ACTIVE)
{
// same IP address, simply kill the previous session and continue
g_pConsole->Print(0, "Forcing off previous instance of user '%d'\n", m_iUserID);
sessionmanager()->ForceDisconnectUser(userID);
}
else if (oldUserSession->State() == USERSESSION_ACTIVE)
{
// begin logging off the old user, quit ourselves
oldUserSession->Logoff(true, false, true);
return false;
}
}
// save off data
m_iUserID = userID;
m_iStatus = iStatus;
v_strncpy(m_szPassword, szPassword, sizeof(m_szPassword));
m_iUserSessionState = USERSESSION_CONNECTING;
m_NetAddress = loginMsg->NetAddress();
m_flTimeoutDelay = (6 * 60.0f); // 6 minute timeout by default
if (iFirewallWindow)
{
m_flFirewallWindow = (float)iFirewallWindow;
}
else
{
// no firewall info sent, mark it as wide as possible
m_flFirewallWindow = 999999.0f;
}
// set the current access time
m_flLastAccess = sessionmanager()->GetCurrentTime();
// build a reply
ISendMessage *replyMsg = net()->CreateReply(TSVC_CHALLENGE, loginMsg );
replyMsg->SetEncrypted(true);
// session ID is 0 for this message, indicating a non-validated packet
replyMsg->SetSessionID( 0 );
// generate a random challenge key (that the Client will have to respond with correctly)
m_iChallengeKey = RandomLong(1, MAX_RANDOM_RANGE);
// write out the reply
replyMsg->WriteUInt("sessionID", m_iSessionID);
replyMsg->WriteInt("challenge", m_iChallengeKey);
// this could be changed to unreliable for optimization
SendMessage(replyMsg, NET_RELIABLE /*NET_UNRELIABLE*/);
// g_pConsole->Print(4, "Sending challenge for login request from '%s'\n", szEmail);
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Response to a login challenge
// If this response to the challenge is correct, then fully log the user
// into the database
// Invalid 'response' messages receive no reply (since it's then a security problem)
//-----------------------------------------------------------------------------
void CUserSession::ReceivedMsg_Response(IReceiveMessage *pMsg)
{
unsigned int msgSessionID = pMsg->SessionID();
unsigned int sessionID;
int challenge, status, buildNum, heartBeatRateMillis;
pMsg->ReadInt("challenge", challenge);
pMsg->ReadUInt("sessionID", sessionID);
pMsg->ReadInt("status", status);
pMsg->ReadInt("build", buildNum);
pMsg->ReadInt("hrate", heartBeatRateMillis);
// g_pConsole->Print(4, "Received Response from user (%d) sessionID (%d)\n", m_iUserID, m_iSessionID);
// Validate
if (msgSessionID != sessionID || m_iChallengeKey != challenge)
{
g_pConsole->Print(6, "Response ignored, invalid challenge or sessionID\n");
m_iUserSessionState = USERSESSION_CONNECTFAILED;
sessionmanager()->FreeUserSession(this);
return;
}
// check build number
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -