// ClientConnection.cpp // copyright 2001 Verant Interactive //----------------------------------------------------------------------- #include "FirstConnectionServer.h" #include "ClientConnection.h" #include "Archive/ByteStream.h" #include "ChatServerConnection.h" #include "CustomerServiceConnection.h" #include "ConfigConnectionServer.h" #include "ConnectionServer.h" #include "GameConnection.h" #include "SessionApiClient.h" #include "UnicodeUtils.h" #include "serverKeyShare/KeyShare.h" #include "serverNetworkMessages/CentralConnectionServerMessages.h" #include "serverNetworkMessages/ChatDisconnectAvatar.h" #include "serverNetworkMessages/GameConnectionServerMessages.h" #include "serverNetworkMessages/RandomName.h" #include "serverNetworkMessages/RequestGameServerForLoginMessage.h" #include "serverNetworkMessages/ValidateAccountMessage.h" #include "serverNetworkMessages/ValidateCharacterForLoginMessage.h" #include "serverNetworkMessages/VerifyAndLockName.h" #include "serverUtility/AdminAccountManager.h" #include "sharedFoundation/CalendarTime.h" #include "sharedFoundation/Clock.h" #include "sharedFoundation/Crc.h" #include "sharedGame/PlatformFeatureBits.h" #include "sharedLog/Log.h" #include "sharedNetwork/Connection.h" #include "sharedNetworkMessages/ClientCentralMessages.h" #include "sharedNetworkMessages/ClientPermissionsMessage.h" #include "sharedNetworkMessages/AppendCommentMessage.h" #include "sharedNetworkMessages/CancelTicketMessage.h" #include "sharedNetworkMessages/ChatEnterRoom.h" #include "sharedNetworkMessages/ChatEnterRoomById.h" #include "sharedNetworkMessages/ChatEnum.h" #include "sharedNetworkMessages/ChatOnEnteredRoom.h" #include "sharedNetworkMessages/ChatPersistentMessageToServer.h" #include "sharedNetworkMessages/ChatQueryRoom.h" #include "sharedNetworkMessages/ConnectPlayerMessage.h" #include "sharedNetworkMessages/CreateTicketMessage.h" #include "sharedNetworkMessages/GenericValueTypeMessage.h" #include "sharedNetworkMessages/GetTicketsMessage.h" #include "sharedNetworkMessages/NewTicketActivityMessage.h" #include "sharedNetworkMessages/ErrorMessage.h" #include "sharedNetworkMessages/HeartBeat.h" #include "Session/LoginAPI/Client.h" #include "UdpLibrary.h" #include "sharedFoundation/CrcConstexpr.hpp" #include #include "webAPI.h" using namespace StellaBellum; //----------------------------------------------------------------------- namespace ClientConnectionNamespace { unsigned long gs_receiveDelayMaxMs = 16384; } using namespace ClientConnectionNamespace; //----------------------------------------------------------------------- std::map ClientConnection::sm_outgoingBytesMap_Working; // working stats that will rotate after 1 minute std::map ClientConnection::sm_outgoingBytesMap_Stats; // computed stats from the last minute uint32 ClientConnection::sm_outgoingBytesMap_Worktime = 0; // time we started filling in the working map //----------------------------------------------------------------------- ClientConnection::ClientConnection(UdpConnectionMT *u, TcpClient *t) : ServerConnection(u, t), m_accountName(""), m_canCreateRegularCharacter(false), m_canCreateJediCharacter(false), m_hasRequestedCharacterCreate(false), m_hasCreatedCharacter(false), m_pendingCharacterCreate(nullptr), m_canSkipTutorial(false), m_characterId(NetworkId::cms_invalid), m_characterName(), m_startPlayTime(0), m_lastActiveTime(0), m_activePlayTimeDuration(0), m_client(0), m_containerId(NetworkId::cms_invalid), m_featureBitsGame(0), m_featureBitsSubscription(0), m_hasBeenSentToGameServer(false), m_hasBeenValidated(false), m_hasSelectedCharacter(false), m_isSecure(false), m_isAdminAccount(false), m_hasCSLoggedAccountFeatureIds(false), m_suid(0), m_requestedSuid(0), m_usingAdminLogin(false), m_targetCoordinates(), m_targetScene(""), m_validatingCharacter(false), m_receiveHistoryBytes(0), m_receiveHistoryPackets(0), m_receiveHistoryMs(0), m_receiveLastTimeMs(0), m_sendLastTimeMs(0), m_sessionId(""), m_sessionValidated(false), m_connectionServerLag(0), m_gameServerLag(0), m_countSpamLimitResetTime(0), m_entitlementTotalTime(0), m_entitlementEntitledTime(0), m_entitlementTotalTimeSinceLastLogin(0), m_entitlementEntitledTimeSinceLastLogin(0), m_buddyPoints(0), m_sendToStarport(false), m_pendingChatEnterRoomRequests(), m_pendingChatQueryRoomRequests() { static const std::string loginTrace("TRACE_LOGIN"); LOG(loginTrace, ("new ClientConnection")); setNoDataTimeout(600000); } //----------------------------------------------------------------------- ClientConnection::~ClientConnection() { bool hasBeenKicked = false; if (ConnectionServer::getClientConnection(m_suid) == this) { ConnectionServer::removeConnectedCharacter(m_suid); } if (m_client) { hasBeenKicked = m_client->hasBeenKicked(); delete m_client; m_client = nullptr; } // tell Session to stop recording play time for the character if (m_hasBeenValidated && m_sessionValidated && ConnectionServer::getSessionApiClient() && (m_lastActiveTime > 0) && ConfigConnectionServer::getSessionRecordPlayTime()) { LOG("CustomerService", ("Login:%s calling SessionStopPlay() for %s/%s/%s (%s). Active play time: %s", ClientConnection::describeAccount(this).c_str(), this->getSessionId().c_str(), ConfigConnectionServer::getClusterName(), this->getCharacterName().c_str(), this->getCharacterId().getValueString().c_str(), this->getCurrentActivePlayTimeDuration().c_str())); // log total active play time for the session to the balance log LOG("GameBalance", ("balancelog:%s calling SessionStopPlay() for %s/%s/%s (%s). Active play time: %s", ClientConnection::describeAccount(this).c_str(), this->getSessionId().c_str(), ConfigConnectionServer::getClusterName(), this->getCharacterName().c_str(), this->getCharacterId().getValueString().c_str(), this->getActivePlayTimeDuration().c_str())); ConnectionServer::getSessionApiClient()->stopPlay(*this); } if (ConnectionServer::getSessionApiClient()) { ConnectionServer::getSessionApiClient()->dropClient(this, hasBeenKicked); } std::map < unsigned long, GameClientMessage * > ::const_iterator iter; for (iter = m_pendingChatEnterRoomRequests.begin(); iter != m_pendingChatEnterRoomRequests.end(); ++iter) { delete iter->second; } m_pendingChatEnterRoomRequests.clear(); for (iter = m_pendingChatQueryRoomRequests.begin(); iter != m_pendingChatQueryRoomRequests.end(); ++iter) { delete iter->second; } m_pendingChatQueryRoomRequests.clear(); delete m_pendingCharacterCreate; m_pendingCharacterCreate = nullptr; } //----------------------------------------------------------------------- const NetworkId &ClientConnection::getCharacterId() const { return m_characterId; } //----------------------------------------------------------------------- const std::string &ClientConnection::getCharacterName() const { return m_characterName; } //----------------------------------------------------------------------- std::string ClientConnection::getPlayTimeDuration() const { int playTimeDuration = 0; if (m_startPlayTime > 0) { playTimeDuration = static_cast(::time(nullptr) - m_startPlayTime); } return CalendarTime::convertSecondsToHMS(static_cast(playTimeDuration)); } //----------------------------------------------------------------------- std::string ClientConnection::getActivePlayTimeDuration() const { int activePlayTimeDuration = static_cast(m_activePlayTimeDuration); if (m_lastActiveTime > 0) { activePlayTimeDuration += static_cast(::time(nullptr) - m_lastActiveTime); } return CalendarTime::convertSecondsToHMS(static_cast(activePlayTimeDuration)); } //----------------------------------------------------------------------- std::string ClientConnection::getCurrentActivePlayTimeDuration() const { int activePlayTimeDuration = 0; if (m_lastActiveTime > 0) { activePlayTimeDuration = static_cast(::time(nullptr) - m_lastActiveTime); } return CalendarTime::convertSecondsToHMS(static_cast(activePlayTimeDuration)); } //----------------------------------------------------------------------- void ClientConnection::sendPlayTimeInfoToGameServer() const { if (m_client && m_client->getGameConnection()) { // update the game server with play time info GenericValueTypeMessage < std::pair < int32, std::pair < int32, unsigned long > > > const msgPlayTimeInfo( "UpdateSessionPlayTimeInfo", std::make_pair(static_cast(m_startPlayTime), std::make_pair(static_cast(m_lastActiveTime), m_activePlayTimeDuration))); std::vector v; v.push_back(m_client->getNetworkId()); GameClientMessage const gcm(v, true, msgPlayTimeInfo); m_client->getGameConnection()->send(gcm, true); } } // ---------------------------------------------------------------------- void ClientConnection::handleSelectCharacterMessage(const SelectCharacter &msg) { if (!m_sessionValidated) { } //Only accept this message from clients who have been validated and //haven't already selected. if (m_hasSelectedCharacter || m_validatingCharacter || !m_hasBeenValidated || !m_sessionValidated) { if (m_hasSelectedCharacter) { LOG("TraceCharacterSelection", ("%d cannot select a character because the client has already selected a character", getSUID())); } if (m_validatingCharacter) { LOG("TraceCharacterSelection", ("%d cannot select a character because the client has not yet received validation", getSUID())); } if (!m_hasBeenValidated) { LOG("TraceCharacterSelection", ("%d cannot select a character because the client has not been validated", getSUID())); } if (!m_sessionValidated) { LOG("TraceCharacterSelection", ("%d cannot select a character because the client has not been session validated", getSUID())); } return; } m_validatingCharacter = true; // The client is picking a character from the list the Login Server gave him. // But we don't trust him not to cheat, so we double-check that he really // owns the character he selected. ValidateCharacterForLoginMessage vclm(getSUID(), msg.getId()); ConnectionServer::sendToCentralProcess(vclm); LOG("TraceCharacterSelection", ("%d selected %s for login. Sending a validation request to CentralServer to verify this client can use this character", getSUID(), msg.getId().getValueString().c_str())); } // ---------------------------------------------------------------------- /** * We got a message telling us what game server to use for the player (who * is in the process of logging in). * Connect the player with the game server. */ void ClientConnection::handleGameServerForLoginMessage(uint32 serverId) { DEBUG_WARNING(serverId == 0, ("Got handleGameServerForLoginMessage with serverId=0.\n")); IGNORE_RETURN(sendToGameServer(serverId)); } //---------------------------------------------------------------------- /** * Examine the ID the client sends us. If it looks OK, send it to Central * to be validated. Central will reply with a list of permissions. * @see onIdValidated */ void ClientConnection::handleClientIdMessage(const ClientIdMsg &msg) { //Only check clients that have not been validated. if (m_hasBeenValidated) { return; } DEBUG_FATAL(m_hasSelectedCharacter, ("Trying to validate a client who already has a character selected.\n")); bool result = false; m_gameBitsToClear = msg.getGameBitsToClear(); if (msg.getTokenSize() > 0) { Archive::ByteStream t(msg.getToken(), msg.getTokenSize()); Archive::ReadIterator ri(t); KeyShare::Token token(ri); char sessionId[apiSessionIdWidth]; if (!ConfigConnectionServer::getValidateStationKey()) { // get SUID from token result = ConnectionServer::decryptToken(token, m_suid, m_isSecure, m_accountName); } else { result = ConnectionServer::decryptToken(token, sessionId, m_requestedSuid); } static const std::string sessURL(ConfigConnectionServer::getSessionURL()); if (result || strlen(sessionId) != 0) { if (ConfigConnectionServer::getValidateStationKey()) { bool cont = false; StationId apiSuid = 0; FATAL(sessURL.empty(), ("Session URL is empty in connection server.")); const std::string clientIP(getRemoteAddress()); const std::string sess(sessionId); DEBUG_WARNING(true, ("ConnectionServer::handleClientIdMessage - For ip %s suid is %lu requestedSUID is %lu and session is %s", clientIP.c_str(), m_suid, m_requestedSuid, sess.c_str())); webAPI api(sessURL); // add our data api.addJsonData("session_key", sess); api.addJsonData("ip", clientIP); if (api.submit()) { bool status = api.getNullableValue("status"); if (status) { std::string apiUser = api.getString("user_name"); std::string apiIP = api.getString("ip"); int expired = api.getNullableValue("expired"); if (!ConfigConnectionServer::getUseOldSuidGenerator()) { apiSuid = api.getNullableValue("user_id"); } else { if (apiUser.length() > MAX_ACCOUNT_NAME_LENGTH) { apiUser.resize(MAX_ACCOUNT_NAME_LENGTH); } apiSuid = std::hash < std::string > {}(apiUser.c_str()); } if (apiIP == clientIP && expired == 0) { m_suid = apiSuid; cont = true; } } } if (!cont) { LOG("ClientDisconnect", ("SUID %d (%d) passed a bad token to the connections erver. Disconnecting.", m_suid, apiSuid)); disconnect(); return; } } static const std::string loginTrace("TRACE_LOGIN"); LOG(loginTrace, ("ClientConnection SUID = %d", m_suid)); //check for duplicate login ClientConnection *oldConnection = ConnectionServer::getClientConnection(m_suid); if (oldConnection) { //There is already someone connected to this cluster with this suid. LOG("Network", ("SUID %d already logged in, disconnecting client.\n", m_suid)); ConnectionServer::dropClient(oldConnection, "Already Connected"); disconnect(); return; } // verify version if (ConfigConnectionServer::getValidateClientVersion() && msg.getVersion() != GameNetworkMessage::NetworkVersionId) { std::string strSessionId(sessionId, apiSessionIdWidth); strSessionId += '\0'; const int bufferSize = 255 + apiSessionIdWidth; char *buffer = new char[bufferSize]; snprintf(buffer, bufferSize - 1, "network version mismatch: got (ip=[%s], sessionId=[%s], version=[%s]), required (version=[%s])", getRemoteAddress().c_str(), strSessionId.c_str(), msg.getVersion().c_str(), GameNetworkMessage::NetworkVersionId.c_str()); buffer[bufferSize - 1] = '\0'; ConnectionServer::dropClient(this, std::string(buffer)); disconnect(); delete[] buffer; return; } // test mode only if (!m_suid && !ConfigConnectionServer::getValidateStationKey()) { WARNING(true, ("Generating suid from username. This is not safe or secure.")); m_suid = atoi(m_accountName.c_str()); if (m_suid == 0) { m_suid = std::hash < std::string > {}(m_accountName.c_str()); } } onValidateClient(m_suid, m_accountName, m_isSecure, nullptr, ConfigConnectionServer::getDefaultGameFeatures(), ConfigConnectionServer::getDefaultSubscriptionFeatures(), 0, 0, 0, 0, ConfigConnectionServer::getFakeBuddyPoints()); } } else { WARNING(true, ("SUID %d passed a bad token to the connections server (cache issue or hacker?). Disconnecting.", m_suid)); // They sent us a token that was no good -- either a hack attempt, or // possibly it was just too old. LOG("ClientDisconnect", ("SUID %d passed a bad token to the connections server (cache issue or hacker?). Disconnecting.", m_suid)); disconnect(); } } //----------------------------------------------------------------------- void ClientConnection::onIdValidated(bool canLogin, bool canCreateRegularCharacter, bool canCreateJediCharacter, bool canSkipTutorial, std::vector > const &consumedRewardEvents, std::vector > const &claimedRewardItems) { //@todo start session with station. //@todo add more permissions to this message as needed. // resume character creation if (m_pendingCharacterCreate) { if (!m_pendingCharacterCreate->getUseNewbieTutorial() && !canSkipTutorial) { LOG("TraceCharacterCreation", ("%d failed character creation. The client is not allowed to skip the tutorial", getSUID())); // This is probably a hack attempt, because the Client was already told they couldn't skip the tutorial LOG("ClientDisconnect", ("Disconnecting %u because they tried to skip the tutorial without permission.\n", getSUID())); disconnect(); } else if (m_pendingCharacterCreate->getJedi() && !canCreateJediCharacter) { LOG("TraceCharacterCreation", ("%d failed character creation. The request character type was Jedi, but this client cannot create a Jedi character", getSUID())); // This is probably a hack attempt, because the Client was already told they couldn't create a character LOG("ClientDisconnect", ("Disconnecting %u because they tried to create a Jedi character without permission.\n", getSUID())); disconnect(); } else if (!m_pendingCharacterCreate->getJedi() && !canCreateRegularCharacter) { LOG("TraceCharacterCreation", ("%d failed character creation. The client is not allowed to create any characters", getSUID())); // This is probably a hack attempt, because the Client was already told they couldn't create a character LOG("ClientDisconnect", ("Disconnecting %u because they tried to create a regular character without permission.\n", getSUID())); disconnect(); } else { ConnectionServer::sendToCentralProcess(*m_pendingCharacterCreate); LOG("TraceCharacterCreation", ("%d character creation request sent to CentralServer", getSUID())); m_hasRequestedCharacterCreate = true; } delete m_pendingCharacterCreate; m_pendingCharacterCreate = nullptr; return; } // Save lists of claimed rewards, which won't be used again until later in the login sequence m_consumedRewardEvents = consumedRewardEvents; m_claimedRewardItems = claimedRewardItems; int level = 0; if (AdminAccountManager::isAdminAccount(Unicode::toLower(getAccountName()), level) && (level != 0)) // Note: not checking IP, so that owners of god accounts can create characters to play from home without having to erase the characters they use for work { canLogin = true; canCreateRegularCharacter = true; canSkipTutorial = true; m_isAdminAccount = true; } ClientPermissionsMessage c(canLogin, canCreateRegularCharacter, canCreateJediCharacter, canSkipTutorial); send(c, true); DEBUG_REPORT_LOG(true, ("Permissions for %lu:\n", getSUID())); DEBUG_REPORT_LOG(canLogin, ("\tcanLogin\n")); DEBUG_REPORT_LOG(canCreateRegularCharacter, ("\tcanCreateRegularCharacter\n")); DEBUG_REPORT_LOG(canCreateJediCharacter, ("\tcanCreateJediCharacter\n")); DEBUG_REPORT_LOG(canSkipTutorial, ("\tcanSkipTutorial\n")); DEBUG_REPORT_LOG(!(canLogin || canCreateRegularCharacter || canCreateJediCharacter || canSkipTutorial), ("\tnone\n")); if (canLogin) { m_hasBeenValidated = true; m_canCreateRegularCharacter = canCreateRegularCharacter; m_canCreateJediCharacter = canCreateJediCharacter; m_canSkipTutorial = canSkipTutorial; } else { LOG("TRACE_LOGIN", ("%d does not have permissions to log in", getSUID())); LOG("ClientDisconnect", ("Client (SUID %u) does not have permissions to log in. Disconnecting.", getSUID())); disconnect(); } } //----------------------------------------------------------------------- void ClientConnection::onConnectionClosed() { ServerConnection::onConnectionClosed(); static MessageConnectionCallback m("ClientConnectionClosed"); emitMessage(m); LOG("TRACE_LOGIN", ("%d closed connection", getSUID())); if (m_client) { if (!m_client->hasBeenKicked()) { LOG("CustomerService", ("Login:%s Dropped Reason: Client Dropped Connection. Character: %s (%s). Play time: %s. Active play time: %s", describeAccount(this).c_str(), getCharacterName().c_str(), getCharacterId().getValueString().c_str(), getPlayTimeDuration().c_str(), getActivePlayTimeDuration().c_str())); } ChatServerConnection *chatConnection = m_client->getChatConnection(); if (chatConnection) { ChatDisconnectAvatar m(m_characterId); chatConnection->send(m, true); } // We cannot do this here, as this connection will be deleted on // return from this function already. //m_client->kick(); } } //----------------------------------------------------------------------- void ClientConnection::onConnectionOpened() { ServerConnection::onConnectionOpened(); static MessageConnectionCallback m("ClientConnectionOpened"); emitMessage(m); setOverflowLimit(ConfigConnectionServer::getClientOverflowLimit()); } //----------------------------------------------------------------------- void ClientConnection::onConnectionOverflowing(const unsigned int bytesPending) { char errbuf[1024]; snprintf(errbuf, sizeof(errbuf), "Connection overflow from server to client, %d bytes. Disconnected.", bytesPending); LOG("Network", ("Disconnect: Client connection overflowing. %d bytes pending", bytesPending)); std::vector < std::pair < std::string, int > > ::const_iterator i; for (i = m_pendingPackets.begin(); i != m_pendingPackets.end(); ++i) { LOG("Network", ("Overflow packets this frame: [%s] %d bytes", i->first.c_str(), i->second)); } // ErrorMessage err(name, desc, false); // send(err, true); WARNING(true, (errbuf)); LOG("ClientDisconnect", ("About to drop client (character) %s because the connection is overflowing\n", m_characterName.c_str())); snprintf(errbuf, sizeof(errbuf) - 1, "Connection Overflow (bytes pending=%u)", bytesPending); errbuf[sizeof(errbuf) - 1] = '\0'; ConnectionServer::dropClient(this, std::string(errbuf)); } //----------------------------------------------------------------------- bool ClientConnection::checkSpamLimit(unsigned int messageSize) { if (!ConfigConnectionServer::getSpamLimitEnabled()) { return true; } unsigned long curTimeMs = Clock::timeMs(); if (m_receiveLastTimeMs) { ++m_receiveHistoryPackets; m_receiveHistoryBytes += messageSize; m_receiveHistoryMs += curTimeMs - m_receiveLastTimeMs; // rescale the history information if we've exceeded the reset time; this // must be done before the spam check below or else we may run into overflow // issues because m_receiveHistoryMs could be pretty large if we haven't // received anything from the client for a while while (m_receiveHistoryMs > ConfigConnectionServer::getSpamLimitResetTimeMs()) { ++m_countSpamLimitResetTime; unsigned int resetScale = ConfigConnectionServer::getSpamLimitResetScaleFactor(); m_receiveHistoryMs /= resetScale; m_receiveHistoryBytes /= resetScale; m_receiveHistoryPackets /= resetScale; } // check for exceeding limits, but wait for at least // one reset cycle so that there has been enough // elapsed time, so we won't get a false positive if (m_countSpamLimitResetTime) { if (m_receiveHistoryBytes >= m_receiveHistoryMs * ConfigConnectionServer::getSpamLimitBytesPerSec() / 1000) { LOG("Network", ("Client %s disconnected for exceeding bytes/sec limit (bytes=%u, time=%lums)\n", getCharacterId().getValueString().c_str(), m_receiveHistoryBytes, m_receiveHistoryMs)); return false; } if (m_receiveHistoryPackets >= m_receiveHistoryMs * ConfigConnectionServer::getSpamLimitPacketsPerSec() / 1000) { LOG("Network", ("Client %s disconnected for exceeding packets/sec limit (packets=%u, time=%lums)\n", getCharacterId().getValueString().c_str(), m_receiveHistoryPackets, m_receiveHistoryMs)); return false; } } } m_receiveLastTimeMs = curTimeMs; return true; } //----------------------------------------------------------------------- void ClientConnection::onReceive(const Archive::ByteStream &message) { try { if (!checkSpamLimit(message.getSize())) { ConnectionServer::dropClient(this, "Spam Detected"); return; } unsigned long curTimeMs = Clock::timeMs(); if (m_sendLastTimeMs + std::min(gs_receiveDelayMaxMs, static_cast(Clock::frameTime() * 1000.0f)) < curTimeMs) { static HeartBeat h; send(h, false); } Archive::ReadIterator ri = message.begin(); GameNetworkMessage m(ri); ri = message.begin(); const uint32 messageType = m.getType(); //Clients with a selected character get routed to a game server. //@todo check for filtering out bad messages. if (m_hasSelectedCharacter) { // if it is a chat message, send it directly to the chat server switch (messageType) { case constcrc("ChatAddFriend") : case constcrc("ChatAddModeratorToRoom") : case constcrc("ChatBanAvatarFromRoom") : case constcrc("ChatCreateRoom") : case constcrc("ChatDeletePersistentMessage") : case constcrc("ChatDeleteAllPersistentMessages") : case constcrc("ChatDestroyRoom") : case constcrc("ChatInstantMessageToCharacter") : case constcrc("ChatInviteAvatarToRoom") : case constcrc("ChatKickAvatarFromRoom") : case constcrc("ChatRemoveAvatarFromRoom") : case constcrc("ChatRemoveFriend") : case constcrc("ChatRemoveModeratorFromRoom") : case constcrc("ChatRequestPersistentMessage") : case constcrc("ChatRequestRoomList") : case constcrc("ChatSendToRoom") : case constcrc("ChatUninviteFromRoom") : case constcrc("ChatUnbanAvatarFromRoom") : case constcrc("VerifyPlayerNameMessage") : { DEBUG_REPORT_LOG(true, ("ConnServ: ClientConnection::onReceive()\n")); NOT_NULL(m_client); if (m_client) { static std::vector v; v.clear(); v.push_back(m_client->getNetworkId()); GameClientMessage gcm(v, true, ri); if (m_client->getChatConnection()) { m_client->getChatConnection()->send(gcm, true); } else { // defer chat messages until a server is back online Archive::ByteStream bs; m.pack(bs); m_client->deferChatMessage(bs); } } else { ConnectionServer::dropClient(this, "m_client is nullptr while receiving a message!"); disconnect(); } break; } // ChatEnterRoom and ChatEnterRoomById needs to go to the game server to determine // if the character is not allowed to enter the room because of game rule restrictions; // only if that test pass do we forward the message on to the chat server to request // to enter the room case constcrc("ChatEnterRoom") : case constcrc("ChatEnterRoomById") : { NOT_NULL(m_client); unsigned int sequence; std::string roomName; Archive::ReadIterator cri = message.begin(); if (messageType == constcrc("ChatEnterRoom")) { ChatEnterRoom const cer(cri); sequence = cer.getSequence(); roomName = cer.getRoomName(); } else { ChatEnterRoomById const cerbi(cri); sequence = cerbi.getSequence(); roomName = cerbi.getRoomName(); } if (m_client && m_client->getGameConnection()) { if (m_pendingChatEnterRoomRequests.count(sequence) == 0) { GenericValueTypeMessage < std::pair < std::pair < NetworkId, std::string >, unsigned int > > const cervr( "ChatEnterRoomValidationRequest", std::make_pair(std::make_pair(m_client->getNetworkId(), roomName), sequence)); m_client->getGameConnection()->send(cervr, true); // queue up request until game server responds static std::vector v; v.clear(); v.push_back(m_client->getNetworkId()); m_pendingChatEnterRoomRequests[sequence] = new GameClientMessage(v, true, ri); } } else { // send back response to client saying game server not available // the client only cares about sequence and result when it's a failure ChatOnEnteredRoom fail(sequence, SWG_CHAT_ERR_NO_GAME_SERVER, 0, ChatAvatarId()); send(fail, true); } break; } // ChatQueryRoom needs to go to the game server to determine if the character is // not allowed to query the room because of game rule restrictions; only if that // test pass do we forward the message on to the chat server for completion case constcrc("ChatQueryRoom") : { NOT_NULL(m_client); Archive::ReadIterator cri = message.begin(); ChatQueryRoom cqr(cri); if (m_client && m_client->getGameConnection()) { if (m_pendingChatQueryRoomRequests.count(cqr.getSequence()) == 0) { GenericValueTypeMessage < std::pair < std::pair < NetworkId, std::string >, unsigned int > > const cqrvr( "ChatQueryRoomValidationRequest", std::make_pair(std::make_pair(m_client->getNetworkId(), cqr.getRoomName()), cqr.getSequence())); m_client->getGameConnection()->send(cqrvr, true); // queue up request until game server responds static std::vector v; v.clear(); v.push_back(m_client->getNetworkId()); m_pendingChatQueryRoomRequests[cqr.getSequence()] = new GameClientMessage(v, true, ri); } } break; } // ChatInviteGroupToRoom needs to go to the game server to get group information case constcrc("ChatInviteGroupToRoom") : { NOT_NULL(m_client); if (m_client) { if (m_client->getGameConnection()) { static std::vector v; v.clear(); v.push_back(m_client->getNetworkId()); GameClientMessage gcm(v, true, ri); m_client->getGameConnection()->send(gcm, true); } else { // defer chat messages until a server is back online Archive::ByteStream bs; m.pack(bs); m_client->deferChatMessage(bs); } } else { ConnectionServer::dropClient(this, "m_client is nullptr while receiving a message!"); disconnect(); } break; } // ChatPersistentMessageToServer may need to be passed off to the game server for guild or citizens messages case constcrc("ChatPersistentMessageToServer") : { NOT_NULL(m_client); if (m_client) { static std::vector v; v.clear(); v.push_back(m_client->getNetworkId()); Archive::ReadIterator cri = message.begin(); ChatPersistentMessageToServer chat(cri); std::string const &toName = chat.getToCharacterName().name; if (!_stricmp(toName.c_str(), "guild") || !_strnicmp(toName.c_str(), "guild ", 6) || !_stricmp(toName.c_str(), "citizens")) { if (m_client->getGameConnection()) { GameClientMessage gcm(v, true, ri); m_client->getGameConnection()->send(gcm, true); } } else { if (m_client->getChatConnection()) { GameClientMessage gcm(v, true, ri); m_client->getChatConnection()->send(gcm, true); } else { // defer chat messages until a server is back online Archive::ByteStream bs; m.pack(bs); m_client->deferChatMessage(bs); } } } else { ConnectionServer::dropClient(this, "m_client is nullptr while receiving a message!"); disconnect(); } break; } // if it is a cs message, send it directly to the cs server case constcrc("ConnectPlayerMessage") : case constcrc("DisconnectPlayerMessage") : case constcrc("CreateTicketMessage") : case constcrc("AppendCommentMessage") : case constcrc("CancelTicketMessage") : case constcrc("GetTicketsMessage") : case constcrc("GetCommentsMessage") : case constcrc("SearchKnowledgeBaseMessage") : case constcrc("GetArticleMessage") : case constcrc("RequestCategoriesMessage") : case constcrc("NewTicketActivityMessage") : { NOT_NULL(m_client); if (m_client) { CustomerServiceConnection *customerServiceConnection = m_client->getCustomerServiceConnection(); //DEBUG_REPORT_LOG(true, ("CONSRV::CS - suid: %i\n", getSUID())); if (customerServiceConnection != nullptr) { static std::vector v; v.clear(); v.push_back(m_client->getNetworkId()); // TODO: this shit could be made into a template switch (messageType) { case constcrc("ConnectPlayerMessage") : { ConnectPlayerMessage message(ri); message.setStationId(getSUID()); GameClientMessage gcm(v, true, message); customerServiceConnection->send(gcm, true); break; } case constcrc("CreateTicketMessage") : { CreateTicketMessage message(ri); message.setStationId(getSUID()); GameClientMessage gcm(v, true, message); customerServiceConnection->send(gcm, true); break; } case constcrc("AppendCommentMessage") : { AppendCommentMessage message(ri); message.setStationId(getSUID()); GameClientMessage gcm(v, true, message); customerServiceConnection->send(gcm, true); break; } case constcrc("CancelTicketMessage") : { CancelTicketMessage message(ri); message.setStationId(getSUID()); GameClientMessage gcm(v, true, message); customerServiceConnection->send(gcm, true); break; } case constcrc("GetTicketsMessage") : { GetTicketsMessage message(ri); message.setStationId(getSUID()); GameClientMessage gcm(v, true, message); customerServiceConnection->send(gcm, true); break; } case constcrc("NewTicketActivityMessage") : { NewTicketActivityMessage message(ri); message.setStationId(getSUID()); GameClientMessage gcm(v, true, message); customerServiceConnection->send(gcm, true); break; } default : { GameClientMessage gcm(v, true, ri); customerServiceConnection->send(gcm, true); break; } } } } else { ConnectionServer::dropClient(this, "m_client is nullptr while receiving a message!"); disconnect(); } break; } case constcrc("28afefcc187a11dc888b001") : // obfuscation for ClientInactivityMessage message { GenericValueTypeMessage msg(ri); if (m_hasBeenValidated && m_sessionValidated) { // client went inactive if (msg.getValue()) { if (m_lastActiveTime > 0) { // record the amount of active time m_activePlayTimeDuration += static_cast(::time(nullptr) - m_lastActiveTime); // tell Session to stop recording play time for the character if (ConnectionServer::getSessionApiClient() && ConfigConnectionServer::getSessionRecordPlayTime()) { LOG("CustomerService", ("Login:%s calling SessionStopPlay() for %s/%s/%s (%s). Active play time: %s", ClientConnection::describeAccount(this).c_str(), this->getSessionId().c_str(), ConfigConnectionServer::getClusterName(), this->getCharacterName().c_str(), this->getCharacterId().getValueString().c_str(), this->getCurrentActivePlayTimeDuration().c_str())); ConnectionServer::getSessionApiClient()->stopPlay(*this); } // client is no longer active; this needs to be set after the LOG() statement // above because getCurrentActivePlayTimeDuration() uses m_lastActiveTime m_lastActiveTime = 0; // update the play time info on the game server sendPlayTimeInfoToGameServer(); // drop inactive character if (ConfigConnectionServer::getDisconnectOnInactive()) { LOG("ClientDisconnect", ("Disconnecting %u because the player was inactive for too long.", getSUID())); ConnectionServer::dropClient(this, "Client inactivity"); disconnect(); } else if (ConfigConnectionServer::getDisconnectFreeTrialOnInactive() && ((m_featureBitsSubscription & ClientSubscriptionFeature::Base) == 0)) { LOG("ClientDisconnect", ("Disconnecting (free trial) %u because the player was inactive for too long.", getSUID())); ConnectionServer::dropClient(this, "Client inactivity (free trial)"); disconnect(); } } } // client went active else { if (m_lastActiveTime == 0) { // record the time client went active m_lastActiveTime = ::time(nullptr); // tell Session to start recording play time for the character if (ConnectionServer::getSessionApiClient() && ConfigConnectionServer::getSessionRecordPlayTime()) { LOG("CustomerService", ("Login:%s calling SessionStartPlay() for %s/%s/%s (%s)", ClientConnection::describeAccount(this).c_str(), this->getSessionId().c_str(), ConfigConnectionServer::getClusterName(), this->getCharacterName().c_str(), this->getCharacterId().getValueString().c_str())); ConnectionServer::getSessionApiClient()->startPlay(*this); } // update the play time info on the game server sendPlayTimeInfoToGameServer(); } } } break; } default : { //Forward on to Game Server DEBUG_REPORT_LOG((!m_client || !m_client->getGameConnection()), ("Warn, received game message with no game connection. This may happen for a short time after a GameServer crashes. If it continues to happen, it indicates a bug.\n")); if (m_client && m_client->getGameConnection()) { static std::vector v; v.clear(); v.push_back(m_client->getNetworkId()); GameClientMessage gcm(v, true, ri); m_client->getGameConnection()->send(gcm, true); } break; } } } else { switch (messageType) { case constcrc("ClientIdMsg") : { DEBUG_REPORT_LOG(true, ("Recieved ClientIdMsg\n")); ClientIdMsg k(ri); handleClientIdMessage(k); break; } case constcrc("SelectCharacter") : { SelectCharacter s(ri); DEBUG_REPORT_LOG(true, ("Recvd SelectCharacter message for %s.\n", s.getId().getValueString().c_str())); handleSelectCharacterMessage(s); break; } case constcrc("ClientCreateCharacter") : { if (m_hasBeenValidated && !m_hasSelectedCharacter) //lint !e774 no this doesn't always eval to true { ClientCreateCharacter clientCreate(ri); DEBUG_REPORT_LOG(true, ("Got ClientCreateCharacter message for %lu with name %s\n", m_suid, Unicode::wideToNarrow(clientCreate.getCharacterName()).c_str())); LOG("TraceCharacterCreation", ("%d sent ClientCreateCharacter(charaterName=%s, templateName=%s, scaleFactor=%f, startingLocation=%s, hairTemplateName=%s, profession=%s, jedi=%d, useNewbieTutorial=%d, skillTemplate=%s, workingSkill=%s)", getSUID(), Unicode::wideToNarrow(clientCreate.getCharacterName()).c_str(), clientCreate.getTemplateName().c_str(), clientCreate.getScaleFactor(), clientCreate.getStartingLocation().c_str(), clientCreate.getHairTemplateName().c_str(), clientCreate.getProfession().c_str(), static_cast(clientCreate.getJedi()), static_cast(clientCreate.getUseNewbieTutorial()), clientCreate.getSkillTemplate().c_str(), clientCreate.getWorkingSkill().c_str())); if (!clientCreate.getUseNewbieTutorial() && !m_canSkipTutorial) { LOG("TraceCharacterCreation", ("%d failed character creation. The client is not allowed to skip the tutorial", getSUID())); // This is probably a hack attempt, because the Client was already told they couldn't skip the tutorial LOG("ClientDisconnect", ("Disconnecting %u because they tried to skip the tutorial without permission.\n", getSUID())); disconnect(); } else if (clientCreate.getJedi() && !m_canCreateJediCharacter) { LOG("TraceCharacterCreation", ("%d failed character creation. The request character type was Jedi, but this client cannot create a Jedi character", getSUID())); // This is probably a hack attempt, because the Client was already told they couldn't create a character LOG("ClientDisconnect", ("Disconnecting %u because they tried to create a Jedi character without permission.\n", getSUID())); disconnect(); } else if (!clientCreate.getJedi() && !m_canCreateRegularCharacter) { LOG("TraceCharacterCreation", ("%d failed character creation. The client is not allowed to create any characters", getSUID())); // This is probably a hack attempt, because the Client was already told they couldn't create a character LOG("ClientDisconnect", ("Disconnecting %u because they tried to create a regular character without permission.\n", getSUID())); disconnect(); } else if (m_hasRequestedCharacterCreate) { LOG("TraceCharacterCreation", ("%d failed character creation. The client has already requested character creation on this connection", getSUID())); LOG("ClientDisconnect", ("Disconnecting %u because the client has already requested character creation on this connection.\n", getSUID())); disconnect(); } else if (m_hasCreatedCharacter) { LOG("TraceCharacterCreation", ("%d failed character creation. A character has been created on this or another galaxy for this account while this connection was up", getSUID())); LOG("ClientDisconnect", ("Disconnecting %u because a character has been created on this or another galaxy for this account while this connection was up.\n", getSUID())); disconnect(); } else if (clientCreate.getCharacterName().length() == 0) { LOG("TraceCharacterCreation", ("%d failed character creation. The character's name is empty", getSUID())); LOG("ClientDisconnect", ("Disconnecting %u because they tried to create a character with no name.\n", getSUID())); disconnect(); } else { Unicode::String biography(clientCreate.getBiography()); if (biography.length() > 1024) { IGNORE_RETURN(biography.erase(1024)); DEBUG_REPORT_LOG(true, ("Biography shortened to 1024 characters.\n")); } if (m_isAdminAccount) { ConnectionCreateCharacter connectionCreate(m_suid, clientCreate.getCharacterName(), clientCreate.getTemplateName(), clientCreate.getScaleFactor(), clientCreate.getStartingLocation(), clientCreate.getAppearanceData(), clientCreate.getHairTemplateName(), clientCreate.getHairAppearanceData(), clientCreate.getProfession(), clientCreate.getJedi(), biography, clientCreate.getUseNewbieTutorial(), clientCreate.getSkillTemplate(), clientCreate.getWorkingSkill(), m_isAdminAccount, false, m_featureBitsGame); ConnectionServer::sendToCentralProcess(connectionCreate); LOG("TraceCharacterCreation", ("%d character creation request sent to CentralServer", getSUID())); } else { // for regular players, do one final check with the LoginServer // to make sure the character can be created (i.e. that character // limits have not been exceeded) delete m_pendingCharacterCreate; m_pendingCharacterCreate = new ConnectionCreateCharacter(m_suid, clientCreate.getCharacterName(), clientCreate.getTemplateName(), clientCreate.getScaleFactor(), clientCreate.getStartingLocation(), clientCreate.getAppearanceData(), clientCreate.getHairTemplateName(), clientCreate.getHairAppearanceData(), clientCreate.getProfession(), clientCreate.getJedi(), biography, clientCreate.getUseNewbieTutorial(), clientCreate.getSkillTemplate(), clientCreate.getWorkingSkill(), m_isAdminAccount, false, m_featureBitsGame); LOG("TraceCharacterCreation", ("%d character creation request awaiting final verification from LoginServer", getSUID())); ValidateAccountMessage vcm(m_suid, 0, m_featureBitsSubscription); ConnectionServer::sendToCentralProcess(vcm); } m_hasRequestedCharacterCreate = true; } } break; } case constcrc("ClientRandomNameRequest") : { ClientRandomNameRequest clientRandomName(ri); RandomNameRequest randomNameRequest(m_suid, clientRandomName.getCreatureTemplate()); ConnectionServer::sendToCentralProcess(randomNameRequest); LOG("TraceCharacterCreation", ("%d requested a random name. Request sent to CentralServer", getSUID())); break; } case constcrc("ClientVerifyAndLockNameRequest") : { ClientVerifyAndLockNameRequest clientVerifyAndLockNameRequest(ri); VerifyAndLockNameRequest verifyAndLockNameRequest(m_suid, NetworkId::cms_invalid, clientVerifyAndLockNameRequest.getTemplateName(), clientVerifyAndLockNameRequest.getCharacterName(), m_featureBitsGame); ConnectionServer::sendToCentralProcess(verifyAndLockNameRequest); LOG("TraceCharacterCreation", ("%d requested a verify and lock of name: %s. Request sent to CentralServer", getSUID(), Unicode::wideToNarrow(verifyAndLockNameRequest.getCharacterName()).c_str())); break; } case constcrc("LagRequest") : { // TODO: why is this commented out? // handleLagRequest(); break; } } } } catch (const Archive::ReadException &readException) { WARNING(true, ("Archive read error (%s) from client. Disconnecting client", readException.what())); LOG("ClientDisconnect", ("Archive read error (%s) from client. Disconnecting client", readException.what())); disconnect(); } } //----------------------------------------------------------------------- void ClientConnection::handleLagRequest() { // client is requesting a lag ping (reliable trace) GameNetworkMessage response("ConnectionServerLagResponse"); send(response, true); if (m_hasSelectedCharacter && m_client && m_client->getGameConnection()) { // send to game server GameNetworkMessage request("LagRequest"); std::vector v; v.push_back(m_characterId); GameClientMessage gcm(v, true, request); m_client->getGameConnection()->send(gcm, true); } else { // send game response immediately GameNetworkMessage gameResponse("GameServerLagResponse"); send(gameResponse, true); } } //----------------------------------------------------------------------- /** character has selected a character and calls this function * to associate the connection with the new client they created with that * character. We no longer need the character map. */ void ClientConnection::setClient(Client *newClient) { //This fatal is here to try to catch the reconnect bug. WARNING_STRICT_FATAL(m_client, ("Attempting to set the client on a connection that already has one. Client %s\n", getCharacterName().c_str())); // jrandall - I've removed the fatal because it is blocking some people from getting some work // done. I'm on a high priority fix at the moment. If this warning starts appearing, // set a break point or something. //DEBUG_FATAL(client, ("Attempting to set the client on a connection that already has one.\n")); m_client = newClient; // todo put this in: characterMap.clear(); } //----------------------------------------------------------------------- void ClientConnection::send(const GameNetworkMessage &message, const bool reliable) { m_sendLastTimeMs = Clock::timeMs(); if (sm_outgoingBytesMap_Worktime == 0) { sm_outgoingBytesMap_Worktime = m_sendLastTimeMs; } else if ((m_sendLastTimeMs - sm_outgoingBytesMap_Worktime) > 60000) // 60 seconds { sm_outgoingBytesMap_Stats = sm_outgoingBytesMap_Working; std::map::iterator iter; for (iter = sm_outgoingBytesMap_Working.begin(); iter != sm_outgoingBytesMap_Working.end(); ++iter) { iter->second = 0; } sm_outgoingBytesMap_Worktime = m_sendLastTimeMs; } sm_outgoingBytesMap_Working[message.getCmdName()] += message.getByteStream().getSize(); ServerConnection::send(message, reliable); } //----------------------------------------------------------------------- std::map &ClientConnection::getPacketBytesPerMinStats() { uint32 now = Clock::timeMs(); if (sm_outgoingBytesMap_Worktime == 0) { sm_outgoingBytesMap_Worktime = now; } else if ((now - sm_outgoingBytesMap_Worktime) > 60000) // 60 seconds { sm_outgoingBytesMap_Stats = sm_outgoingBytesMap_Working; std::map::iterator iter; for (iter = sm_outgoingBytesMap_Working.begin(); iter != sm_outgoingBytesMap_Working.end(); ++iter) { iter->second = 0; } sm_outgoingBytesMap_Worktime = now; } return sm_outgoingBytesMap_Stats; } //----------------------------------------------------------------------- void ClientConnection::handleChatEnterRoomValidationResponse(unsigned int sequence, unsigned int result) { std::map < unsigned long, GameClientMessage * > ::iterator iterFind = m_pendingChatEnterRoomRequests.find(sequence); if (iterFind != m_pendingChatEnterRoomRequests.end()) { if (result == CHATRESULT_SUCCESS) { if (m_client && m_client->getChatConnection()) { // game server says it's ok to enter the chat room, // so forward the request on to the chat server m_client->getChatConnection()->send(*(iterFind->second), true); } else { // send back response to client saying chat server not available // the client only cares about sequence and result when it's a failure ChatOnEnteredRoom fail(sequence, SWG_CHAT_ERR_CHAT_SERVER_UNAVAILABLE, 0, ChatAvatarId()); send(fail, true); } } else { // send back response to client saying game server denied enter room request // the client only cares about sequence and result when it's a failure ChatOnEnteredRoom fail(sequence, result, 0, ChatAvatarId()); send(fail, true); } delete iterFind->second; m_pendingChatEnterRoomRequests.erase(iterFind); } } //----------------------------------------------------------------------- void ClientConnection::handleChatQueryRoomValidationResponse(unsigned int sequence, bool success) { std::map < unsigned long, GameClientMessage * > ::iterator iterFind = m_pendingChatQueryRoomRequests.find(sequence); if (iterFind != m_pendingChatQueryRoomRequests.end()) { if (success) { if (m_client && m_client->getChatConnection()) { // game server says it's ok to query the chat room, // so forward the request on to the chat server m_client->getChatConnection()->send(*(iterFind->second), true); } } delete iterFind->second; m_pendingChatQueryRoomRequests.erase(iterFind); } } //----------------------------------------------------------------------- void ClientConnection::sendByteStream(const Archive::ByteStream &bs, bool reliable) { Connection::send(bs, reliable); } //----------------------------------------------------------------------- /** * Send the client to an arbitratry game server based on the current scene */ const bool ClientConnection::sendToGameServer() { GameConnection *c = const_cast(ConnectionServer::getGameConnection(m_targetScene)); return sendToGameServer(c); } // ---------------------------------------------------------------------- /** * Send the client to a particular game server, sepcified by process id. */ const bool ClientConnection::sendToGameServer(uint32 gameServerId) { GameConnection *c = const_cast(ConnectionServer::getGameConnection(gameServerId)); return sendToGameServer(c); } // ---------------------------------------------------------------------- bool ClientConnection::sendToGameServer(GameConnection *c) { bool result = false; if (c && m_hasSelectedCharacter && m_hasBeenValidated) { //create a new client ConnectionServer::addNewClient(this, m_characterId, c, m_targetScene, m_sendToStarport); LoggedInMessage m(m_suid); ConnectionServer::sendToCentralProcess(m); result = true; m_hasBeenSentToGameServer = true; } return result; } //----------------------------------------------------------------------- /** * Called when DBProcess responds to our ValidateCharacterForLoginMessage. * Now we know whether the character is valid and where in the world it * is located. */ void ClientConnection::onCharacterValidated(bool isValid, const NetworkId &character, const std::string &characterName, const NetworkId &container, const std::string &scene, const Vector &coordinates) { if (!m_validatingCharacter) { LOG("TraceCharacterSelection", ("%d received a validation response, but is not in the process of validation", getSUID())); DEBUG_REPORT_LOG(true, ("Got unexpected onCharacterValidated() for account %lu.\n", getSUID())); return; } m_validatingCharacter = false; if (isValid) { LOG("TraceCharacterSelection", ("%d received a validation response. The character (%s: %s) at (%s,%f,%f,%f,%s) selected is valid", getSUID(), character.getValueString().c_str(), characterName.c_str(), scene.c_str(), coordinates.x, coordinates.y, coordinates.z, container.getValueString().c_str())); LOG("CustomerService", ("Login:%s received a validation response. The character (%s: %s) at (%s,%f,%f,%f,%s) selected is valid", describeAccount(this).c_str(), character.getValueString().c_str(), characterName.c_str(), scene.c_str(), coordinates.x, coordinates.y, coordinates.z, container.getValueString().c_str())); m_targetScene = scene; m_targetCoordinates = coordinates; m_characterId = character; m_containerId = container; m_characterName = characterName; m_hasSelectedCharacter = true; // ask CentralServer to suggest a game server for this character // (Central will forward the request to a Planet Server) RequestGameServerForLoginMessage requestmsg(getSUID(), m_characterId, m_containerId, m_targetScene, m_targetCoordinates, false); if (ConnectionServer::getCentralConnection()) { ConnectionServer::getCentralConnection()->send(requestmsg, true); } else { LOG("ClientDisconnect", ("Can't handle login of character %s because there is no connection to Central.\n", m_characterId.getValueString().c_str())); ErrorMessage err("Validation Failed", "The connection to the central server is down. Please try again later."); send(err, true); disconnect(); } } else { LOG("TraceCharacterSelection", ("%d validation failed, disconnecting client", getSUID())); ErrorMessage err("Validation Failed", "Your character was denied login by the database."); send(err, true); LOG("ClientDisconnect", ("Denying login for account %u.\n", getSUID())); disconnect(); } } //------------------------------------------------------------------------------------------ void ClientConnection::onValidateClient(StationId suid, const std::string &username, bool secure, const char *id, const uint32 gameFeatures, const uint32 subscriptionFeatures, unsigned int entitlementTotalTime, unsigned int entitlementEntitledTime, unsigned int entitlementTotalTimeSinceLastLogin, unsigned int entitlementEntitledTimeSinceLastLogin, int buddyPoints) { UNREF(id); m_sessionValidated = true; m_suid = suid; m_accountName = username; m_featureBitsGame = gameFeatures; m_featureBitsSubscription = subscriptionFeatures; m_isSecure = secure; m_entitlementTotalTime = entitlementTotalTime; m_entitlementEntitledTime = entitlementEntitledTime; m_entitlementTotalTimeSinceLastLogin = entitlementTotalTimeSinceLastLogin; m_entitlementEntitledTimeSinceLastLogin = entitlementEntitledTimeSinceLastLogin; m_buddyPoints = buddyPoints; if (id) { m_sessionId = id; } if (m_requestedSuid != 0 && suid != m_requestedSuid) { //verify internal, secure, is on the god list bool loginOK = false; if (!secure) { LOG("CustomerService", ("AdminLogin: User %s (account %li) attempted to log into account %li, but was not using a SecureID token", username.c_str(), suid, m_requestedSuid)); } else { if (!AdminAccountManager::isInternalIp(getRemoteAddress())) { LOG("CustomerService", ("AdminLogin: User %s (account %li) attempted to log into account %li, but was not logging in from an internal IP", username.c_str(), suid, m_requestedSuid)); } else { int adminLevel = 0; if (!AdminAccountManager::isAdminAccount(Unicode::toLower(username), adminLevel) || adminLevel < 10) { LOG("CustomerService", ("AdminLogin: User %s (account %li) attempted to log into account %li, but did not have sufficient permissions", username.c_str(), suid, m_requestedSuid)); } else { LOG("CustomerService", ("AdminLogin: User %s (account %li) logged into account %li", username.c_str(), m_suid, m_requestedSuid)); DEBUG_REPORT_LOG(true, ("AdminLogin: User %s (account %li) logged into account %li\n", username.c_str(), m_suid, m_requestedSuid)); m_suid = m_requestedSuid; m_usingAdminLogin = true; loginOK = true; } } } if (!loginOK) { disconnect(); return; } } m_featureBitsGame &= ~ConfigConnectionServer::getDisabledFeatureBits(); //Configoption to enable JTL features for beta players so that our code can pretend everything uses the JTL Retail bit if (ConfigConnectionServer::getSetJtlRetailIfBetaIsSet()) { if (ClientGameFeature::SpaceExpansionBeta & m_featureBitsGame) { m_featureBitsGame |= ClientGameFeature::SpaceExpansionRetail; } } //Configoption to enable Obiwan features for beta players so that our code can pretend everything uses the Obiwan Retail bit if (ConfigConnectionServer::getSetTrialsOfObiwanRetailIfBetaIsSet()) { if (ClientGameFeature::TrialsOfObiwanBeta & m_featureBitsGame) { //-- add retail bit only if player does not have the preorder bit if ((m_featureBitsGame & ClientGameFeature::TrialsOfObiwanPreorder) == 0) { m_featureBitsGame |= ClientGameFeature::TrialsOfObiwanRetail; } } else { // Clear bits from players who might have them for real, but aren't in the beta m_featureBitsGame &= ~ClientGameFeature::TrialsOfObiwanRetail; m_featureBitsGame &= ~ClientGameFeature::TrialsOfObiwanPreorder; } } //-- Obiwan Preorders get the Retail bit as well... All rewards etc... if (ClientGameFeature::TrialsOfObiwanPreorder & m_featureBitsGame) { m_featureBitsGame |= ClientGameFeature::TrialsOfObiwanRetail; } // Restrictions for "new free trial" account if (((m_featureBitsSubscription & ClientSubscriptionFeature::FreeTrial2) != 0) && ((m_featureBitsSubscription & ClientSubscriptionFeature::Base) == 0)) { // "new free trial" account don't have access to RoW until they convert m_featureBitsGame &= ~ClientGameFeature::Episode3ExpansionRetail; m_featureBitsGame &= ~ClientGameFeature::Episode3PreorderDownload; // ClientGameFeature::FreeTrial2 indicates this is a converted "new free trial" // account, and since this account hasn't converted yet, we remove this bit m_featureBitsGame &= ~ClientGameFeature::FreeTrial2; } // Clear feature bits that only apply if the account is paying (i.e. the sub base bit is set) if ((m_featureBitsSubscription & ClientSubscriptionFeature::Base) == 0) { m_featureBitsGame &= ~ClientGameFeature::HousePackupReward; m_featureBitsGame &= ~ClientGameFeature::BuddyProgramReward; } ValidateAccountMessage vcm(m_suid, 0, m_featureBitsSubscription); ConnectionServer::sendToCentralProcess(vcm); ConnectionServer::addConnectedClient(m_suid, this); uint32 const requiredSubscriptionBits = ConfigConnectionServer::getRequiredSubscriptionBits(); if (requiredSubscriptionBits != 0) { if ((subscriptionFeatures & requiredSubscriptionBits) != requiredSubscriptionBits) { LOG("ClientDisconnect", ("Suid %d (%s) by session denial reason 'Invalid Subscription Bits'.", suid, username.c_str())); LOG("CustomerService", ("Login: %s by session denial reason 'Invalid Subscription Bits'.", describeAccount(this).c_str())); disconnect(); return; } } uint32 const requiredGameBits = ConfigConnectionServer::getRequiredGameBits(); if (requiredGameBits != 0) { if ((gameFeatures & requiredGameBits) != requiredGameBits) { LOG("ClientDisconnect", ("Suid %d (%s) by session denial reason 'Invalid Game Bits'.", suid, username.c_str())); LOG("CustomerService", ("Login: %s by session denial reason 'Invalid Game Bits'.", describeAccount(this).c_str())); disconnect(); return; } } // tell client the server-side game and subscription feature bits and which ConnectionServer we are and the current server Epoch time GenericValueTypeMessage < std::pair < std::pair < unsigned long, unsigned long > , std::pair < int, int32 > > > const msgFeatureBits( "AccountFeatureBits", std::make_pair(std::make_pair(gameFeatures, subscriptionFeatures), std::make_pair(ConfigConnectionServer::getConnectionServerNumber(), static_cast(::time(nullptr))))); send(msgFeatureBits, true); std::string const gameFeaturesDescription = ClientGameFeature::getDescription(gameFeatures); std::string const subscriptionFeaturesDescription = ClientSubscriptionFeature::getDescription(subscriptionFeatures); LOG("CustomerService", ("Login:%s at IP: %s:%hu has connected with game code 0x%x (%s) and sub code 0x%x (%s) (entitlement total: %u/%u, since last login: %u/%u)", describeAccount(this).c_str(), getRemoteAddress().c_str(), getRemotePort(), gameFeatures, gameFeaturesDescription.c_str(), subscriptionFeatures, subscriptionFeaturesDescription.c_str(), m_entitlementEntitledTime, m_entitlementTotalTime, m_entitlementEntitledTimeSinceLastLogin, m_entitlementTotalTimeSinceLastLogin)); // ask CentralServer to tell all other ConnectionServers on this galaxy to drop duplicate connections for this account // and ask CentralServer (via LoginServer) to tell all ConnectionServers on other galaxies to drop duplicate connections for this account if (!m_usingAdminLogin && !m_isSecure) { GenericValueTypeMessage > const dropDuplicateConnections("ConnSrvDropDupeConns", std::make_pair(m_suid, m_sessionId)); ConnectionServer::sendToCentralProcess(dropDuplicateConnections); } } //----------------------------------------------------------------------- std::string ClientConnection::describeAccount(const ClientConnection *c) { std::string result = ""; if (c) { char idbuf[512] = {"\0"}; const std::string &sessionId = c->getSessionId(); if (sessionId.empty()) { snprintf(idbuf, sizeof(idbuf), " (%lu)", c->m_suid); } else { snprintf(idbuf, sizeof(idbuf), " (%lu, %s)", c->m_suid, sessionId.c_str()); } result = c->m_accountName; result += idbuf; } return result; } // ---------------------------------------------------------------------- std::vector > const &ClientConnection::getConsumedRewardEvents() const { return m_consumedRewardEvents; } // ---------------------------------------------------------------------- std::vector > const &ClientConnection::getClaimedRewardItems() const { return m_claimedRewardItems; } // ---------------------------------------------------------------------- bool ClientConnection::isUsingAdminLogin() const { return m_usingAdminLogin; } // ---------------------------------------------------------------------- int ClientConnection::getBuddyPoints() const { return m_buddyPoints; } // ======================================================================