mirror of
https://github.com/SWG-Source/src.git
synced 2026-07-28 22:15:49 -04:00
317 lines
13 KiB
C++
Executable File
317 lines
13 KiB
C++
Executable File
// ClientConnection.cpp
|
|
// copyright 2000 Verant Interactive
|
|
// Author: Justin Randall
|
|
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
#include "FirstLoginServer.h"
|
|
#include "ClientConnection.h"
|
|
|
|
#include "DatabaseConnection.h"
|
|
#include "ConfigLoginServer.h"
|
|
#include "SessionApiClient.h"
|
|
#include "sharedLog/Log.h"
|
|
#include "sharedNetworkMessages/ClientLoginMessages.h"
|
|
#include "sharedNetworkMessages/DeleteCharacterMessage.h"
|
|
#include "sharedNetworkMessages/DeleteCharacterReplyMessage.h"
|
|
#include "sharedNetworkMessages/ErrorMessage.h"
|
|
#include "sharedNetworkMessages/GenericValueTypeMessage.h"
|
|
#include "sharedNetworkMessages/LoginEnumCluster.h"
|
|
|
|
#include "sharedFoundation/CrcConstexpr.hpp"
|
|
|
|
#include "webAPI.h"
|
|
|
|
using namespace StellaBellum;
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
ClientConnection::ClientConnection(UdpConnectionMT *u, TcpClient *t)
|
|
: ServerConnection(u, t), m_clientId(0), m_isValidated(false), m_isSecure(false), m_adminLevel(-1),
|
|
m_stationId(0), m_requestedAdminSuid(0), m_gameBits(0), m_subscriptionBits(0),
|
|
m_waitingForCharacterLoginDeletion(false), m_waitingForCharacterClusterDeletion(false) {
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
ClientConnection::~ClientConnection() {
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
void ClientConnection::onConnectionClosed() {
|
|
// client has disconnected
|
|
if (m_stationId) {
|
|
DEBUG_REPORT_LOG(true, ("Client %lu disconnected\n", m_stationId));
|
|
LOG("LoginClientConnection", ("onConnectionClosed() for stationId (%lu) at IP (%s)", m_stationId, getRemoteAddress().c_str()));
|
|
}
|
|
|
|
LoginServer::getInstance().removeClient(m_clientId);
|
|
|
|
/* if ((ConfigLoginServer::getValidateStationKey() || ConfigLoginServer::getDoSessionLogin()) && !m_isValidated) {
|
|
SessionApiClient *session = LoginServer::getInstance().getSessionApiClient();
|
|
if (session) {
|
|
session->dropClient(this);
|
|
}
|
|
}*/
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
void ClientConnection::onConnectionOpened() {
|
|
m_clientId = LoginServer::getInstance().addClient(*this);
|
|
setOverflowLimit(ConfigLoginServer::getClientOverflowLimit());
|
|
|
|
LOG("LoginClientConnection", ("onConnectionOpened() for stationId (%lu) at IP (%s)", m_stationId, getRemoteAddress().c_str()));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
void ClientConnection::onReceive(const Archive::ByteStream &message) {
|
|
try {
|
|
//Handle all client messages here. Do not forward out.
|
|
Archive::ReadIterator ri = message.begin();
|
|
GameNetworkMessage m(ri);
|
|
ri = message.begin();
|
|
|
|
const uint32 messageType = m.getType();
|
|
|
|
//Validation check
|
|
if (!getIsValidated() && messageType != constcrc("LoginClientId")) {
|
|
//Receiving message from unvalidated client. Pitch it.
|
|
DEBUG_WARNING(true, ("Received %s message from unknown, unvalidated client", m.getCmdName().c_str()));
|
|
return;
|
|
}
|
|
|
|
switch (messageType) {
|
|
case constcrc("LoginClientId") : {
|
|
// send the client the server "now" Epoch time so that the
|
|
// client has an idea of how much difference there is between
|
|
// the client's Epoch time and the server Epoch time
|
|
GenericValueTypeMessage <int32> const serverNowEpochTime("ServerNowEpochTime", static_cast<int32>(::time(nullptr)));
|
|
send(serverNowEpochTime, true);
|
|
|
|
LoginClientId id(ri);
|
|
|
|
// verify version
|
|
#if PRODUCTION == 1
|
|
if(!ConfigLoginServer::getValidateClientVersion() || id.getVersion() == GameNetworkMessage::NetworkVersionId)
|
|
{
|
|
validateClient(id.getId(), id.getKey());
|
|
}
|
|
else
|
|
{
|
|
LOG("CustomerService", ("Login:LoginServer dropping client (stationId=[%lu], ip=[%s], id=[%s], key=[%s], version=[%s]) because of network version mismatch (required version=[%s])", m_stationId, getRemoteAddress().c_str(), id.getId().c_str(), id.getKey().c_str(), id.getVersion().c_str(), GameNetworkMessage::NetworkVersionId.c_str()));
|
|
// disconnect is handled on the client side, as soon as it recieves this message
|
|
#if _DEBUG
|
|
LoginIncorrectClientId incorrectId(GameNetworkMessage::NetworkVersionId, ApplicationVersion::getInternalVersion());
|
|
#else
|
|
LoginIncorrectClientId incorrectId("", "");
|
|
#endif // _DEBUG
|
|
send(incorrectId, true);
|
|
}
|
|
#else
|
|
validateClient(id.getId(), id.getKey());
|
|
#endif // PRODUCTION == 1
|
|
|
|
break;
|
|
}
|
|
case constcrc("RequestExtendedClusterInfo") : {
|
|
LoginServer::getInstance().sendExtendedClusterInfo(*this);
|
|
break;
|
|
}
|
|
case constcrc("DeleteCharacterMessage") : {
|
|
DeleteCharacterMessage msg(ri);
|
|
std::vector<NetworkId>::const_iterator f = std::find(m_charactersPendingDeletion.begin(), m_charactersPendingDeletion.end(), msg.getCharacterId());
|
|
if ((m_waitingForCharacterLoginDeletion || m_waitingForCharacterClusterDeletion) &&
|
|
f != m_charactersPendingDeletion.end()) {
|
|
DeleteCharacterReplyMessage reply(DeleteCharacterReplyMessage::rc_ALREADY_IN_PROGRESS);
|
|
send(reply, true);
|
|
} else {
|
|
if (LoginServer::getInstance().deleteCharacter(msg.getClusterId(), msg.getCharacterId(), getStationId())) {
|
|
m_waitingForCharacterLoginDeletion = true;
|
|
m_waitingForCharacterClusterDeletion = true;
|
|
m_charactersPendingDeletion.push_back(msg.getCharacterId());
|
|
} else {
|
|
DeleteCharacterReplyMessage reply(DeleteCharacterReplyMessage::rc_CLUSTER_DOWN);
|
|
send(reply, true);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} catch (const Archive::ReadException &readException) {
|
|
WARNING(true, ("Archive read error (%s) on message from client. Disconnecting client.", readException.what()));
|
|
disconnect();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// originally was used to validate station API credentials, now uses our custom api
|
|
void ClientConnection::validateClient(const std::string &id, const std::string &key) {
|
|
bool authOK = false;
|
|
bool testMode = false;
|
|
static const std::string authURL(ConfigLoginServer::getExternalAuthUrl());
|
|
static const bool useSimpleAuth = ConfigLoginServer::getUseSimpleAuth();
|
|
|
|
std::string uname;
|
|
std::string sessionID;
|
|
|
|
StationId user_id;
|
|
|
|
if (!authURL.empty()) {
|
|
// create the object
|
|
webAPI api(authURL);
|
|
|
|
if (useSimpleAuth) {
|
|
if (!user_id) {
|
|
uname = id;
|
|
|
|
if (uname.length() > MAX_ACCOUNT_NAME_LENGTH) {
|
|
uname.resize(MAX_ACCOUNT_NAME_LENGTH);
|
|
}
|
|
|
|
// TODO: maybe make this a configurable, whether to be case insensitive or not...
|
|
// for now we'll assume that simple auth users don't care about case
|
|
std::transform(uname.begin(), uname.end(), uname.begin(), ::tolower);
|
|
|
|
user_id = std::hash < std::string > {}(uname.c_str());
|
|
}
|
|
|
|
std::ostringstream postBuf;
|
|
postBuf << "user_name=" << id << "&user_password=" << key << "&stationID=" << user_id << "&ip=" << getRemoteAddress();
|
|
std::string postData = postBuf.str();
|
|
|
|
bool done = api.setData(postData);
|
|
if (done) {
|
|
done = api.submit(1,1,1);
|
|
}
|
|
|
|
if (done) {
|
|
std::string response = api.getString("message");
|
|
|
|
if (response == "success") {
|
|
authOK = true;
|
|
} else {
|
|
ErrorMessage err("Login Failed", response);
|
|
this->send(err, true);
|
|
}
|
|
} else {
|
|
ErrorMessage err("Failed to contact auth provider", "Please contact a server admin");
|
|
this->send(err, true);
|
|
}
|
|
} else {
|
|
// add our data
|
|
// it's the burden of the auth system to trim and lowercase the username, unlike simpleAuth
|
|
api.addJsonData<std::string>("user_name", id);
|
|
api.addJsonData<std::string>("user_password", key);
|
|
api.addJsonData<std::string>("ip", getRemoteAddress());
|
|
|
|
if (api.submit()) {
|
|
bool status = api.getNullableValue<bool>("status");
|
|
uname = api.getString("username");
|
|
sessionID = api.getString("session_key");
|
|
|
|
if (status && !sessionID.empty() && !uname.empty()) {
|
|
authOK = true;
|
|
|
|
if (!ConfigLoginServer::getUseOldSuidGenerator()) {
|
|
user_id = static_cast<StationId>(api.getNullableValue<StationId>("user_id"));
|
|
} else {
|
|
if (uname.length() > MAX_ACCOUNT_NAME_LENGTH) {
|
|
uname.resize(MAX_ACCOUNT_NAME_LENGTH);
|
|
}
|
|
|
|
user_id = std::hash < std::string > {}(uname.c_str());
|
|
}
|
|
} else {
|
|
std::string msg(api.getString("message"));
|
|
if (msg.empty()) {
|
|
msg = "Invalid username or password.";
|
|
}
|
|
|
|
ErrorMessage err("Login Failed", msg);
|
|
this->send(err, true);
|
|
}
|
|
} else {
|
|
ErrorMessage err("Login Failed", "Could not connect to remote.");
|
|
this->send(err, true);
|
|
}
|
|
}
|
|
} else {
|
|
// test mode
|
|
authOK = true;
|
|
testMode = true;
|
|
uname = id;
|
|
|
|
if (uname.length() > MAX_ACCOUNT_NAME_LENGTH) {
|
|
uname.resize(MAX_ACCOUNT_NAME_LENGTH);
|
|
}
|
|
|
|
user_id = std::hash < std::string > {}(uname.c_str());
|
|
}
|
|
|
|
if (authOK) {
|
|
m_stationId = user_id;
|
|
|
|
if (!testMode) {
|
|
REPORT_LOG(true, ("Client connected. Username: %s (%i) \n", uname.c_str(), user_id));
|
|
|
|
LoginServer::getInstance().onValidateClient(m_stationId, uname, this, true, sessionID.c_str(), 0xFFFFFFFF, 0xFFFFFFFF);
|
|
} else {
|
|
LoginServer::getInstance().onValidateClient(m_stationId, uname, this, true, nullptr, 0xFFFFFFFF, 0xFFFFFFFF);
|
|
}
|
|
LOG("LoginClientConnection", ("validateClient() for stationId (%i) at IP (%s), id (%s)", user_id, getRemoteAddress().c_str(), uname.c_str()));
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* The character has been deleted from the login database. 1/2 of what is
|
|
* required for character deletion. If the character has already been deleted
|
|
* from the cluster, send the reply message to the client.
|
|
*/
|
|
void ClientConnection::onCharacterDeletedFromLoginDatabase(const NetworkId &characterId) {
|
|
m_waitingForCharacterLoginDeletion = false;
|
|
if (!m_waitingForCharacterClusterDeletion) {
|
|
std::vector<NetworkId>::iterator f = std::find(m_charactersPendingDeletion.begin(), m_charactersPendingDeletion.end(), characterId);
|
|
if (f != m_charactersPendingDeletion.end()) {
|
|
m_charactersPendingDeletion.erase(f);
|
|
}
|
|
|
|
DeleteCharacterReplyMessage reply(DeleteCharacterReplyMessage::rc_OK);
|
|
send(reply, true);
|
|
LOG("CustomerService", ("Player:deleted character %s for stationId %u at IP: %s", characterId.getValueString().c_str(), m_stationId, getRemoteAddress().c_str()));
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void ClientConnection::onCharacterDeletedFromCluster(const NetworkId &characterId) {
|
|
m_waitingForCharacterClusterDeletion = false;
|
|
if (!m_waitingForCharacterLoginDeletion) {
|
|
std::vector<NetworkId>::iterator f = std::find(m_charactersPendingDeletion.begin(), m_charactersPendingDeletion.end(), characterId);
|
|
if (f != m_charactersPendingDeletion.end()) {
|
|
m_charactersPendingDeletion.erase(f);
|
|
|
|
// TODO: send api request and decrement # characters on this account/subaccount
|
|
}
|
|
|
|
DeleteCharacterReplyMessage reply(DeleteCharacterReplyMessage::rc_OK);
|
|
send(reply, true);
|
|
LOG("CustomerService", ("Player:deleted character %s for stationId %u at IP: %s", characterId.getValueString().c_str(), m_stationId, getRemoteAddress().c_str()));
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
StationId ClientConnection::getRequestedAdminSuid() const {
|
|
return m_requestedAdminSuid;
|
|
}
|
|
|
|
// ======================================================================
|
|
|