diff --git a/CMakeLists.txt b/CMakeLists.txt index bf7130a3..e6140156 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,8 +117,7 @@ elseif (UNIX) -Wno-write-strings -Wno-unknown-pragmas \ -Wno-uninitialized -Wno-reorder -Wno-tautological-constant-out-of-range-compare") - # if you'd like to opt in to our statistics, remove "-DSTELLA_INTERNAL" and your server will send us a heartbeat at startup - add_definitions(-DLINUX -D_REENTRANT -Dlinux -D_USING_STL -D_GNU_SOURCE -D_XOPEN_SOURCE=500 -U_FORTIFY_SOURCE -DSTELLA_INTERNAL) + add_definitions(-DLINUX -D_REENTRANT -Dlinux -D_USING_STL -D_GNU_SOURCE -D_XOPEN_SOURCE=500 -U_FORTIFY_SOURCE) # this is so some profile specific stuff is turned on in the code if (${CMAKE_BUILD_TYPE} STREQUAL "RELWITHDEBINFO") diff --git a/engine/server/application/ConnectionServer/src/shared/ClientConnection.cpp b/engine/server/application/ConnectionServer/src/shared/ClientConnection.cpp index 54d2d0b7..187997ed 100755 --- a/engine/server/application/ConnectionServer/src/shared/ClientConnection.cpp +++ b/engine/server/application/ConnectionServer/src/shared/ClientConnection.cpp @@ -394,19 +394,41 @@ ClientConnection::onIdValidated(bool canLogin, bool canCreateRegularCharacter, b // Save lists of claimed rewards, which won't be used again until later in the login sequence m_consumedRewardEvents = consumedRewardEvents; m_claimedRewardItems = claimedRewardItems; + bool isAdmin = false; - - 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 + // Revised - SWG Source - 2021 (Aconite) + // Determine if this account is in the Admin Table + if(AdminAccountManager::getAdminLevel(Unicode::toLower(getAccountName())) > 0) { + // Determine if we are using Secure Login (IP restricted) + if(ConfigConnectionServer::getUseSecureLoginForGodAccess()) + { + // If we *require* secure login, and we are *not* from a secure IP, disconnect the client + // as they should not be inside this account + if(!AdminAccountManager::isInternalIp(getRemoteAddress())) + { + LOG("GodMode", ("[%s : %s : %s : %s] login to this admin account failed because secure login was required but the remote IP address connected was not in the allowed range.", + getAccountName().c_str(), getCharacterName().c_str(), getCharacterId().getValueString().c_str(), getRemoteAddress().c_str())); + disconnect(); + return; + } + else + { + m_isSecure = true; + } + } + // If we're here, we're an admin account, and we have the correct IP (if applicable) + // so we're overriding the ClientPermissionMessage values canLogin = true; canCreateRegularCharacter = true; canSkipTutorial = true; m_isAdminAccount = true; + isAdmin = true; // this flag goes to the client to let it know we can connect to a Locked Cluster + LOG("GodMode", ("[%s : %s : %s : %s] validating this account to the Connection Server after successful authentication.", + getAccountName().c_str(), getCharacterName().c_str(), getCharacterId().getValueString().c_str(), getRemoteAddress().c_str())); } - ClientPermissionsMessage c(canLogin, canCreateRegularCharacter, canCreateJediCharacter, canSkipTutorial); + ClientPermissionsMessage c(canLogin, canCreateRegularCharacter, canCreateJediCharacter, canSkipTutorial, isAdmin); send(c, true); DEBUG_REPORT_LOG(true, ("Permissions for %lu:\n", getSUID())); diff --git a/engine/server/application/ConnectionServer/src/shared/ConfigConnectionServer.cpp b/engine/server/application/ConnectionServer/src/shared/ConfigConnectionServer.cpp index 538e2c01..2eef9f77 100755 --- a/engine/server/application/ConnectionServer/src/shared/ConfigConnectionServer.cpp +++ b/engine/server/application/ConnectionServer/src/shared/ConfigConnectionServer.cpp @@ -117,6 +117,8 @@ void ConfigConnectionServer::install(void) KEY_STRING (altPublicBindAddress, ""); KEY_BOOL (useOldSuidGenerator, false); + KEY_BOOL (useSecureLoginForGodAccess, false); + int index = 0; char const * result = 0; do diff --git a/engine/server/application/ConnectionServer/src/shared/ConfigConnectionServer.h b/engine/server/application/ConnectionServer/src/shared/ConfigConnectionServer.h index 7d5edc42..67741268 100755 --- a/engine/server/application/ConnectionServer/src/shared/ConfigConnectionServer.h +++ b/engine/server/application/ConnectionServer/src/shared/ConfigConnectionServer.h @@ -68,6 +68,8 @@ public: int connectionServerNumber; int fakeBuddyPoints; + bool useSecureLoginForGodAccess; + bool useOldSuidGenerator; const char *altPublicBindAddress; @@ -187,6 +189,9 @@ public: static const char *getPublicBindAddress(); static bool getUseOldSuidGenerator(); + + static bool getUseSecureLoginForGodAccess(); + private: static Data *data; }; @@ -480,4 +485,9 @@ inline bool ConfigConnectionServer::getUseOldSuidGenerator() { return data->useOldSuidGenerator; } +inline bool ConfigConnectionServer::getUseSecureLoginForGodAccess() +{ + return data->useSecureLoginForGodAccess; +} + #endif // _ConfigConnectionServer_H diff --git a/engine/server/application/LoginServer/src/shared/LoginServer.cpp b/engine/server/application/LoginServer/src/shared/LoginServer.cpp index 677ad357..bd053af2 100755 --- a/engine/server/application/LoginServer/src/shared/LoginServer.cpp +++ b/engine/server/application/LoginServer/src/shared/LoginServer.cpp @@ -943,10 +943,10 @@ LoginServer::validateAccount(const StationId &stationId, uint32 clusterId, uint3 bool clientIsInternal = false; ClientConnection *conn = getValidatedClient(stationId); if (conn) { - clientIsInternal = AdminAccountManager::isInternalIp(conn->getRemoteAddress()); + clientIsInternal = conn->getAdminLevel() > 0; } - if (clientIsInternal && ConfigLoginServer::getInternalBypassOnlineLimit()) { + if (clientIsInternal) { canLogin = true; } else if (cle->m_numPlayers <= cle->m_onlinePlayerLimit) { canLogin = true; @@ -968,6 +968,12 @@ LoginServer::validateAccount(const StationId &stationId, uint32 clusterId, uint3 canSkipTutorial = true; } + // double check locked cluster + if(cle->m_locked) + { + canLogin = clientIsInternal; + } + ValidateAccountReplyMessage msg(stationId, canLogin, canCreateRegular, canCreateJedi, canSkipTutorial, track, consumedRewardEvents, claimedRewardItems); cle->m_centralServerConnection->send(msg, true); } @@ -1228,32 +1234,8 @@ LoginServer::onValidateClient(StationId suid, const std::string &username, Clien NOT_NULL(conn); WARNING_STRICT_FATAL(getValidatedClient(suid), ("Validating an already valid client in onValidateClient(). StationId: %d UserName: %s", suid, username.c_str())); - int adminLevel = 0; - const bool isAdminAccount = AdminAccountManager::isAdminAccount(Unicode::toLower(username), adminLevel); - - if (conn->getRequestedAdminSuid() != 0) { - //verify internal, secure, is on the god list - bool loginOK = false; - if (!isSecure) { - LOG("CustomerService", ("AdminLogin: User %s (account %li) attempted to log into account %li, but was not using a SecureID token", username.c_str(), suid, conn->getRequestedAdminSuid())); - } else { - if (!AdminAccountManager::isInternalIp(conn->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, conn->getRequestedAdminSuid())); - } else { - if (!isAdminAccount || 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, conn->getRequestedAdminSuid())); - } else { - suid = conn->getRequestedAdminSuid(); - loginOK = true; - } - } - } - - if (!loginOK) { - conn->disconnect(); - return; - } - } + // determine if this is an admin account + const bool isAdminAccount = AdminAccountManager::getAdminLevel(username) > 0; // encrypt the clients credentials with the key, return // the cipher text to the client for use as a connection @@ -1297,14 +1279,11 @@ LoginServer::onValidateClient(StationId suid, const std::string &username, Clien conn->send(k, true); delete[] keyBuffer; - // send cluster enum - bool clientInternal = AdminAccountManager::isInternalIp(conn->getRemoteAddress()); - std::vector data; for (ClusterListType::const_iterator j = m_clusterList.begin(); j != m_clusterList.end(); ++j) { ClusterListEntry *cle = *j; - if (cle && cle->m_clusterId != 0 && cle->m_clusterName.size() != 0 && (clientInternal || !cle->m_secret)) { + if (cle && cle->m_clusterId != 0 && cle->m_clusterName.size() != 0 && (isAdminAccount || !cle->m_secret)) { LoginEnumCluster::ClusterData item; item.m_clusterId = cle->m_clusterId; item.m_clusterName = cle->m_clusterName; @@ -1331,7 +1310,7 @@ LoginServer::onValidateClient(StationId suid, const std::string &username, Clien conn->setIsValidated(true); conn->setStationId(suid); conn->setIsSecure(isSecure); - conn->setAdminLevel(isAdminAccount ? adminLevel : -1); + conn->setAdminLevel(isAdminAccount ? AdminAccountManager::getAdminLevel(username) : -1); IGNORE_RETURN(m_validatedClientMap.insert(std::pair(suid, conn))); //Must be done after setting various information in the connection object above @@ -1408,11 +1387,16 @@ void LoginServer::sendExtendedClusterInfo(ClientConnection &client) const { /** * Send the list of active clusters to a client. * Also picks a connection server for the client to use. - * @todo We'd like to resend this to all clients whenever the status - * of any servers changes + * + * Note: *** should send to individual clients - NOT all connected + * clients due to admin permissions check *** */ void LoginServer::sendClusterStatus(ClientConnection &conn) const { - const bool clientIsPrivate = AdminAccountManager::isInternalIp(conn.getRemoteAddress()); + + // Validate admin level here as clientIsPrivate. We don't need to check secure/IP because the + // connection server will force disconnect non-secure connections if they are required by configuration. + const bool clientIsPrivate = conn.getAdminLevel() > 0; + const unsigned int subscriptionBits = conn.getSubscriptionBits(); const bool isFreeTrialAccount = (((subscriptionBits & ClientSubscriptionFeature::FreeTrial) != 0) && ((subscriptionBits & ClientSubscriptionFeature::Base) == 0)); @@ -1451,10 +1435,7 @@ void LoginServer::sendClusterStatus(ClientConnection &conn) const { if (item.m_connectionServerPort) { item.m_connectionServerPingPort = connServer.pingPort; - // for security/confidential information issue, only report - // population count to secured internal connections with - // admin privilege >= 10 - if (clientIsPrivate && conn.getIsSecure() && (conn.getAdminLevel() >= 10)) { + if (clientIsPrivate) { item.m_populationOnline = cle->m_numPlayers; } else { item.m_populationOnline = -1; @@ -1493,10 +1474,14 @@ void LoginServer::sendClusterStatus(ClientConnection &conn) const { } else { item.m_status = LoginClusterStatus::ClusterData::S_loading; } - if (cle->m_locked && !clientIsPrivate) { + if (cle->m_locked) { item.m_status = LoginClusterStatus::ClusterData::S_locked; } // locked takes precedence over up or loading + // flag as admin/secret (see LoginClusterStatus.h) + item.m_isAdmin = clientIsPrivate; + item.m_isSecret = cle->m_secret; + item.m_dontRecommend = (cle->m_notRecommendedDatabase || cle->m_notRecommendedCentral); item.m_onlinePlayerLimit = cle->m_onlinePlayerLimit; item.m_onlineFreeTrialLimit = cle->m_onlineFreeTrialLimit; diff --git a/engine/server/library/serverGame/src/shared/command/CommandCppFuncs.cpp b/engine/server/library/serverGame/src/shared/command/CommandCppFuncs.cpp index 7a03d1aa..c8a7749b 100755 --- a/engine/server/library/serverGame/src/shared/command/CommandCppFuncs.cpp +++ b/engine/server/library/serverGame/src/shared/command/CommandCppFuncs.cpp @@ -3392,9 +3392,27 @@ static void commandFuncOpenContainer(Command const & cmd, NetworkId const &actor return; } - //-- don't open factory crates - if (container->getGameObjectType() == SharedObjectTemplate::GOT_misc_factory_crate) + //-- don't open factory crates or crafting tools + const int got = container->getGameObjectType(); + if (got == SharedObjectTemplate::GOT_misc_factory_crate || got == SharedObjectTemplate::GOT_tool_crafting) + { return; + } + + //-- if we are requesting to open an inventory, datapad, or bank, make sure it belongs to us or we're a CSR + //-- this provides better security for /editDatapad /editInventory and /editBank admin commands. + const uint32 crc = container->getTemplateCrc(); + if((player->isPlayerControlled()) && (crc == 2007924155 || crc == -1783727815 || crc == -172438875)) + { + Client * const clientObj = player->getClient(); + if(clientObj) + { + if(container->getOwnerId() != actor && !clientObj->isGod()) + { + return; + } + } + } //-- if they are opening a crafting station, what they really want is the hopper //-- but only if the object is not a volume container @@ -7929,6 +7947,10 @@ static void commandFuncSquelch(Command const &, NetworkId const &actor, NetworkI const Client * gmClient = gm->getClient(); if (gmClient) { + if(!gmClient->isGod()) + { + return; + } char buffer[2048]; if (!target.isValid()) { @@ -7998,6 +8020,10 @@ static void commandFuncUnsquelch(Command const &, NetworkId const &actor, Networ const Client * gmClient = gm->getClient(); if (gmClient) { + if(!gmClient->isGod()) + { + return; + } char buffer[2048]; if (!target.isValid()) { @@ -8067,6 +8093,10 @@ static void commandFuncGrantWarden(Command const &, NetworkId const &actor, Netw const Client * gmClient = gm->getClient(); if (gmClient) { + if(!gmClient->isGod()) + { + return; + } char buffer[2048]; if (!target.isValid()) { @@ -8138,6 +8168,10 @@ static void commandFuncRevokeWarden(Command const &, NetworkId const &actor, Net const Client * gmClient = gm->getClient(); if (gmClient) { + if(!gmClient->isGod()) + { + return; + } char buffer[2048]; if (!target.isValid()) { @@ -8210,16 +8244,16 @@ static void commandFuncSpammer(Command const &, NetworkId const &actor, NetworkI const Client * gmClient = gm->getClient(); if (gmClient) { - // check warden permission first + // check warden permission first or if god const PlayerObject * gmPlayerObject = PlayerCreatureController::getPlayerObject(gm); - if (!gmPlayerObject || !gmPlayerObject->isWarden()) + if (!gmPlayerObject || (!gmPlayerObject->isWarden()) && !gmClient->isGod()) { Chat::sendSystemMessage(*gm, StringId("warden", "not_authorized"), Unicode::emptyString); return; } - // check to see if warden functionality is enabled - if (!ConfigServerGame::getEnableWarden()) + // check to see if warden functionality is enabled but don't restrict if god + if (!ConfigServerGame::getEnableWarden() && !gmClient->isGod()) { Chat::sendSystemMessage(*gm, StringId("warden", "warden_functionality_disabled"), Unicode::emptyString); return; @@ -8304,6 +8338,16 @@ static void commandFuncSpammer(Command const &, NetworkId const &actor, NetworkI } } + // can't spammer someone in god mode or another warden + if(so->getClient()->isGod() || p->isWarden()) + { + ProsePackage prosePackage; + prosePackage.stringId = StringId("warden", "cannot_spammer_target"); + prosePackage.target.str = so->getAssignedObjectName(); + Chat::sendSystemMessage(*gm, prosePackage); + return; + } + // cannot /spammer self if (target == actor) { @@ -8351,9 +8395,9 @@ static void commandFuncUnspammer(Command const &, NetworkId const &actor, Networ const Client * gmClient = gm->getClient(); if (gmClient) { - // check warden permission first + // check warden permission first or if god const PlayerObject * gmPlayerObject = PlayerCreatureController::getPlayerObject(gm); - if (!gmPlayerObject || !gmPlayerObject->isWarden()) + if (!gmPlayerObject || (!gmPlayerObject->isWarden()) && !gmClient->isGod()) { Chat::sendSystemMessage(*gm, StringId("warden", "not_authorized"), Unicode::emptyString); return; @@ -8431,8 +8475,8 @@ static void commandFuncUnspammer(Command const &, NetworkId const &actor, Networ return; } - // can only /unspammer target that I /spammer(ed) - if (p->getSquelchedById() != actor) + // can only /unspammer target that I /spammer(ed) unless I'm god + if (p->getSquelchedById() != actor && !gmClient->isGod()) { ProsePackage prosePackage; prosePackage.stringId = StringId("warden", "cannot_unspammer_target"); @@ -8458,8 +8502,16 @@ static void commandFuncUnspammer(Command const &, NetworkId const &actor, Networ //----------------------------------------------------------------------- +/** + * @deprecated This ungodly idea to empower wardens to grant warden permissions to other players is horrible + * and I don't believe this functionality should remotely exist at all absent intentional implementation + * by a specific server. GMs can use /grantWarden if they want a Warden or the grant can be scripted. + * + * Aconite - SWG Source - 2021 + */ static void commandFuncDeputizeWarden(Command const &, NetworkId const &actor, NetworkId const & target, Unicode::String const ¶ms) { + /* const CreatureObject * gm = dynamic_cast(NetworkIdManager::getObjectById(actor)); if (gm) { @@ -8531,12 +8583,18 @@ static void commandFuncDeputizeWarden(Command const &, NetworkId const &actor, N "C++DeputizeWardenRspCannotDeputize"); } } + */ } //----------------------------------------------------------------------- +/** + * @deprecated + * @see commandFuncDeputizeWarden + */ static void commandFuncUndeputizeWarden(Command const &, NetworkId const &actor, NetworkId const & target, Unicode::String const ¶ms) { + /* const CreatureObject * gm = dynamic_cast(NetworkIdManager::getObjectById(actor)); if (gm) { @@ -8594,6 +8652,7 @@ static void commandFuncUndeputizeWarden(Command const &, NetworkId const &actor, "C++UndeputizeWardenRspCannotUndeputize"); } } + */ } //----------------------------------------------------------------------- diff --git a/engine/server/library/serverGame/src/shared/console/ConsoleCommandParserObject.cpp b/engine/server/library/serverGame/src/shared/console/ConsoleCommandParserObject.cpp index 9ae54ff5..dc2fd5f0 100755 --- a/engine/server/library/serverGame/src/shared/console/ConsoleCommandParserObject.cpp +++ b/engine/server/library/serverGame/src/shared/console/ConsoleCommandParserObject.cpp @@ -475,6 +475,7 @@ static const CommandParser::CmdInfo cmds[] = {"reloadSwgTcgAccountFeatureId", 1, "", "Retrieve the SWG TCG account feature Ids for the specified character"}, {"unloadBuildingContents", 1, "", "Unload the contents of a player-placed authoritative demand-loaded building"}, {"isOnSolidFloor", 1, "", "Checks to see if the specified object is \"on solid floor\""}, + {"proxyToAllGameServers", 1, "", "Creates a Proxy of the given object on all Game Servers."}, {"", 0, "", ""} // this must be last }; @@ -4204,6 +4205,21 @@ bool ConsoleCommandParserObject::performParsing2(const NetworkId & userId, const // ---------------------------------------------------------------------- + else if (isAbbrev(argv[0], "proxyToAllGameServers")) + { + NetworkId const oid(Unicode::wideToNarrow(argv[1])); + auto * const object = dynamic_cast(NetworkIdManager::getObjectById(oid)); + GameServer &gs = GameServer::getInstance(); + if(object) { + gs.createProxyOnAllServers(object); + result += getErrorMessage(argv[0], ERR_SUCCESS); + } else { + result += getErrorMessage(argv[0], ERR_FAIL); + } + } + + // ---------------------------------------------------------------------- + else { result += getErrorMessage(argv[0], ERR_NO_HANDLER); diff --git a/engine/server/library/serverGame/src/shared/console/ConsoleCommandParserServer.cpp b/engine/server/library/serverGame/src/shared/console/ConsoleCommandParserServer.cpp index 63d8fb1d..e0558251 100755 --- a/engine/server/library/serverGame/src/shared/console/ConsoleCommandParserServer.cpp +++ b/engine/server/library/serverGame/src/shared/console/ConsoleCommandParserServer.cpp @@ -195,6 +195,7 @@ static const CommandParser::CmdInfo cmds[] = {"getCommoditiesAuctionLocationPriorityQueue", 1, "", "Gets the top items in the auction location priority queue"}, {"getCommoditiesItemTypeMap", 1, "", "For the specified game object type, gets the list of item type"}, {"setCompletedTutorial", 1, "", "Mark the account belonging to the specified character (character does not have to be logged in) as having completed the tutorial, so that the skip tutorial option will be available during character creation for that account"}, + {"getUniverseProcess", 0, "", "Returns the Game Server Universe Process ID"}, #ifdef _DEBUG {"setExtraDelayPerFrameMs", 1, "", "Do an intentional sleep each frame, to emulate long loop time"}, {"serverinfo", 0, "", "Serverinformation"}, @@ -3449,6 +3450,11 @@ bool ConsoleCommandParserServer::performParsing2(const NetworkId & userId, const result += Unicode::narrowToWide(FormattedString<512>().sprintf("There is no character on this galaxy with id (%s)\n", oid.getValueString().c_str())); } } + else if (isAbbrev(argv[0], "getUniverseProcess")) + { + result += Unicode::narrowToWide(FormattedString<512>().sprintf("Universe Process ID is: %d\n", ServerUniverse::getInstance().getUniverseProcess())); + result += getErrorMessage(argv[0], ERR_SUCCESS); + } #ifdef _DEBUG else if (isAbbrev(argv[0], "setExtraDelayPerFrameMs")) { diff --git a/engine/server/library/serverGame/src/shared/core/Client.cpp b/engine/server/library/serverGame/src/shared/core/Client.cpp index 7d327942..78f2c8ca 100755 --- a/engine/server/library/serverGame/src/shared/core/Client.cpp +++ b/engine/server/library/serverGame/src/shared/core/Client.cpp @@ -501,6 +501,12 @@ void Client::addControlledObject(ServerObject &object) { setGodMode(true); } } + + // validate isUsingAdminLogin each onClientReady() call per SWG Source change - 2021 (Aconite) + // isUsingAdminLogin is used to check if an *account* is in the admin table + // so we can monitor admin accessed accounts *regardless* of their current god mode/level + setUsingAdminLogin(AdminAccountManager::getAdminLevel(getAccountName()) > 0); + } // ---------------------------------------------------------------------- @@ -1937,53 +1943,134 @@ float Client::computeDeltaTimeInSeconds(uint32 const syncStampLong) const { //----------------------------------------------------------------------- +/** + * setGodMode + * Processes the request to turn God Mode in the Client on, + * enabling admin commands and admin-treatment of a CreatureObject. + * + * @param value true = god mode on; false = god mode off + * @return true if successful, false if not + * + * Handling was rewritten by SWG Source - 2021 for applicability + * to Source/VM context + * Authors: Aconite + */ bool Client::setGodMode(bool value) { - // (re?) check god permissions - m_godLevel = AdminAccountManager::getAdminLevel(m_accountName.c_str()); - if (ConfigServerGame::getAdminGodToAll() || (m_godLevel > 0)) { - m_godValidated = true; - if (ConfigServerGame::getAdminGodToAll()) { - m_godLevel = ConfigServerGame::getAdminGodToAllGodLevel(); - } - } + const bool wasInGodMode = m_godMode; + auto * creatureObject = safe_cast(m_primaryControlledObject.getObject()); + ScriptParams params; - bool wasInGodMode = m_godMode; - m_godMode = value; - - if (value && !m_godValidated) { - LOG("CustomerService", ("Avatar:%s denied god mode because it wasn't validated.", PlayerObject::getAccountDescription(getCharacterObjectId()).c_str())); - m_godMode = false; - } - - CreatureObject *primaryControlledObject = safe_cast(m_primaryControlledObject.getObject()); - if (!primaryControlledObject) { - if (value && !wasInGodMode) { - LOG("CustomerService", ("Avatar:%s denied god mode because it has no associated character.", m_accountName.c_str())); - } - m_godMode = false; + if(!creatureObject) + { + LOG("GodMode", ("[%s : %s : %s : %s] /setGodMode failed because we couldn't get the CreatureObject.", + getAccountName().c_str(), getCharacterName().c_str(), getCharacterObjectId().getValueString().c_str(), getIpAddress().c_str())); return false; } - if (m_godMode) { - IGNORE_RETURN(primaryControlledObject->grantCommand(AdminAccountManager::getAdminCommandName(), false)); - } else { - primaryControlledObject->revokeCommand(AdminAccountManager::getAdminCommandName(), false, true); - } - - if (m_godMode != wasInGodMode) { - if (m_godMode) { - LOG("CustomerService", ("Avatar:%s granted god mode %s level %d.", PlayerObject::getAccountDescription(getCharacterObjectId()).c_str(), AdminAccountManager::getAdminCommandName(), m_godLevel)); - } else { - LOG("CustomerService", ("Avatar:%s dropped god mode.", PlayerObject::getAccountDescription(getCharacterObjectId()).c_str())); + // Request to turn God Mode Off, in which case we don't need to validate permissions, because off isn't bad + if(!value) + { + // If we were already in god mode, then we've already made an authenticated request to be in god mode, + // so log the event and proceed as an authorized activity, unless a script trigger blocks the switch. + if(wasInGodMode) + { + // allow a SCRIPT_OVERRIDE to block turning god mode off if necessary + // see OnTurnedGodModeOff in script.player.base_player.java + if(creatureObject->getScriptObject()->trigAllScripts(Scripting::TRIG_ON_SET_GOD_MODE_OFF, params) != SCRIPT_CONTINUE) + { + LOG("GodMode", ("[%s : %s : %s : %s] /setGodMode off failed because OnSetGodModeOff did not return SCRIPT_CONTINUE.", + getAccountName().c_str(), getCharacterName().c_str(), getCharacterObjectId().getValueString().c_str(), getIpAddress().c_str())); + return false; + } + else + { + LOG("GodMode", ("[%s : %s : %s : %s] /setGodMode off success.", + getAccountName().c_str(), getCharacterName().c_str(), getCharacterObjectId().getValueString().c_str(), getIpAddress().c_str())); + } } - - // cell permissions may change for us for all cells, so observe all buildings in range for the change - CellPermissions::ViewerChangeObserver o(primaryControlledObject); - + m_godLevel = 0; + m_godMode = false; + m_godValidated = false; + // reset observers and notify cells of potential change + CellPermissions::ViewerChangeObserver o(creatureObject); ObserveTracker::onGodModeChanged(*this); + // remove the characterAbility "admin" so GM commands aren't sent by the client while not in God Mode + IGNORE_RETURN(creatureObject->revokeCommand(AdminAccountManager::getAdminCommandName(), false, true)); + return true; } - return (value == m_godMode); // return true if the value was set to what was requested, false otherwise + + // If we're here, it's because the request is to turn God Mode ON + // First, determine if we're using Secure Login Mode (IP-restricted) + if(ConfigServerGame::getUseSecureLoginForGodAccess()) + { + if(!AdminAccountManager::isInternalIp(getIpAddress())) + { + LOG("GodMode", ("[%s : %s : %s : %s] /setGodMode on failed because secure login was required and the connection IP was not approved.", + getAccountName().c_str(), getCharacterName().c_str(), getCharacterObjectId().getValueString().c_str(), getIpAddress().c_str())); + return false; + } + } + + // If we aren't required to be secure, or if we are and we are secure, begin permission checks + // Start with if everyone can have God Mode, in which case we don't need to bother otherwise + int godLevel = 0; + if(ConfigServerGame::getAdminGodToAll() && ConfigServerGame::getAdminGodToAllGodLevel() > 0) + { + // Make sure the account isn't in the admin table, because that should supersede + // whatever the adminGodToAll level is. + if(AdminAccountManager::getAdminLevel(m_accountName) == 0) + { + godLevel = ConfigServerGame::getAdminGodToAllGodLevel(); + } + else + { + godLevel = AdminAccountManager::getAdminLevel(m_accountName); + } + } + // We aren't giving god to everyone, so validate actual god level permissions + else + { + godLevel = AdminAccountManager::getAdminLevel(m_accountName); + } + // Grant God Mode + if(godLevel > 0) + { + // Allow a SCRIPT_OVERRIDE to block turning God Mode on via OnSetGodModeOn trigger + // see script.player.player_base.java + if(creatureObject->getScriptObject()->trigAllScripts(Scripting::TRIG_ON_SET_GOD_MODE_ON, params) != SCRIPT_CONTINUE) + { + LOG("GodMode", ("[%s : %s : %s : %s] /setGodMode on failed because OnSetGodModeOn did not return SCRIPT_CONTINUE.", + getAccountName().c_str(), getCharacterName().c_str(), getCharacterObjectId().getValueString().c_str(), getIpAddress().c_str())); + return false; + } + else + { + // flags + m_godMode = true; + m_godValidated = true; + m_godLevel = godLevel; + + // grant all commands with the "admin" characterAbility + IGNORE_RETURN(creatureObject->grantCommand(AdminAccountManager::getAdminCommandName(), false)); + + // reset observers and notify cells of potential change + CellPermissions::ViewerChangeObserver o(creatureObject); + ObserveTracker::onGodModeChanged(*this); + + LOG("GodMode", ("[%s : %s : %s : %s] /setGodMode on success.", + getAccountName().c_str(), getCharacterName().c_str(), getCharacterObjectId().getValueString().c_str(), getIpAddress().c_str())); + return true; + } + } + // If we're here, it's because someone tried to use /setGod who doesn't have permissions + else + { + LOG("GodMode", ("[%s : %s : %s : %s] /setGodMode on failed because the requesting account does not have permissions.", + getAccountName().c_str(), getCharacterName().c_str(), getCharacterObjectId().getValueString().c_str(), getIpAddress().c_str())); + return false; + } + } //----------------------------------------------------------------------- @@ -2165,12 +2252,20 @@ void Client::launchWebBrowser(std::string const &url) const { // ---------------------------------------------------------------------- -bool Client::isUsingAdminLogin() const { +bool Client::isUsingAdminLogin() const +{ return m_usingAdminLogin; } // ---------------------------------------------------------------------- +void Client::setUsingAdminLogin(bool value) +{ + m_usingAdminLogin = value; +} + +// ---------------------------------------------------------------------- + bool Client::shouldReceiveCombatSpam(NetworkId const &attacker, Vector const &attackerPosition_w, NetworkId const &defender, Vector const &defenderPosition_w) const { PROFILER_AUTO_BLOCK_DEFINE("Client::shouldReceiveCombatSpam"); diff --git a/engine/server/library/serverGame/src/shared/core/Client.h b/engine/server/library/serverGame/src/shared/core/Client.h index e495059b..9fcf1724 100755 --- a/engine/server/library/serverGame/src/shared/core/Client.h +++ b/engine/server/library/serverGame/src/shared/core/Client.h @@ -105,6 +105,8 @@ public: bool isUsingAdminLogin() const; + void setUsingAdminLogin(bool value); + CombatDataTable::CombatSpamFilterType getCombatSpamFilter() const; int getCombatSpamRangeSquaredFilter() const; diff --git a/engine/server/library/serverGame/src/shared/core/ReportManager.cpp b/engine/server/library/serverGame/src/shared/core/ReportManager.cpp index b73e89ff..843373c7 100755 --- a/engine/server/library/serverGame/src/shared/core/ReportManager.cpp +++ b/engine/server/library/serverGame/src/shared/core/ReportManager.cpp @@ -18,6 +18,10 @@ #include "sharedFoundation/FormattedString.h" #include "sharedGame/ProsePackage.h" #include "sharedGame/ProsePackageParticipant.h" +#include "serverScript/GameScriptObject.h" +#include "serverScript/ScriptDictionary.h" +#include "serverScript/ScriptFunctionTable.h" +#include "serverScript/ScriptParameters.h" #include "sharedLog/Log.h" #include "sharedNetworkMessages/ChatAvatarId.h" #include "sharedNetworkMessages/ChatOnRequestLog.h" @@ -331,10 +335,18 @@ void ReportManager::handleMessage(ChatOnRequestLog const &chatOnRequestLog) report.append(reportData.m_clientData); } - if (!reportData.m_reportLogChannel.empty()) - LOGU("CustomerService", (reportData.m_reportLogChannel.c_str()), report); - else - LOGU("CustomerService", ("Report:"), report); + //if (!reportData.m_reportLogChannel.empty()) + // LOGU("CustomerService", (reportData.m_reportLogChannel.c_str()), report); + //else + // LOGU("CustomerService", ("Report:"), report); + + if(reportingCreatureObject->getScriptObject()) + { + ScriptParams p; + p.addParam(reportData.m_harassingNetworkId); + p.addParam(report); + IGNORE_RETURN(reportingCreatureObject->getScriptObject()->trigAllScripts(Scripting::TRIG_ON_PLAYER_REPORTED_CHAT, p)); + } // System message for success diff --git a/engine/server/library/serverGame/src/shared/core/ServerBuildoutManager.cpp b/engine/server/library/serverGame/src/shared/core/ServerBuildoutManager.cpp index 53e6063c..7bbbb1a9 100755 --- a/engine/server/library/serverGame/src/shared/core/ServerBuildoutManager.cpp +++ b/engine/server/library/serverGame/src/shared/core/ServerBuildoutManager.cpp @@ -662,6 +662,7 @@ void ServerBuildoutManagerNamespace::loadArea(AreaInfo &areaInfo) std::set< int64 > objects; + int64 originalObjId = 0; for (int buildoutRow = 0; buildoutRow < buildoutRowCount; ++buildoutRow) { int64 objId = 0; @@ -726,7 +727,8 @@ void ServerBuildoutManagerNamespace::loadArea(AreaInfo &areaInfo) } else { - objId = areaBuildoutTable.getIntValue(objIdColumn, buildoutRow); + originalObjId = areaBuildoutTable.getIntValue(objIdColumn, buildoutRow); + objId = originalObjId; containerId = areaBuildoutTable.getIntValue(containerColumn, buildoutRow); // with new buildout files, the object id is a random 31-bit negative value @@ -776,6 +778,10 @@ void ServerBuildoutManagerNamespace::loadArea(AreaInfo &areaInfo) if (containerId == 0 || objects.find(containerId) != objects.end()) { + // store original ObjId from buildout table as ObjVar on item for reference in-game if needed + std::string objVars = areaBuildoutTable.getStringValue(objvarsColumn, buildoutRow); + objVars.insert(0, "buildoutObjectId|0|" + std::to_string(originalObjId) + "|"); + areaInfo.buildoutRows.push_back( BuildoutRow( objId, @@ -784,7 +790,7 @@ void ServerBuildoutManagerNamespace::loadArea(AreaInfo &areaInfo) transform_p, serverTemplate, areaBuildoutTable.getStringValue(scriptsColumn, buildoutRow), - areaBuildoutTable.getStringValue(objvarsColumn, buildoutRow), + objVars, eventRequired, requiredLoadLevel)); } diff --git a/engine/server/library/serverGame/src/shared/object/ServerObject.cpp b/engine/server/library/serverGame/src/shared/object/ServerObject.cpp index 598f2e1f..585cd19f 100755 --- a/engine/server/library/serverGame/src/shared/object/ServerObject.cpp +++ b/engine/server/library/serverGame/src/shared/object/ServerObject.cpp @@ -1963,9 +1963,6 @@ void ServerObject::serverObjectEndBaselines(bool fromDatabase) // If the object was created authoritative, trigger if needed if (isAuthoritative()) triggerMadeAuthoritative(); - - if (markedNoTradeRemovable()) - getScriptObject()->attachScript("item.special.no_trade_removable", true); } if (isPlayerControlled()) @@ -2566,7 +2563,13 @@ bool ServerObject::serverObjectInitializeFirstTimeObject(ServerObject *cell, Tra int count = newTemplate->getScriptsCount(); for (int i = 0; i < count; ++i) + { getScriptObject()->attachScript(newTemplate->getScripts(i), true); + } + if (markedNoTradeRemovable()) + { + getScriptObject()->attachScript("item.special.no_trade_removable", true); + } if (cell) { diff --git a/engine/server/library/serverScript/src/shared/ScriptFunctionTable.cpp b/engine/server/library/serverScript/src/shared/ScriptFunctionTable.cpp index 3a56b674..c3f80bd8 100755 --- a/engine/server/library/serverScript/src/shared/ScriptFunctionTable.cpp +++ b/engine/server/library/serverScript/src/shared/ScriptFunctionTable.cpp @@ -330,6 +330,7 @@ static const Scripting::ScriptFuncTable ScriptFuncList[] = {Scripting::TRIG_ENTER_REGION, "OnEnterRegion", "ss"}, {Scripting::TRIG_EXIT_REGION, "OnExitRegion", "ss"}, + {Scripting::TRIG_ON_PLAYER_REPORTED_CHAT, "OnPlayerReportedChat", "Ou"}, //---------------------------------------------------------------------- {Scripting::TRIG_VENDOR_ITEM_COUNT_REPLY, "OnVendorItemCountReply", "ii"}, @@ -379,6 +380,9 @@ static const Scripting::ScriptFuncTable ScriptFuncList[] = {Scripting::TRIG_PVP_RANKING_CHANGED, "OnPvpRankingChanged", "ii"}, + {Scripting::TRIG_ON_SET_GOD_MODE_ON, "OnSetGodModeOn", ""}, + {Scripting::TRIG_ON_SET_GOD_MODE_OFF, "OnSetGodModeOff", ""}, + //----------------------------------------------------------------------- //-- buff builder diff --git a/engine/server/library/serverScript/src/shared/ScriptFunctionTable.h b/engine/server/library/serverScript/src/shared/ScriptFunctionTable.h index 63b508c0..3bb59f51 100755 --- a/engine/server/library/serverScript/src/shared/ScriptFunctionTable.h +++ b/engine/server/library/serverScript/src/shared/ScriptFunctionTable.h @@ -480,6 +480,9 @@ enum TrigId TRIG_ON_ABANDON_PLAYER_QUEST = 302, TRIG_ON_GCW_SCORE_CATEGORY_PERCENTILE_CHANGE = 303, TRIG_WAYPOINT_WARP = 304, + TRIG_ON_PLAYER_REPORTED_CHAT = 305, + TRIG_ON_SET_GOD_MODE_ON = 306, + TRIG_ON_SET_GOD_MODE_OFF = 307, TRIG_LAST_TRIGGER }; diff --git a/engine/server/library/serverScript/src/shared/ScriptMethodsPlanet.cpp b/engine/server/library/serverScript/src/shared/ScriptMethodsPlanet.cpp index ff933506..27362089 100755 --- a/engine/server/library/serverScript/src/shared/ScriptMethodsPlanet.cpp +++ b/engine/server/library/serverScript/src/shared/ScriptMethodsPlanet.cpp @@ -481,7 +481,7 @@ jstring JNICALL ScriptMethodsPlanetNamespace::getBuildoutAreaName( JNIEnv *const return 0; } - const BuildoutArea * const area = SharedBuildoutAreaManager::findBuildoutAreaAtPosition(sceneId.c_str(), x, z, false ); + const BuildoutArea * const area = SharedBuildoutAreaManager::findBuildoutAreaAtPosition(sceneId, x, z, false, false); return area ? JavaString( area->areaName ).getReturnValue() : 0; } diff --git a/engine/shared/library/sharedNetworkMessages/src/shared/clientGameServer/ClientPermissionsMessage.cpp b/engine/shared/library/sharedNetworkMessages/src/shared/clientGameServer/ClientPermissionsMessage.cpp index 8e3c0aea..7d4652f5 100755 --- a/engine/shared/library/sharedNetworkMessages/src/shared/clientGameServer/ClientPermissionsMessage.cpp +++ b/engine/shared/library/sharedNetworkMessages/src/shared/clientGameServer/ClientPermissionsMessage.cpp @@ -10,17 +10,19 @@ // ====================================================================== -ClientPermissionsMessage::ClientPermissionsMessage(bool canLogin, bool canCreateRegularCharacter, bool canCreateJediCharacter, bool canSkipTutorial) : +ClientPermissionsMessage::ClientPermissionsMessage(bool canLogin, bool canCreateRegularCharacter, bool canCreateJediCharacter, bool canSkipTutorial, bool isAdmin) : GameNetworkMessage("ClientPermissionsMessage"), m_canLogin(canLogin), m_canCreateRegularCharacter(canCreateRegularCharacter), m_canCreateJediCharacter(canCreateJediCharacter), - m_canSkipTutorial(canSkipTutorial) + m_canSkipTutorial(canSkipTutorial), + m_isAdmin(isAdmin) { addVariable(m_canLogin); addVariable(m_canCreateRegularCharacter); addVariable(m_canCreateJediCharacter); addVariable(m_canSkipTutorial); + addVariable(m_isAdmin); } //----------------------------------------------------------------------- @@ -30,12 +32,14 @@ ClientPermissionsMessage::ClientPermissionsMessage(Archive::ReadIterator & sourc m_canLogin(), m_canCreateRegularCharacter(), m_canCreateJediCharacter(), - m_canSkipTutorial() + m_canSkipTutorial(), + m_isAdmin() { addVariable(m_canLogin); addVariable(m_canCreateRegularCharacter); addVariable(m_canCreateJediCharacter); addVariable(m_canSkipTutorial); + addVariable(m_isAdmin); unpack(source); } diff --git a/engine/shared/library/sharedNetworkMessages/src/shared/clientGameServer/ClientPermissionsMessage.h b/engine/shared/library/sharedNetworkMessages/src/shared/clientGameServer/ClientPermissionsMessage.h index 8ad6c9da..374a63d8 100755 --- a/engine/shared/library/sharedNetworkMessages/src/shared/clientGameServer/ClientPermissionsMessage.h +++ b/engine/shared/library/sharedNetworkMessages/src/shared/clientGameServer/ClientPermissionsMessage.h @@ -24,7 +24,7 @@ class ClientPermissionsMessage : public GameNetworkMessage { public: - ClientPermissionsMessage(bool canLogin, bool canCreateRegularCharacter, bool canCreateJediCharacter, bool canSkipTutorial); + ClientPermissionsMessage(bool canLogin, bool canCreateRegularCharacter, bool canCreateJediCharacter, bool canSkipTutorial, bool isAdmin); explicit ClientPermissionsMessage(Archive::ReadIterator & source); virtual ~ClientPermissionsMessage(); @@ -33,12 +33,14 @@ class ClientPermissionsMessage : public GameNetworkMessage bool getCanCreateRegularCharacter() const; bool getCanCreateJediCharacter() const; bool getCanSkipTutorial() const; + bool getIsAdmin() const; private: Archive::AutoVariable m_canLogin; Archive::AutoVariable m_canCreateRegularCharacter; Archive::AutoVariable m_canCreateJediCharacter; Archive::AutoVariable m_canSkipTutorial; + Archive::AutoVariable m_isAdmin; ClientPermissionsMessage(); ClientPermissionsMessage(const ClientPermissionsMessage&); @@ -73,6 +75,13 @@ inline bool ClientPermissionsMessage::getCanSkipTutorial() const return m_canSkipTutorial.get(); } +// ---------------------------------------------------------------------- + +inline bool ClientPermissionsMessage::getIsAdmin() const +{ + return m_isAdmin.get(); +} + // ====================================================================== #endif // _INCLUDED_ClientCentralMessages_H diff --git a/engine/shared/library/sharedNetworkMessages/src/shared/clientLoginServer/LoginClusterStatus.h b/engine/shared/library/sharedNetworkMessages/src/shared/clientLoginServer/LoginClusterStatus.h index 6c074132..69897510 100755 --- a/engine/shared/library/sharedNetworkMessages/src/shared/clientLoginServer/LoginClusterStatus.h +++ b/engine/shared/library/sharedNetworkMessages/src/shared/clientLoginServer/LoginClusterStatus.h @@ -31,6 +31,17 @@ struct LoginClusterStatus_ClusterData bool m_dontRecommend; uint32 m_onlinePlayerLimit; uint32 m_onlineFreeTrialLimit; + + // This flag tells the client that the account which requested the cluster information + // is considered an admin by the server (in Admin Account Table). This enables connection + // to locked clusters and viewing secret clusters. We don't need to worry about this flag + // being manipulated because the Connection Server verifies admin permissions after the fact. + // This flag merely unblocks client-side user interface elements for locked/secret clusters. + // We also send this flag in the ClientPermissionsMessage header for character creation. + bool m_isAdmin; + // Flag so we can append (Secret) in Client (for admins) + bool m_isSecret; + }; /** @@ -85,6 +96,8 @@ namespace Archive get(source,c.m_dontRecommend); get(source,c.m_onlinePlayerLimit); get(source,c.m_onlineFreeTrialLimit); + get(source, c.m_isAdmin); + get(source,c.m_isSecret); } inline void put(ByteStream & target, const LoginClusterStatus_ClusterData &c) @@ -101,6 +114,8 @@ namespace Archive put(target,c.m_dontRecommend); put(target,c.m_onlinePlayerLimit); put(target,c.m_onlineFreeTrialLimit); + put(target,c.m_isAdmin); + put(target,c.m_isSecret); } }