diff --git a/akashi.pro b/akashi.pro index 611f8f8..ceaa2f0 100644 --- a/akashi.pro +++ b/akashi.pro @@ -26,6 +26,9 @@ RC_ICONS = resource/icon/akashi.ico # Enable this to print network messages tothe console #DEFINES += NET_DEBUG +# Enable this to skip all authentication checks +#DEFINES += SKIP_AUTH + SOURCES += src/advertiser.cpp \ src/aoclient.cpp \ src/aopacket.cpp \ diff --git a/bin/config_sample/config.ini b/bin/config_sample/config.ini index 844d00c..4f80324 100644 --- a/bin/config_sample/config.ini +++ b/bin/config_sample/config.ini @@ -19,6 +19,7 @@ logging=modcall maximum_statements=10 multiclient_limit=15 maximum_characters=256 +message_floodguard=250 [Dice] max_value=100 diff --git a/include/aoclient.h b/include/aoclient.h index 6f69605..b6e8014 100644 --- a/include/aoclient.h +++ b/include/aoclient.h @@ -229,6 +229,7 @@ class AOClient : public QObject { {"UNCM", 1ULL << 11}, {"SAVETEST", 1ULL << 12}, {"FORCE_CHARSELECT",1ULL << 13}, + {"BYPASS_LOCKS", 1ULL << 14}, {"SUPER", ~0ULL } }; @@ -289,6 +290,11 @@ class AOClient : public QObject { */ bool testimony_saving = false; + /** + * @brief If true, the client's next OOC message will be interpreted as a moderator login. + */ + bool is_logging_in = false; + public slots: /** * @brief A slot for when the client disconnects from the server. @@ -688,11 +694,9 @@ class AOClient : public QObject { ///@{ /** - * @brief Logs the user in as a moderator. + * @brief Sets the client to be in the process of logging in, setting is_logging_in to **true**. * - * @details If the authorisation type is `"simple"`, then this command expects one argument, the **global moderator password**. - * - * If the authorisation type is `"advanced"`, then it requires two arguments, the **moderator's username** and the **matching password**. + * @details No arguments. * * @iscommand */ @@ -1899,7 +1903,7 @@ class AOClient : public QObject { * See @ref CommandInfo "the type's documentation" for more details. */ const QMap commands { - {"login", {ACLFlags.value("NONE"), 1, &AOClient::cmdLogin}}, + {"login", {ACLFlags.value("NONE"), 0, &AOClient::cmdLogin}}, {"getareas", {ACLFlags.value("NONE"), 0, &AOClient::cmdGetAreas}}, {"getarea", {ACLFlags.value("NONE"), 0, &AOClient::cmdGetArea}}, {"ban", {ACLFlags.value("BAN"), 2, &AOClient::cmdBan}}, @@ -2014,8 +2018,8 @@ class AOClient : public QObject { {"togglemusic", {ACLFlags.value("CM"), 0, &AOClient::cmdToggleMusic}}, {"a", {ACLFlags.value("NONE"), 2, &AOClient::cmdA}}, {"s", {ACLFlags.value("NONE"), 0, &AOClient::cmdS}}, - {"kickuid", {ACLFlags.value("NONE"), 2, &AOClient::cmdKickUid}}, - {"kick_uid", {ACLFlags.value("NONE"), 2, &AOClient::cmdKickUid}}, + {"kickuid", {ACLFlags.value("KICK"), 2, &AOClient::cmdKickUid}}, + {"kick_uid", {ACLFlags.value("KICK"), 2, &AOClient::cmdKickUid}}, {"firstperson", {ACLFlags.value("NONE"), 0, &AOClient::cmdFirstPerson}}, }; @@ -2087,6 +2091,13 @@ class AOClient : public QObject { * @brief The size, in bytes, of the last data the client sent to the server. */ int last_read; + + /** + * @brief A helper function for logging in a client as moderator. + * + * @param message The OOC message the client has sent. + */ + void loginAttempt(QString message); }; #endif // AOCLIENT_H diff --git a/include/db_manager.h b/include/db_manager.h index d5e14ae..2aa44a9 100644 --- a/include/db_manager.h +++ b/include/db_manager.h @@ -128,6 +128,7 @@ public: unsigned long time; //!< The time the ban was registered. QString reason; //!< The reason given for the ban by the moderator who registered it. long long duration; //!< The duration of the ban, in seconds. + int id; //!< The unique ID of the ban. }; /** diff --git a/include/server.h b/include/server.h index 8fe4aa8..3e610d2 100644 --- a/include/server.h +++ b/include/server.h @@ -303,6 +303,21 @@ class Server : public QObject { */ int max_chars; + /** + * @brief Timer until the next IC message can be sent. + */ + QTimer next_message_timer; + + /** + * @brief If false, IC messages will be rejected. + */ + bool can_send_ic_messages = true; + + /** + * @brief The minimum time between IC messages, in milliseconds. + */ + int message_floodguard; + public slots: /** * @brief Handles a new connection. @@ -312,6 +327,13 @@ class Server : public QObject { */ void clientConnected(); + /** + * @brief Sets #can_send_messages to true. + * + * @details Called whenever #next_message_timer reaches 0. + */ + void allowMessage(); + signals: /** diff --git a/src/aoclient.cpp b/src/aoclient.cpp index 1fc9c92..3784b4a 100644 --- a/src/aoclient.cpp +++ b/src/aoclient.cpp @@ -109,7 +109,7 @@ void AOClient::changeArea(int new_area) sendServerMessage("You are already in area " + server->area_names[current_area]); return; } - if (server->areas[new_area]->locked == AreaData::LockStatus::LOCKED && !server->areas[new_area]->invited.contains(id)) { + if (server->areas[new_area]->locked == AreaData::LockStatus::LOCKED && !server->areas[new_area]->invited.contains(id) && !checkAuth(ACLFlags.value("BYPASS_LOCKS"))) { sendServerMessage("Area " + server->area_names[new_area] + " is locked."); return; } @@ -311,6 +311,9 @@ void AOClient::sendServerBroadcast(QString message) bool AOClient::checkAuth(unsigned long long acl_mask) { +#ifdef SKIP_AUTH + return true; +#endif if (acl_mask != ACLFlags.value("NONE")) { if (acl_mask == ACLFlags.value("CM")) { AreaData* area = server->areas[current_area]; diff --git a/src/commands/authentication.cpp b/src/commands/authentication.cpp index 1aeb24d..436608a 100644 --- a/src/commands/authentication.cpp +++ b/src/commands/authentication.cpp @@ -26,46 +26,21 @@ void AOClient::cmdLogin(int argc, QStringList argv) sendServerMessage("You are already logged in!"); return; } - if (server->auth_type == "simple") { if (server->modpass == "") { - sendServerMessage("No modpass is set! Please set a modpass before authenticating."); - } - else if(argv[0] == server->modpass) { - sendPacket("AUTH", {"1"}); // Client: "You were granted the Disable Modcalls button." - sendServerMessage("Logged in as a moderator."); // pre-2.9.1 clients are hardcoded to display the mod UI when this string is sent in OOC - authenticated = true; - } - else { - sendPacket("AUTH", {"0"}); // Client: "Login unsuccessful." - sendServerMessage("Incorrect password."); - } - server->areas.value(current_area)->logger->logLogin(this, authenticated, "moderator"); - } - else if (server->auth_type == "advanced") { - if (argc < 2) { - sendServerMessage("You must specify a username and a password"); + sendServerMessage("No modpass is set. Please set a modpass before logging in."); return; } - QString username = argv[0]; - QString password = argv[1]; - if (server->db_manager->authenticate(username, password)) { - moderator_name = username; - authenticated = true; - sendPacket("AUTH", {"1"}); // Client: "You were granted the Disable Modcalls button." - if (version.release <= 2 && version.major <= 9 && version.minor <= 0) - sendServerMessage("Logged in as a moderator."); // pre-2.9.1 clients are hardcoded to display the mod UI when this string is sent in OOC - sendServerMessage("Welcome, " + username); - } else { - sendPacket("AUTH", {"0"}); // Client: "Login unsuccessful." - sendServerMessage("Incorrect password."); + sendServerMessage("Entering login prompt.\nPlease enter the server modpass."); + is_logging_in = true; + return; } - server->areas.value(current_area)->logger->logLogin(this, authenticated, username); } - else { - qWarning() << "config.ini has an unrecognized auth_type!"; - sendServerMessage("Config.ini contains an invalid auth_type, please check your config."); + else if (server->auth_type == "advanced") { + sendServerMessage("Entering login prompt.\nPlease enter your username and password."); + is_logging_in = true; + return; } } diff --git a/src/commands/moderation.cpp b/src/commands/moderation.cpp index 0ba42b7..52c0830 100644 --- a/src/commands/moderation.cpp +++ b/src/commands/moderation.cpp @@ -178,12 +178,12 @@ void AOClient::cmdBans(int argc, QStringList argv) if (ban.duration == -2) banned_until = "The heat death of the universe"; else - banned_until = QDateTime::fromSecsSinceEpoch(ban.time).addSecs(ban.duration).toString("dd.MM.yyyy, hh:mm"); - recent_bans << "Ban ID: " + QString::number(server->db_manager->getBanID(ban.ipid)); + banned_until = QDateTime::fromSecsSinceEpoch(ban.time).addSecs(ban.duration).toString("MM/dd/yyyy, hh:mm"); + recent_bans << "Ban ID: " + QString::number(ban.id); recent_bans << "Affected IPID: " + ban.ipid; recent_bans << "Affected HDID: " + ban.hdid; recent_bans << "Reason for ban: " + ban.reason; - recent_bans << "Date of ban: " + QDateTime::fromSecsSinceEpoch(ban.time).toString("dd.MM.yyyy, hh:mm"); + recent_bans << "Date of ban: " + QDateTime::fromSecsSinceEpoch(ban.time).toString("MM/dd/yyyy, hh:mm"); recent_bans << "Ban lasts until: " + banned_until; recent_bans << "-----"; } diff --git a/src/db_manager.cpp b/src/db_manager.cpp index b8bf836..6814117 100644 --- a/src/db_manager.cpp +++ b/src/db_manager.cpp @@ -131,7 +131,7 @@ long long DBManager::getBanDuration(QHostAddress ip) int DBManager::getBanID(QString hdid) { QSqlQuery query; - query.prepare("SELECT ID FROM BANS WHERE HDID = ?"); + query.prepare("SELECT ID FROM BANS WHERE HDID = ? ORDER BY TIME DESC"); query.addBindValue(hdid); query.exec(); if (query.first()) { @@ -146,7 +146,7 @@ int DBManager::getBanID(QString hdid) int DBManager::getBanID(QHostAddress ip) { QSqlQuery query; - query.prepare("SELECT ID FROM BANS WHERE IP = ?"); + query.prepare("SELECT ID FROM BANS WHERE IP = ? ORDER BY TIME DESC"); query.addBindValue(ip.toString()); query.exec(); if (query.first()) { @@ -161,17 +161,18 @@ QList DBManager::getRecentBans() { QList return_list; QSqlQuery query; - query.prepare("SELECT TOP(5) * FROM BANS ORDER BY TIME DESC"); + query.prepare("SELECT * FROM BANS ORDER BY TIME DESC LIMIT 5"); query.setForwardOnly(true); query.exec(); while (query.next()) { BanInfo ban; - ban.ipid = query.value(0).toString(); - ban.hdid = query.value(1).toString(); - ban.ip = QHostAddress(query.value(2).toString()); - ban.time = static_cast(query.value(3).toULongLong()); - ban.reason = query.value(4).toString(); - ban.duration = query.value(5).toLongLong(); + ban.id = query.value(0).toInt(); + ban.ipid = query.value(1).toString(); + ban.hdid = query.value(2).toString(); + ban.ip = QHostAddress(query.value(3).toString()); + ban.time = static_cast(query.value(4).toULongLong()); + ban.reason = query.value(5).toString(); + ban.duration = query.value(6).toLongLong(); return_list.append(ban); } std::reverse(return_list.begin(), return_list.end()); diff --git a/src/packets.cpp b/src/packets.cpp index 1137180..14dfd73 100644 --- a/src/packets.cpp +++ b/src/packets.cpp @@ -157,6 +157,10 @@ void AOClient::pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket pa return; } + if (!server->can_send_ic_messages) { + return; + } + AOPacket validated_packet = validateIcPacket(packet); if (validated_packet.header == "INVALID") return; @@ -168,6 +172,9 @@ void AOClient::pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket pa server->broadcast(validated_packet, current_area); area->last_ic_message.clear(); area->last_ic_message.append(validated_packet.contents); + + server->can_send_ic_messages = false; + server->next_message_timer.start(server->message_floodguard); } void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket packet) @@ -186,6 +193,11 @@ void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket p return; } + if (is_logging_in) { + loginAttempt(argv[1]); + return; + } + QString message = dezalgo(argv[1]); if (message.length() == 0 || message.length() > server->max_chars) return; @@ -474,7 +486,7 @@ AOPacket AOClient::validateIcPacket(AOPacket packet) // Spectators cannot use IC return invalid; AreaData* area = server->areas[current_area]; - if (area->locked == AreaData::LockStatus::SPECTATABLE && !area->invited.contains(id)) + if (area->locked == AreaData::LockStatus::SPECTATABLE && !area->invited.contains(id) && !checkAuth(ACLFlags.value("BYPASS_LOCKS"))) // Non-invited players cannot speak in spectatable areas return invalid; @@ -832,3 +844,51 @@ QString AOClient::decodeMessage(QString incoming_message) .replace("", "&"); return decoded_message; } + +void AOClient::loginAttempt(QString message) +{ + if (server->auth_type == "simple") { + if (message == server->modpass) { + sendPacket("AUTH", {"1"}); // Client: "You were granted the Disable Modcalls button." + sendServerMessage("Logged in as a moderator."); // pre-2.9.1 clients are hardcoded to display the mod UI when this string is sent in OOC + authenticated = true; + } + else { + sendPacket("AUTH", {"0"}); // Client: "Login unsuccessful." + sendServerMessage("Incorrect password."); + } + server->areas.value(current_area)->logger->logLogin(this, authenticated, "moderator"); + } + else if (server->auth_type == "advanced") { + QStringList login = message.split(" "); + if (login.size() < 2) { + sendServerMessage("You must specify a username and a password"); + sendServerMessage("Exiting login prompt."); + is_logging_in = false; + return; + } + QString username = login[0]; + QString password = login[1]; + if (server->db_manager->authenticate(username, password)) { + moderator_name = username; + authenticated = true; + sendPacket("AUTH", {"1"}); // Client: "You were granted the Disable Modcalls button." + if (version.release <= 2 && version.major <= 9 && version.minor <= 0) + sendServerMessage("Logged in as a moderator."); // pre-2.9.1 clients are hardcoded to display the mod UI when this string is sent in OOC + sendServerMessage("Welcome, " + username); + } + else { + sendPacket("AUTH", {"0"}); // Client: "Login unsuccessful." + sendServerMessage("Incorrect password."); + } + server->areas.value(current_area)->logger->logLogin(this, authenticated, username); + } + else { + qWarning() << "config.ini has an unrecognized auth_type!"; + sendServerMessage("Config.ini contains an invalid auth_type, please check your config."); + } + sendServerMessage("Exiting login prompt."); + is_logging_in = false; + return; +} + diff --git a/src/server.cpp b/src/server.cpp index bf58757..21bf840 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -104,6 +104,7 @@ void Server::start() QString area_name = raw_area_names[i]; areas.insert(i, new AreaData(area_name, i)); } + connect(&next_message_timer, SIGNAL(timeout()), this, SLOT(allowMessage())); } void Server::clientConnected() @@ -290,6 +291,10 @@ void Server::loadServerConfig() max_chars = config.value("maximum_characters", "256").toInt(&max_char_conversion_success); if (!max_char_conversion_success) max_chars = 256; + bool message_floodguard_conversion_success; + message_floodguard = config.value("message_floodguard", "250").toInt(&message_floodguard_conversion_success); + if (!message_floodguard_conversion_success) + message_floodguard = 30; config.endGroup(); //Load dice values @@ -306,6 +311,11 @@ void Server::loadServerConfig() config.endGroup(); } +void Server::allowMessage() +{ + can_send_ic_messages = true; +} + Server::~Server() { for (AOClient* client : clients) {