////////////////////////////////////////////////////////////////////////////////////// // akashi - a server for Attorney Online 2 // // Copyright (C) 2020 scatterflower // // // // This program is free software: you can redistribute it and/or modify // // it under the terms of the GNU Affero General Public License as // // published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. // // // // You should have received a copy of the GNU Affero General Public License // // along with this program. If not, see . // ////////////////////////////////////////////////////////////////////////////////////// #include "config_manager.h" #include #include QSettings *ConfigManager::m_settings = new QSettings("config/config.ini", QSettings::IniFormat); QSettings *ConfigManager::m_discord = new QSettings("config/discord.ini", QSettings::IniFormat); QSettings *ConfigManager::m_areas = new QSettings("config/areas.ini", QSettings::IniFormat); QSettings *ConfigManager::m_logtext = new QSettings("config/text/logtext.ini", QSettings::IniFormat); QSettings *ConfigManager::m_ambience = new QSettings("config/ambience.ini", QSettings::IniFormat); ConfigManager::CommandSettings *ConfigManager::m_commands = new CommandSettings(); QElapsedTimer *ConfigManager::m_uptimeTimer = new QElapsedTimer; MusicList *ConfigManager::m_musicList = new MusicList; QHash *ConfigManager::m_commands_help = new QHash; QStringList *ConfigManager::m_ordered_list = new QStringList; bool ConfigManager::verifyServerConfig() { // Verify directories QStringList l_directories{"config/", "config/text/"}; for (const QString &l_directory : l_directories) { if (!dirExists(QFileInfo(l_directory))) { qCritical() << l_directory + " does not exist!"; return false; } } // Verify config files QStringList l_config_files{"config/config.ini", "config/areas.ini", "config/backgrounds.txt", "config/characters.txt", "config/music.json", "config/discord.ini", "config/text/8ball.txt", "config/text/gimp.txt", "config/text/praise.txt", "config/text/reprimands.txt", "config/text/commandhelp.json", "config/text/cdns.txt", "config/ipbans.json"}; for (const QString &l_file : l_config_files) { if (!fileExists(QFileInfo(l_file))) { qCritical() << l_file + " does not exist!"; return false; } } // Verify areas QSettings l_areas_ini("config/areas.ini", QSettings::IniFormat); if (l_areas_ini.childGroups().length() < 1) { qCritical() << "areas.ini is invalid!"; return false; } // Verify config settings m_settings->beginGroup("Options"); bool ok; m_settings->value("ms_port", 27016).toInt(&ok); if (!ok) { qCritical("ms_port is not a valid port!"); return false; } m_settings->value("port", 27016).toInt(&ok); if (!ok) { qCritical("port is not a valid port!"); return false; } bool web_ao = m_settings->value("webao_enable", false).toBool(); if (!web_ao) { m_settings->setValue("webao_port", -1); } else { m_settings->value("webao_port", 27017).toInt(&ok); if (!ok) { qCritical("webao_port is not a valid port!"); return false; } } QString l_auth = m_settings->value("auth", "simple").toString().toLower(); if (!(l_auth == "simple" || l_auth == "advanced")) { qCritical("auth is not a valid auth type!"); return false; } m_settings->endGroup(); m_commands->magic_8ball = (loadConfigFile("8ball")); m_commands->praises = (loadConfigFile("praise")); m_commands->reprimands = (loadConfigFile("reprimands")); m_commands->gimps = (loadConfigFile("gimp")); m_commands->filters = (loadConfigFile("filter")); m_commands->cdns = (loadConfigFile("cdns")); if (m_commands->cdns.isEmpty()) m_commands->cdns = QStringList{"cdn.discord.com"}; m_uptimeTimer->start(); return true; } QString ConfigManager::bindIP() { return m_settings->value("Options/bind_ip", "all").toString(); } QStringList ConfigManager::charlist() { QStringList l_charlist; QFile l_file("config/characters.txt"); l_file.open(QIODevice::ReadOnly | QIODevice::Text); while (!l_file.atEnd()) { l_charlist.append(l_file.readLine().trimmed()); } l_file.close(); return l_charlist; } QStringList ConfigManager::backgrounds() { QStringList l_backgrounds; QFile l_file("config/backgrounds.txt"); l_file.open(QIODevice::ReadOnly | QIODevice::Text); while (!l_file.atEnd()) { l_backgrounds.append(l_file.readLine().trimmed()); } l_file.close(); return l_backgrounds; } MusicList ConfigManager::musiclist() { QFile l_music_json("config/music.json"); l_music_json.open(QIODevice::ReadOnly | QIODevice::Text); QJsonParseError l_error; QJsonDocument l_music_list_json = QJsonDocument::fromJson(l_music_json.readAll(), &l_error); if (!(l_error.error == QJsonParseError::NoError)) { // Non-Terminating error. qWarning() << "Unable to load musiclist. The following error was encounted : " + l_error.errorString(); return QMap>{}; // Server can still run without music. } // Make sure the list is empty before appending new data. if (!m_ordered_list->empty()) { m_ordered_list->clear(); } // Akashi expects the musiclist to be contained in a JSON array, even if its only a single category. QJsonArray l_Json_root_array = l_music_list_json.array(); QJsonObject l_child_obj; QJsonArray l_child_array; for (int i = 0; i < l_Json_root_array.size(); i++) { // Iterate trough entire JSON file to assemble musiclist l_child_obj = l_Json_root_array.at(i).toObject(); // Technically not a requirement, but neat for organisation. QString l_category_name = l_child_obj["category"].toString(); if (!l_category_name.isEmpty()) { m_musicList->insert(l_category_name, {l_category_name, 0}); m_ordered_list->append(l_category_name); } else { qWarning() << "Category name not set. This may cause the musiclist to be displayed incorrectly."; } l_child_array = l_child_obj["songs"].toArray(); for (int i = 0; i < l_child_array.size(); i++) { // Inner for loop because a category can contain multiple songs. QJsonObject l_song_obj = l_child_array.at(i).toObject(); QString l_song_name = l_song_obj["name"].toString(); QString l_real_name = l_song_obj["realname"].toString(); if (l_real_name.isEmpty()) { l_real_name = l_song_name; } int l_song_duration = l_song_obj["length"].toVariant().toInt(); m_musicList->insert(l_song_name, {l_real_name, l_song_duration}); m_ordered_list->append(l_song_name); } } l_music_json.close(); return *m_musicList; } QStringList ConfigManager::ordered_songs() { return *m_ordered_list; } void ConfigManager::loadCommandHelp() { QFile l_help_json("config/text/commandhelp.json"); l_help_json.open(QIODevice::ReadOnly | QIODevice::Text); QJsonParseError l_error; QJsonDocument l_help_list_json = QJsonDocument::fromJson(l_help_json.readAll(), &l_error); if (!(l_error.error == QJsonParseError::NoError)) { // Non-Terminating error. qWarning() << "Unable to load help information. The following error occurred: " + l_error.errorString(); } // Akashi expects the helpfile to contain multiple entires, so it always checks for an array first. QJsonArray l_Json_root_array = l_help_list_json.array(); QJsonObject l_child_obj; QJsonArray l_names; for (int i = 0; i < l_Json_root_array.size(); i++) { l_child_obj = l_Json_root_array.at(i).toObject(); l_names = l_child_obj["names"].toArray(); QString l_usage = l_child_obj["usage"].toString(); QString l_text = l_child_obj["text"].toString(); for (int j = 0; j < l_names.size(); j++) { QString l_name = l_names.at(j).toString(); if (!l_name.isEmpty()) { help l_help_information = { .usage = l_usage, .text = l_text}; m_commands_help->insert(l_name, l_help_information); } } } } QSettings *ConfigManager::areaData() { return m_areas; } QSettings *ConfigManager::ambience() { return m_ambience; } QStringList ConfigManager::sanitizedAreaNames() { QStringList l_area_names = m_areas->childGroups(); // invisibly does a lexicographical sort, because Qt is great like that std::sort(l_area_names.begin(), l_area_names.end(), [](const QString &a, const QString &b) { return a.split(":")[0].toInt() < b.split(":")[0].toInt(); }); QStringList l_sanitized_area_names; for (const QString &areaName : qAsConst(l_area_names)) { QStringList l_nameSplit = areaName.split(":"); l_nameSplit.removeFirst(); QString l_area_name_sanitized = l_nameSplit.join(":"); l_sanitized_area_names.append(l_area_name_sanitized); } return l_sanitized_area_names; } QStringList ConfigManager::rawAreaNames() { return m_areas->childGroups(); } QStringList ConfigManager::iprangeBans() { QFile l_json_file("config/ipbans.json"); l_json_file.open(QIODevice::ReadOnly | QIODevice::Text); QJsonParseError l_error; QJsonDocument l_ip_bans = QJsonDocument::fromJson(l_json_file.readAll(), &l_error); if (l_error.error != QJsonParseError::NoError) { qDebug() << "Unable to parse JSON file. Error:" << l_error.errorString(); return {}; } QJsonObject l_json_obj = l_ip_bans.object(); QStringList l_range_bans; l_range_bans.append(l_json_obj["ip_range"].toVariant().toStringList()); if (QFile::exists("storage/asn.sqlite3")) { QSqlDatabase asn_db = QSqlDatabase::addDatabase("QSQLITE", "ASN"); asn_db.setDatabaseName("storage/asn.sqlite3"); asn_db.open(); // This is a dumb hack. Idk how else I can do this, but who gives a shit? QSqlQuery query("SELECT ip FROM maxmind WHERE asn in (" + l_json_obj["asn"].toVariant().toStringList().join(",") + ")", asn_db); query.exec(); while (query.next()) { l_range_bans.append(query.value(0).toString()); } asn_db.close(); } l_range_bans.removeDuplicates(); return l_range_bans; } void ConfigManager::reloadSettings() { m_settings->sync(); m_discord->sync(); m_logtext->sync(); } QStringList ConfigManager::loadConfigFile(const QString filename) { QStringList stringlist; QFile l_file("config/text/" + filename + ".txt"); l_file.open(QIODevice::ReadOnly | QIODevice::Text); while (!(l_file.atEnd())) { stringlist.append(l_file.readLine().trimmed()); } l_file.close(); return stringlist; } int ConfigManager::maxPlayers() { bool ok; int l_players = m_settings->value("Options/max_players", 100).toInt(&ok); if (!ok) { qWarning("max_players is not an int!"); l_players = 100; } return l_players; } int ConfigManager::serverPort() { if (m_settings->contains("Options/webao_port")) { qWarning("webao_port is deprecated, use port instead"); return m_settings->value("Options/webao_port", 27016).toInt(); } return m_settings->value("Options/port", 27016).toInt(); } QString ConfigManager::serverDescription() { return m_settings->value("Options/server_description", "This is my flashy new server!").toString(); } QString ConfigManager::serverName() { return m_settings->value("Options/server_name", "An Unnamed Server").toString(); } QString ConfigManager::motd() { return m_settings->value("Options/motd", "MOTD not set").toString(); } bool ConfigManager::webaoEnabled() { return m_settings->value("Options/webao_enable", false).toBool(); } DataTypes::AuthType ConfigManager::authType() { QString l_auth = m_settings->value("Options/auth", "simple").toString().toUpper(); return toDataType(l_auth); } QString ConfigManager::modpass() { return m_settings->value("Options/modpass", "changeme").toString(); } int ConfigManager::logBuffer() { bool ok; int l_buffer = m_settings->value("Options/logbuffer", 500).toInt(&ok); if (!ok) { qWarning("logbuffer is not an int!"); l_buffer = 500; } return l_buffer; } DataTypes::LogType ConfigManager::loggingType() { QString l_log = m_settings->value("Options/logging", "modcall").toString().toUpper(); return toDataType(l_log); } int ConfigManager::maxStatements() { bool ok; int l_max = m_settings->value("Options/maximum_statements", 10).toInt(&ok); if (!ok) { qWarning("maximum_statements is not an int!"); l_max = 10; } return l_max; } int ConfigManager::multiClientLimit() { bool ok; int l_limit = m_settings->value("Options/multiclient_limit", 15).toInt(&ok); if (!ok) { qWarning("multiclient_limit is not an int!"); l_limit = 15; } return l_limit; } int ConfigManager::maxCharacters() { bool ok; int l_max = m_settings->value("Options/maximum_characters", 256).toInt(&ok); if (!ok) { qWarning("maximum_characters is not an int!"); l_max = 256; } return l_max; } int ConfigManager::messageFloodguard() { bool ok; int l_flood = m_settings->value("Options/message_floodguard", 250).toInt(&ok); if (!ok) { qWarning("message_floodguard is not an int!"); l_flood = 250; } return l_flood; } int ConfigManager::globalMessageFloodguard() { bool ok; int l_flood = m_settings->value("Options/global_message_floodguard", 0).toInt(&ok); if (!ok) { qWarning("global_message_floodguard is not an int!"); l_flood = 0; } return l_flood; } QUrl ConfigManager::assetUrl() { QByteArray l_url = m_settings->value("Options/asset_url", "").toString().toUtf8(); if (QUrl(l_url).isValid()) { return QUrl(l_url); } else { qWarning("asset_url is not a valid url!"); return QUrl(NULL); } } int ConfigManager::diceMaxValue() { bool ok; int l_value = m_settings->value("Dice/max_value", 100).toInt(&ok); if (!ok) { qWarning("max_value is not an int!"); l_value = 100; } return l_value; } int ConfigManager::diceMaxDice() { bool ok; int l_dice = m_settings->value("Dice/max_dice", 100).toInt(&ok); if (!ok) { qWarning("max_dice is not an int!"); l_dice = 100; } return l_dice; } bool ConfigManager::discordWebhookEnabled() { return m_discord->value("Discord/webhook_enabled", false).toBool(); } bool ConfigManager::discordModcallWebhookEnabled() { return m_discord->value("Discord/webhook_modcall_enabled", false).toBool(); } QString ConfigManager::discordModcallWebhookUrl() { return m_discord->value("Discord/webhook_modcall_url", "").toString(); } QString ConfigManager::discordModcallWebhookContent() { return m_discord->value("Discord/webhook_modcall_content", "").toString(); } bool ConfigManager::discordModcallWebhookSendFile() { return m_discord->value("Discord/webhook_modcall_sendfile", false).toBool(); } bool ConfigManager::discordBanWebhookEnabled() { return m_discord->value("Discord/webhook_ban_enabled", false).toBool(); } QString ConfigManager::discordBanWebhookUrl() { return m_discord->value("Discord/webhook_ban_url", "").toString(); } bool ConfigManager::discordUptimeEnabled() { return m_discord->value("Discord/webhook_uptime_enabled", "false").toBool(); } int ConfigManager::discordUptimeTime() { bool ok; int l_aliveTime = m_discord->value("Discord/webhook_uptime_time", "60").toInt(&ok); if (!ok) { qWarning("alive_time is not an int"); l_aliveTime = 60; } return l_aliveTime; } QString ConfigManager::discordUptimeWebhookUrl() { return m_discord->value("Discord/webhook_uptime_url", "").toString(); } QString ConfigManager::discordWebhookColor() { const QString l_default_color = "13312842"; QString l_color = m_discord->value("Discord/webhook_color", l_default_color).toString(); if (l_color.isEmpty()) { return l_default_color; } else { return l_color; } } bool ConfigManager::passwordRequirements() { return m_settings->value("Password/password_requirements", true).toBool(); } int ConfigManager::passwordMinLength() { bool ok; int l_min = m_settings->value("Password/pass_min_length", 8).toInt(&ok); if (!ok) { qWarning("pass_min_length is not an int!"); l_min = 8; } return l_min; } int ConfigManager::passwordMaxLength() { bool ok; int l_max = m_settings->value("Password/pass_max_length", 0).toInt(&ok); if (!ok) { qWarning("pass_max_length is not an int!"); l_max = 0; } return l_max; } bool ConfigManager::passwordRequireMixCase() { return m_settings->value("Password/pass_required_mix_case", true).toBool(); } bool ConfigManager::passwordRequireNumbers() { return m_settings->value("Password/pass_required_numbers", true).toBool(); } bool ConfigManager::passwordRequireSpecialCharacters() { return m_settings->value("Password/pass_required_special", true).toBool(); } bool ConfigManager::passwordCanContainUsername() { return m_settings->value("Password/pass_can_contain_username", false).toBool(); } QString ConfigManager::LogText(QString f_logtype) { return m_logtext->value("LogConfiguration/" + f_logtype, "").toString(); } int ConfigManager::afkTimeout() { bool ok; int l_afk = m_settings->value("Options/afk_timeout", 300).toInt(&ok); if (!ok) { qWarning("afk_timeout is not an int!"); l_afk = 300; } return l_afk; } void ConfigManager::setAuthType(const DataTypes::AuthType f_auth) { m_settings->setValue("Options/auth", fromDataType(f_auth).toLower()); } QStringList ConfigManager::magic8BallAnswers() { return m_commands->magic_8ball; } QStringList ConfigManager::praiseList() { return m_commands->praises; } QStringList ConfigManager::reprimandsList() { return m_commands->reprimands; } QStringList ConfigManager::gimpList() { return m_commands->gimps; } QStringList ConfigManager::filterList() { return m_commands->filters; } QStringList ConfigManager::cdnList() { return m_commands->cdns; } bool ConfigManager::publishServerEnabled() { return m_settings->value("Advertiser/advertise", "true").toBool(); } QUrl ConfigManager::serverlistURL() { return m_settings->value("Advertiser/ms_ip", "").toUrl(); } QString ConfigManager::serverDomainName() { return m_settings->value("Advertiser/hostname", "").toString(); } bool ConfigManager::advertiseWSProxy() { return m_settings->value("Advertiser/cloudflare_enabled", "false").toBool(); } qint64 ConfigManager::uptime() { return m_uptimeTimer->elapsed(); } ConfigManager::help ConfigManager::commandHelp(QString f_command_name) { return m_commands_help->value(f_command_name); } void ConfigManager::setMotd(const QString f_motd) { m_settings->setValue("Options/motd", f_motd); } bool ConfigManager::fileExists(const QFileInfo &f_file) { return (f_file.exists() && f_file.isFile()); } bool ConfigManager::dirExists(const QFileInfo &f_dir) { return (f_dir.exists() && f_dir.isDir()); }