diff --git a/src/ChatManager.cpp b/src/ChatManager.cpp index 2cf0df6..018eed5 100644 --- a/src/ChatManager.cpp +++ b/src/ChatManager.cpp @@ -845,6 +845,86 @@ void unregisterallCommand(std::string full, std::vector& args, CNSo sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_SUCC, sizeof(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_SUCC)); } +void banCommand(std::string full, std::vector& args, CNSocket *sock) { + Player* plr = PlayerManager::getPlayer(sock); + + if (args.size() < 2) { + ChatManager::sendServerMessage(sock, "Usage: /ban PlayerID [reason...]"); + return; + } + + char *rest; + int playerId = std::strtol(args[1].c_str(), &rest, 10); + if (*rest) { + ChatManager::sendServerMessage(sock, "Invalid PlayerID: " + args[1]); + return; + } + + std::string reason; + if (args.size() == 2) { + reason = "no reason given"; + } else { + reason = args[2]; + for (int i = 3; i < args.size(); i++) + reason += " " + args[i]; + } + + // ban the account that player belongs to + if (!Database::banPlayer(playerId, reason)) { + // propagating a more descriptive error message from banPlayer() would be too much work + ChatManager::sendServerMessage(sock, "Failed to ban target player. Check server logs."); + return; + } + + ChatManager::sendServerMessage(sock, "Banned target player."); + std::cout << "[INFO] " << PlayerManager::getPlayerName(plr) << " banned player " << playerId << std::endl; + + // if the player is online, kick them + CNSocket *otherSock = PlayerManager::getSockFromID(playerId); + if (otherSock == nullptr) { + ChatManager::sendServerMessage(sock, "Player wasn't online. Didn't need to kick."); + return; + } + + Player *otherPlr = PlayerManager::getPlayer(otherSock); + + INITSTRUCT(sP_FE2CL_REP_PC_EXIT_SUCC, pkt); + + pkt.iID = otherPlr->iID; + pkt.iExitCode = 3; // "a GM has terminated your connection" + + // send to target player + otherSock->sendPacket((void*)&pkt, P_FE2CL_REP_PC_EXIT_SUCC, sizeof(sP_FE2CL_REP_PC_EXIT_SUCC)); + + // ensure that the connection has terminated + otherSock->kill(); + + ChatManager::sendServerMessage(sock, PlayerManager::getPlayerName(otherPlr) + " was online. Kicked."); +} + +void unbanCommand(std::string full, std::vector& args, CNSocket *sock) { + Player* plr = PlayerManager::getPlayer(sock); + + if (args.size() < 2) { + ChatManager::sendServerMessage(sock, "Usage: /unban PlayerID"); + return; + } + + char *rest; + int playerId = std::strtol(args[1].c_str(), &rest, 10); + if (*rest) { + ChatManager::sendServerMessage(sock, "Invalid PlayerID: " + args[1]); + return; + } + + if (Database::unbanPlayer(playerId)) { + ChatManager::sendServerMessage(sock, "Unbanned player."); + std::cout << "[INFO] " << PlayerManager::getPlayerName(plr) << " unbanned player " << playerId << std::endl; + } else { + ChatManager::sendServerMessage(sock, "Failed to unban player. Check server logs."); + } +} + void ChatManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_FREECHAT_MESSAGE, chatHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT, emoteHandler); @@ -873,6 +953,8 @@ void ChatManager::init() { registerCommand("players", 30, playersCommand, "print all players on the server"); registerCommand("summonGroup", 30, summonGroupCommand, "summon group NPCs"); registerCommand("summonGroupW", 30, summonGroupCommand, "permanently summon group NPCs"); + registerCommand("ban", 30, banCommand, "ban the account the given PlayerID belongs to"); + registerCommand("unban", 30, unbanCommand, "unban the account the given PlayerID belongs to"); registerCommand("whois", 50, whoisCommand, "describe nearest NPC"); registerCommand("lair", 50, lairUnlockCommand, "get the required mission for the nearest fusion lair"); registerCommand("hide", 100, hideCommand, "hide yourself from the global player map"); diff --git a/src/Database.cpp b/src/Database.cpp index 5d1d3c6..16dff95 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -304,24 +304,121 @@ int Database::addAccount(std::string login, std::string password) { return sqlite3_last_insert_rowid(db); } -void Database::banAccount(int accountId, int days) { - std::lock_guard lock(dbCrit); - +// NOTE: internal function; does not lock dbCrit +bool Database::banAccount(int accountId, int days, std::string& reason) { const char* sql = R"( UPDATE Accounts SET BannedSince = (strftime('%s', 'now')), - BannedUntil = (strftime('%s', 'now')) + ? + BannedUntil = (strftime('%s', 'now')) + ?, + BanReason = ? WHERE AccountID = ?; )"; sqlite3_stmt* stmt; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + sqlite3_bind_int(stmt, 1, days * 86400); // convert days to seconds - sqlite3_bind_int(stmt, 2, accountId); + sqlite3_bind_text(stmt, 2, reason.c_str(), -1, NULL); + sqlite3_bind_int(stmt, 3, accountId); + if (sqlite3_step(stmt) != SQLITE_DONE) { - std::cout << "[WARN] Database: failed to ban player: " << sqlite3_errmsg(db) << std::endl; + std::cout << "[WARN] Database: failed to ban account: " << sqlite3_errmsg(db) << std::endl; + sqlite3_finalize(stmt); + return false; } + sqlite3_finalize(stmt); + return true; +} + +// NOTE: internal function; does not lock dbCrit +bool Database::unbanAccount(int accountId) { + const char* sql = R"( + UPDATE Accounts SET + BannedSince = 0, + BannedUntil = 0, + BanReason = '' + WHERE AccountID = ?; + )"; + sqlite3_stmt* stmt; + + sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + + sqlite3_bind_int(stmt, 1, accountId); + + if (sqlite3_step(stmt) != SQLITE_DONE) { + std::cout << "[WARN] Database: failed to unban account: " << sqlite3_errmsg(db) << std::endl; + sqlite3_finalize(stmt); + return false; + } + + sqlite3_finalize(stmt); + return true; +} + +// expressed in days +#define THIRTY_YEARS 10957 + +bool Database::banPlayer(int playerId, std::string& reason) { + std::lock_guard lock(dbCrit); + + int accountLevel; + int accountId = getAccountIDFromPlayerID(playerId, &accountLevel); + if (accountId < 0) { + return false; + } + + if (accountLevel <= 30) { + std::cout << "[WARN] Cannot ban a GM." << std::endl; + return false; + } + + // do the ban + if (!banAccount(accountId, THIRTY_YEARS, reason)) { + return false; + } + + return true; +} + +bool Database::unbanPlayer(int playerId) { + std::lock_guard lock(dbCrit); + + int accountId = getAccountIDFromPlayerID(playerId); + if (accountId < 0) + return false; + + return unbanAccount(accountId); +} + +int Database::getAccountIDFromPlayerID(int playerId, int *accountLevel) { + const char *sql = R"( + SELECT Players.AccountID, AccountLevel + FROM Players + JOIN Accounts ON Players.AccountID = Accounts.AccountID + WHERE PlayerID = ?; + )"; + sqlite3_stmt *stmt; + + // get AccountID from PlayerID + sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + sqlite3_bind_int(stmt, 1, playerId); + + if (sqlite3_step(stmt) != SQLITE_ROW) { + std::cout << "[WARN] Database: failed to get AccountID from PlayerID: " << sqlite3_errmsg(db) << std::endl; + sqlite3_finalize(stmt); + return -1; + } + + int accountId = sqlite3_column_int(stmt, 0); + + // optional secondary return value, for checking GM status + if (accountLevel != nullptr) + *accountLevel = sqlite3_column_int(stmt, 1); + + sqlite3_finalize(stmt); + + return accountId; } void Database::updateSelected(int accountId, int slot) { @@ -1845,7 +1942,7 @@ void Database::postRaceRanking(Database::RaceRanking ranking) { sqlite3_finalize(stmt); } -bool Database::isCodeRedeemed (int playerId, std::string code) { +bool Database::isCodeRedeemed(int playerId, std::string code) { std::lock_guard lock(dbCrit); const char* sql = R"( diff --git a/src/Database.hpp b/src/Database.hpp index 0cb5628..4b26471 100644 --- a/src/Database.hpp +++ b/src/Database.hpp @@ -48,7 +48,12 @@ namespace Database { void findAccount(Account* account, std::string login); /// returns ID, 0 if something failed int addAccount(std::string login, std::string password); - void banAccount(int accountId, int days); + bool banAccount(int accountId, int days, std::string& reason); + bool unbanAccount(int accountId); + bool banPlayer(int playerId, std::string& reason); + bool unbanPlayer(int playerId); + + int getAccountIDFromPlayerID(int playerId, int *accountLevel=nullptr); void updateSelected(int accountId, int playerId); bool validateCharacter(int characterID, int userID);