/** * MaNGOS is a full featured server for World of Warcraft, supporting * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 * * Copyright (C) 2005-2015 MaNGOS project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * World of Warcraft, and all World of Warcraft or Warcraft art, images, * and lore are copyrighted by Blizzard Entertainment, Inc. */ /// \addtogroup mangosd /// @{ /// \file #include "Common.h" #include "Language.h" #include "Log.h" #include "World.h" #include "ObjectMgr.h" #include "WorldSession.h" #include "Config/Config.h" #include "Util.h" #include "AccountMgr.h" #include "CliRunnable.h" #include "MapManager.h" #include "Player.h" #include "Chat.h" void utf8print(void* /*arg*/, const char* str) { #if PLATFORM == PLATFORM_WINDOWS wchar_t wtemp_buf[6000]; size_t wtemp_len = 6000 - 1; if (!Utf8toWStr(str, strlen(str), wtemp_buf, wtemp_len)) { return; } char temp_buf[6000]; CharToOemBuffW(&wtemp_buf[0], &temp_buf[0], wtemp_len + 1); printf("%s", temp_buf); #else printf("%s", str); #endif } void commandFinished(void*, bool /*sucess*/) { printf("mangos>"); fflush(stdout); } /// Delete a user account and all associated characters in this realm /// \todo This function has to be enhanced to respect the login/realm split (delete char, delete account chars in realm, delete account chars in realm then delete account bool ChatHandler::HandleAccountDeleteCommand(char* args) { if (!*args) { return false; } std::string account_name; uint32 account_id = ExtractAccountId(&args, &account_name); if (!account_id) { return false; } /// Commands not recommended call from chat, but support anyway /// can delete only for account with less security /// This is also reject self apply in fact if (HasLowerSecurityAccount(NULL, account_id, true)) { return false; } AccountOpResult result = sAccountMgr.DeleteAccount(account_id); switch (result) { case AOR_OK: PSendSysMessage(LANG_ACCOUNT_DELETED, account_name.c_str()); break; case AOR_NAME_NOT_EXIST: PSendSysMessage(LANG_ACCOUNT_NOT_EXIST, account_name.c_str()); SetSentErrorMessage(true); return false; case AOR_DB_INTERNAL_ERROR: PSendSysMessage(LANG_ACCOUNT_NOT_DELETED_SQL_ERROR, account_name.c_str()); SetSentErrorMessage(true); return false; default: PSendSysMessage(LANG_ACCOUNT_NOT_DELETED, account_name.c_str()); SetSentErrorMessage(true); return false; } return true; } /** * Collects all GUIDs (and related info) from deleted characters which are still in the database. * * @param foundList a reference to an std::list which will be filled with info data * @param searchString the search string which either contains a player GUID (low part) or a part of the character-name * @return returns false if there was a problem while selecting the characters (e.g. player name not normalizeable) */ bool ChatHandler::GetDeletedCharacterInfoList(DeletedInfoList& foundList, std::string searchString) { QueryResult* resultChar; if (!searchString.empty()) { // search by GUID if (isNumeric(searchString)) { resultChar = CharacterDatabase.PQuery("SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL AND guid = %u", uint32(atoi(searchString.c_str()))); } // search by name else { if (!normalizePlayerName(searchString)) { return false; } resultChar = CharacterDatabase.PQuery("SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL AND deleteInfos_Name " _LIKE_ " " _CONCAT3_("'%%'", "'%s'", "'%%'"), searchString.c_str()); } } else { resultChar = CharacterDatabase.Query("SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL"); } if (resultChar) { do { Field* fields = resultChar->Fetch(); DeletedInfo info; info.lowguid = fields[0].GetUInt32(); info.name = fields[1].GetCppString(); info.accountId = fields[2].GetUInt32(); // account name will be empty for nonexistent account sAccountMgr.GetName(info.accountId, info.accountName); info.deleteDate = time_t(fields[3].GetUInt64()); foundList.push_back(info); } while (resultChar->NextRow()); delete resultChar; } return true; } /** * Generate WHERE guids list by deleted info in way preventing return too long where list for existed query string length limit. * * @param itr a reference to an deleted info list iterator, it updated in function for possible next function call if list to long * @param itr_end a reference to an deleted info list iterator end() * @return returns generated where list string in form: 'guid IN (gui1, guid2, ...)' */ std::string ChatHandler::GenerateDeletedCharacterGUIDsWhereStr(DeletedInfoList::const_iterator& itr, DeletedInfoList::const_iterator const& itr_end) { std::ostringstream wherestr; wherestr << "guid IN ('"; for (; itr != itr_end; ++itr) { wherestr << itr->lowguid; if (wherestr.str().size() > MAX_QUERY_LEN - 50) // near to max query { ++itr; break; } DeletedInfoList::const_iterator itr2 = itr; if (++itr2 != itr_end) { wherestr << "','"; } } wherestr << "')"; return wherestr.str(); } /** * Shows all deleted characters which matches the given search string, expected non empty list * * @see ChatHandler::HandleCharacterDeletedListCommand * @see ChatHandler::HandleCharacterDeletedRestoreCommand * @see ChatHandler::HandleCharacterDeletedDeleteCommand * @see ChatHandler::DeletedInfoList * * @param foundList contains a list with all found deleted characters */ void ChatHandler::HandleCharacterDeletedListHelper(DeletedInfoList const& foundList) { if (!m_session) { SendSysMessage(LANG_CHARACTER_DELETED_LIST_BAR); SendSysMessage(LANG_CHARACTER_DELETED_LIST_HEADER); SendSysMessage(LANG_CHARACTER_DELETED_LIST_BAR); } for (DeletedInfoList::const_iterator itr = foundList.begin(); itr != foundList.end(); ++itr) { std::string dateStr = TimeToTimestampStr(itr->deleteDate); if (!m_session) PSendSysMessage(LANG_CHARACTER_DELETED_LIST_LINE_CONSOLE, itr->lowguid, itr->name.c_str(), itr->accountName.empty() ? "" : itr->accountName.c_str(), itr->accountId, dateStr.c_str()); else PSendSysMessage(LANG_CHARACTER_DELETED_LIST_LINE_CHAT, itr->lowguid, itr->name.c_str(), itr->accountName.empty() ? "" : itr->accountName.c_str(), itr->accountId, dateStr.c_str()); } if (!m_session) { SendSysMessage(LANG_CHARACTER_DELETED_LIST_BAR); } } /** * Handles the '.character deleted list' command, which shows all deleted characters which matches the given search string * * @see ChatHandler::HandleCharacterDeletedListHelper * @see ChatHandler::HandleCharacterDeletedRestoreCommand * @see ChatHandler::HandleCharacterDeletedDeleteCommand * @see ChatHandler::DeletedInfoList * * @param args the search string which either contains a player GUID or a part of the character-name */ bool ChatHandler::HandleCharacterDeletedListCommand(char* args) { DeletedInfoList foundList; if (!GetDeletedCharacterInfoList(foundList, args)) { return false; } // if no characters have been found, output a warning if (foundList.empty()) { SendSysMessage(LANG_CHARACTER_DELETED_LIST_EMPTY); return false; } HandleCharacterDeletedListHelper(foundList); return true; } /** * Restore a previously deleted character * * @see ChatHandler::HandleCharacterDeletedListHelper * @see ChatHandler::HandleCharacterDeletedRestoreCommand * @see ChatHandler::HandleCharacterDeletedDeleteCommand * @see ChatHandler::DeletedInfoList * * @param delInfo the informations about the character which will be restored */ void ChatHandler::HandleCharacterDeletedRestoreHelper(DeletedInfo const& delInfo) { if (delInfo.accountName.empty()) // account not exist { PSendSysMessage(LANG_CHARACTER_DELETED_SKIP_ACCOUNT, delInfo.name.c_str(), delInfo.lowguid, delInfo.accountId); return; } // check character count uint32 charcount = sAccountMgr.GetCharactersCount(delInfo.accountId); if (charcount >= 10) { PSendSysMessage(LANG_CHARACTER_DELETED_SKIP_FULL, delInfo.name.c_str(), delInfo.lowguid, delInfo.accountId); return; } if (sObjectMgr.GetPlayerGuidByName(delInfo.name)) { PSendSysMessage(LANG_CHARACTER_DELETED_SKIP_NAME, delInfo.name.c_str(), delInfo.lowguid, delInfo.accountId); return; } CharacterDatabase.PExecute("UPDATE characters SET name='%s', account='%u', deleteDate=NULL, deleteInfos_Name=NULL, deleteInfos_Account=NULL WHERE deleteDate IS NOT NULL AND guid = %u", delInfo.name.c_str(), delInfo.accountId, delInfo.lowguid); } /** * Handles the '.character deleted restore' command, which restores all deleted characters which matches the given search string * * The command automatically calls '.character deleted list' command with the search string to show all restored characters. * * @see ChatHandler::HandleCharacterDeletedRestoreHelper * @see ChatHandler::HandleCharacterDeletedListCommand * @see ChatHandler::HandleCharacterDeletedDeleteCommand * * @param args the search string which either contains a player GUID or a part of the character-name */ bool ChatHandler::HandleCharacterDeletedRestoreCommand(char* args) { // It is required to submit at least one argument if (!*args) { return false; } std::string searchString; std::string newCharName; uint32 newAccount = 0; // GCC by some strange reason fail build code without temporary variable std::istringstream params(args); params >> searchString >> newCharName >> newAccount; DeletedInfoList foundList; if (!GetDeletedCharacterInfoList(foundList, searchString)) { return false; } if (foundList.empty()) { SendSysMessage(LANG_CHARACTER_DELETED_LIST_EMPTY); return false; } SendSysMessage(LANG_CHARACTER_DELETED_RESTORE); HandleCharacterDeletedListHelper(foundList); if (newCharName.empty()) { // Drop nonexistent account cases for (DeletedInfoList::iterator itr = foundList.begin(); itr != foundList.end(); ++itr) { HandleCharacterDeletedRestoreHelper(*itr); } } else if (foundList.size() == 1 && normalizePlayerName(newCharName)) { DeletedInfo delInfo = foundList.front(); // update name delInfo.name = newCharName; // if new account provided update deleted info if (newAccount && newAccount != delInfo.accountId) { delInfo.accountId = newAccount; sAccountMgr.GetName(newAccount, delInfo.accountName); } HandleCharacterDeletedRestoreHelper(delInfo); } else { SendSysMessage(LANG_CHARACTER_DELETED_ERR_RENAME); } return true; } /** * Handles the '.character deleted delete' command, which completely deletes all deleted characters which matches the given search string * * @see Player::GetDeletedCharacterGUIDs * @see Player::DeleteFromDB * @see ChatHandler::HandleCharacterDeletedListCommand * @see ChatHandler::HandleCharacterDeletedRestoreCommand * * @param args the search string which either contains a player GUID or a part of the character-name */ bool ChatHandler::HandleCharacterDeletedDeleteCommand(char* args) { // It is required to submit at least one argument if (!*args) { return false; } DeletedInfoList foundList; if (!GetDeletedCharacterInfoList(foundList, args)) { return false; } if (foundList.empty()) { SendSysMessage(LANG_CHARACTER_DELETED_LIST_EMPTY); return false; } SendSysMessage(LANG_CHARACTER_DELETED_DELETE); HandleCharacterDeletedListHelper(foundList); // Call the appropriate function to delete them (current account for deleted characters is 0) for (DeletedInfoList::const_iterator itr = foundList.begin(); itr != foundList.end(); ++itr) { Player::DeleteFromDB(ObjectGuid(HIGHGUID_PLAYER, itr->lowguid), 0, false, true); } return true; } /** * Handles the '.character deleted old' command, which completely deletes all deleted characters deleted with some days ago * * @see Player::DeleteOldCharacters * @see Player::DeleteFromDB * @see ChatHandler::HandleCharacterDeletedDeleteCommand * @see ChatHandler::HandleCharacterDeletedListCommand * @see ChatHandler::HandleCharacterDeletedRestoreCommand * * @param args the search string which either contains a player GUID or a part of the character-name */ bool ChatHandler::HandleCharacterDeletedOldCommand(char* args) { int32 keepDays = sWorld.getConfig(CONFIG_UINT32_CHARDELETE_KEEP_DAYS); if (!ExtractOptInt32(&args, keepDays, sWorld.getConfig(CONFIG_UINT32_CHARDELETE_KEEP_DAYS))) { return false; } if (keepDays < 0) { return false; } Player::DeleteOldCharacters((uint32)keepDays); return true; } bool ChatHandler::HandleCharacterEraseCommand(char* args) { char* nameStr = ExtractLiteralArg(&args); if (!nameStr) { return false; } Player* target; ObjectGuid target_guid; std::string target_name; if (!ExtractPlayerTarget(&nameStr, &target, &target_guid, &target_name)) { return false; } uint32 account_id; if (target) { account_id = target->GetSession()->GetAccountId(); target->GetSession()->KickPlayer(); } else { account_id = sObjectMgr.GetPlayerAccountIdByGUID(target_guid); } std::string account_name; sAccountMgr.GetName(account_id, account_name); Player::DeleteFromDB(target_guid, account_id, true, true); PSendSysMessage(LANG_CHARACTER_DELETED, target_name.c_str(), target_guid.GetCounter(), account_name.c_str(), account_id); return true; } /// Close RA connection bool ChatHandler::HandleQuitCommand(char* /*args*/) { // processed in RASocket SendSysMessage(LANG_QUIT_WRONG_USE_ERROR); return true; } /// Exit the realm bool ChatHandler::HandleServerExitCommand(char* /*args*/) { SendSysMessage(LANG_COMMAND_EXIT); World::StopNow(SHUTDOWN_EXIT_CODE); return true; } /// Display info on users currently in the realm bool ChatHandler::HandleAccountOnlineListCommand(char* args) { uint32 limit; if (!ExtractOptUInt32(&args, limit, 100)) { return false; } ///- Get the list of accounts ID logged to the realm // 0 1 2 3 4 QueryResult* result = LoginDatabase.PQuery("SELECT id, username, last_ip, gmlevel, expansion FROM account WHERE active_realm_id = %u", realmID); return ShowAccountListHelper(result, &limit); } /// Create an account bool ChatHandler::HandleAccountCreateCommand(char* args) { ///- %Parse the command line arguments char* szAcc = ExtractQuotedOrLiteralArg(&args); char* szPassword = ExtractQuotedOrLiteralArg(&args); if (!szAcc || !szPassword) { return false; } // normalized in accmgr.CreateAccount std::string account_name = szAcc; std::string password = szPassword; AccountOpResult result; result = sAccountMgr.CreateAccount(account_name, password); switch (result) { case AOR_OK: PSendSysMessage(LANG_ACCOUNT_CREATED, account_name.c_str()); break; case AOR_NAME_TOO_LONG: SendSysMessage(LANG_ACCOUNT_TOO_LONG); SetSentErrorMessage(true); return false; case AOR_NAME_ALREADY_EXIST: SendSysMessage(LANG_ACCOUNT_ALREADY_EXIST); SetSentErrorMessage(true); return false; case AOR_DB_INTERNAL_ERROR: PSendSysMessage(LANG_ACCOUNT_NOT_CREATED_SQL_ERROR, account_name.c_str()); SetSentErrorMessage(true); return false; default: PSendSysMessage(LANG_ACCOUNT_NOT_CREATED, account_name.c_str()); SetSentErrorMessage(true); return false; } return true; } /// Set the filters of logging bool ChatHandler::HandleServerLogFilterCommand(char* args) { if (!*args) { SendSysMessage(LANG_LOG_FILTERS_STATE_HEADER); for (int i = 0; i < LOG_FILTER_COUNT; ++i) if (*logFilterData[i].name) { PSendSysMessage(" %-20s = %s", logFilterData[i].name, GetOnOffStr(sLog.HasLogFilter(1 << i))); } return true; } char* filtername = ExtractLiteralArg(&args); if (!filtername) { return false; } bool value; if (!ExtractOnOff(&args, value)) { SendSysMessage(LANG_USE_BOL); SetSentErrorMessage(true); return false; } if (strncmp(filtername, "all", 4) == 0) { sLog.SetLogFilter(LogFilters(0xFFFFFFFF), value); PSendSysMessage(LANG_ALL_LOG_FILTERS_SET_TO_S, GetOnOffStr(value)); return true; } for (int i = 0; i < LOG_FILTER_COUNT; ++i) { if (!*logFilterData[i].name) { continue; } if (!strncmp(filtername, logFilterData[i].name, strlen(filtername))) { sLog.SetLogFilter(LogFilters(1 << i), value); PSendSysMessage(" %-20s = %s", logFilterData[i].name, GetOnOffStr(value)); return true; } } return false; } /// Set the level of logging bool ChatHandler::HandleServerLogLevelCommand(char* args) { if (!*args) { PSendSysMessage("Log level: %u", sLog.GetLogLevel()); return true; } sLog.SetLogLevel(args); return true; } /// @} #if (PLATFORM == PLATFORM_APPLE) || (PLATFORM == PLATFORM_UNIX) // Non-blocking keypress detector, when return pressed, return 1, else always return 0 int kb_hit_return() { struct timeval tv; fd_set fds; tv.tv_sec = 0; tv.tv_usec = 0; FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv); return FD_ISSET(STDIN_FILENO, &fds); } #endif /// %Thread start void CliRunnable::run() { ///- Init new SQL thread for the world database (one connection call enough) WorldDatabase.ThreadStart(); // let thread do safe mySQL requests char commandbuf[256]; ///- Display the list of available CLI functions then beep sLog.outString(); if (sConfig.GetBoolDefault("BeepAtStart", true)) { printf("\a"); } // \a = Alert // print this here the first time // later it will be printed after command queue updates printf("mangos>"); ///- As long as the World is running (no World::m_stopEvent), get the command line and handle it while (!World::IsStopped()) { fflush(stdout); #if (PLATFORM == PLATFORM_APPLE) || (PLATFORM == PLATFORM_UNIX) while (!kb_hit_return() && !World::IsStopped()) // With this, we limit CLI to 10commands/second { usleep(100); } if (World::IsStopped()) { break; } #endif char* command_str = fgets(commandbuf, sizeof(commandbuf), stdin); if (command_str != NULL) { for (int x = 0; command_str[x]; ++x) if (command_str[x] == '\r' || command_str[x] == '\n') { command_str[x] = 0; break; } if (!*command_str) { printf("mangos>"); continue; } std::string command; if (!consoleToUtf8(command_str, command)) // convert from console encoding to utf8 { printf("mangos>"); continue; } sWorld.QueueCliCommand(new CliCommandHolder(0, SEC_CONSOLE, NULL, command.c_str(), &utf8print, &commandFinished)); } else if (feof(stdin)) { World::StopNow(SHUTDOWN_EXIT_CODE); } } ///- End the database thread WorldDatabase.ThreadEnd(); // free mySQL thread resources }