Merge remote-tracking branch 'refs/remotes/scatter/master'

Conflicts:
	akashi.pro
	core/src/aoclient.cpp
	core/src/commands/authentication.cpp
	core/src/packets.cpp
	src/aoclient.cpp
	src/commands/authentication.cpp
	src/packets.cpp
This commit is contained in:
Cerapter 2021-05-09 15:11:02 +02:00
commit ba3d20186d
12 changed files with 185 additions and 52 deletions

View File

@ -19,6 +19,7 @@ logging=modcall
maximum_statements=10 maximum_statements=10
multiclient_limit=15 multiclient_limit=15
maximum_characters=256 maximum_characters=256
message_floodguard=250
[Dice] [Dice]
max_value=100 max_value=100

View File

@ -24,6 +24,7 @@
#include <QHostAddress> #include <QHostAddress>
#include <QString> #include <QString>
#include <QTcpSocket> #include <QTcpSocket>
#include <QTimer>
/** /**
* @brief A communicator class to update the master server on the server's status. * @brief A communicator class to update the master server on the server's status.

View File

@ -229,6 +229,7 @@ class AOClient : public QObject {
{"UNCM", 1ULL << 11}, {"UNCM", 1ULL << 11},
{"SAVETEST", 1ULL << 12}, {"SAVETEST", 1ULL << 12},
{"FORCE_CHARSELECT",1ULL << 13}, {"FORCE_CHARSELECT",1ULL << 13},
{"BYPASS_LOCKS", 1ULL << 14},
{"SUPER", ~0ULL } {"SUPER", ~0ULL }
}; };
@ -289,6 +290,11 @@ class AOClient : public QObject {
*/ */
bool testimony_saving = false; 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: public slots:
/** /**
* @brief A slot for when the client disconnects from the server. * @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**. * @details No arguments.
*
* If the authorisation type is `"advanced"`, then it requires two arguments, the **moderator's username** and the **matching password**.
* *
* @iscommand * @iscommand
*/ */
@ -1176,6 +1180,20 @@ class AOClient : public QObject {
*/ */
void cmdPermitSaving(int argc, QStringList argv); void cmdPermitSaving(int argc, QStringList argv);
/**
* @brief Kicks a client from the server, forcibly severing its connection to the server.
*
* @details The first argument is the **target's UID**, while the remaining arguments are the **reason**
* the client was kicked. Both arguments are mandatory.
*
* Unlike cmdKick, this command will only kick a single client, thus a multiclienting user will not have all their clients kicked.
*
* @iscommand
*
* @see #cmdKick
*/
void cmdKickUid(int argc, QStringList argv);
///@} ///@}
/** /**
@ -1880,7 +1898,7 @@ class AOClient : public QObject {
* See @ref CommandInfo "the type's documentation" for more details. * See @ref CommandInfo "the type's documentation" for more details.
*/ */
const QMap<QString, CommandInfo> commands { const QMap<QString, CommandInfo> commands {
{"login", {ACLFlags.value("NONE"), 1, &AOClient::cmdLogin}}, {"login", {ACLFlags.value("NONE"), 0, &AOClient::cmdLogin}},
{"getareas", {ACLFlags.value("NONE"), 0, &AOClient::cmdGetAreas}}, {"getareas", {ACLFlags.value("NONE"), 0, &AOClient::cmdGetAreas}},
{"getarea", {ACLFlags.value("NONE"), 0, &AOClient::cmdGetArea}}, {"getarea", {ACLFlags.value("NONE"), 0, &AOClient::cmdGetArea}},
{"ban", {ACLFlags.value("BAN"), 2, &AOClient::cmdBan}}, {"ban", {ACLFlags.value("BAN"), 2, &AOClient::cmdBan}},
@ -1995,6 +2013,8 @@ class AOClient : public QObject {
{"togglemusic", {ACLFlags.value("CM"), 0, &AOClient::cmdToggleMusic}}, {"togglemusic", {ACLFlags.value("CM"), 0, &AOClient::cmdToggleMusic}},
{"a", {ACLFlags.value("NONE"), 2, &AOClient::cmdA}}, {"a", {ACLFlags.value("NONE"), 2, &AOClient::cmdA}},
{"s", {ACLFlags.value("NONE"), 0, &AOClient::cmdS}}, {"s", {ACLFlags.value("NONE"), 0, &AOClient::cmdS}},
{"kickuid", {ACLFlags.value("KICK"), 2, &AOClient::cmdKickUid}},
{"kick_uid", {ACLFlags.value("KICK"), 2, &AOClient::cmdKickUid}},
{"firstperson", {ACLFlags.value("NONE"), 0, &AOClient::cmdFirstPerson}}, {"firstperson", {ACLFlags.value("NONE"), 0, &AOClient::cmdFirstPerson}},
}; };
@ -2066,6 +2086,13 @@ class AOClient : public QObject {
* @brief The size, in bytes, of the last data the client sent to the server. * @brief The size, in bytes, of the last data the client sent to the server.
*/ */
int last_read; 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 #endif // AOCLIENT_H

View File

@ -126,6 +126,7 @@ public:
unsigned long time; //!< The time the ban was registered. unsigned long time; //!< The time the ban was registered.
QString reason; //!< The reason given for the ban by the moderator who registered it. QString reason; //!< The reason given for the ban by the moderator who registered it.
long long duration; //!< The duration of the ban, in seconds. long long duration; //!< The duration of the ban, in seconds.
int id; //!< The unique ID of the ban.
}; };
/** /**

View File

@ -303,6 +303,21 @@ class Server : public QObject {
*/ */
int max_chars; 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: public slots:
/** /**
* @brief Handles a new connection. * @brief Handles a new connection.
@ -312,6 +327,13 @@ class Server : public QObject {
*/ */
void clientConnected(); void clientConnected();
/**
* @brief Sets #can_send_messages to true.
*
* @details Called whenever #next_message_timer reaches 0.
*/
void allowMessage();
signals: signals:
/** /**

View File

@ -62,6 +62,15 @@ void Advertiser::socketConnected()
void Advertiser::socketDisconnected() void Advertiser::socketDisconnected()
{ {
qDebug("Connection to master server lost"); qDebug("Connection to master server lost");
QTimer timer;
while (socket->state() == QAbstractSocket::UnconnectedState) {
timer.start(60000);
QEventLoop timer_loop;
connect(&timer, SIGNAL(timeout()), &timer_loop, SLOT(quit()));
timer_loop.exec();
socket->connectToHost(ip, port);
socket->waitForConnected();
}
} }
void Advertiser::reloadRequested(QString p_name, QString p_desc) void Advertiser::reloadRequested(QString p_name, QString p_desc)

View File

@ -107,7 +107,7 @@ void AOClient::changeArea(int new_area)
sendServerMessage("You are already in area " + server->area_names[current_area]); sendServerMessage("You are already in area " + server->area_names[current_area]);
return; return;
} }
if (server->areas[new_area]->lockStatus() == AreaData::LockStatus::LOCKED && !server->areas[new_area]->invited().contains(id)) { if (server->areas[new_area]->lockStatus() == AreaData::LockStatus::LOCKED && !server->areas[new_area]->invited().contains(id) && !checkAuth(ACLFlags.value("BYPASS_LOCKS"))) {
sendServerMessage("Area " + server->area_names[new_area] + " is locked."); sendServerMessage("Area " + server->area_names[new_area] + " is locked.");
return; return;
} }
@ -301,6 +301,9 @@ void AOClient::sendServerBroadcast(QString message)
bool AOClient::checkAuth(unsigned long long acl_mask) 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("NONE")) {
if (acl_mask == ACLFlags.value("CM")) { if (acl_mask == ACLFlags.value("CM")) {
AreaData* area = server->areas[current_area]; AreaData* area = server->areas[current_area];

View File

@ -26,46 +26,21 @@ void AOClient::cmdLogin(int argc, QStringList argv)
sendServerMessage("You are already logged in!"); sendServerMessage("You are already logged in!");
return; return;
} }
if (server->auth_type == "simple") { if (server->auth_type == "simple") {
if (server->modpass == "") { if (server->modpass == "") {
sendServerMessage("No modpass is set! Please set a modpass before authenticating."); sendServerMessage("No modpass is set. Please set a modpass before logging in.");
}
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)->logLogin(current_char, ipid, authenticated, "moderator");
}
else if (server->auth_type == "advanced") {
if (argc < 2) {
sendServerMessage("You must specify a username and a password");
return; 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 { else {
sendPacket("AUTH", {"0"}); // Client: "Login unsuccessful." sendServerMessage("Entering login prompt.\nPlease enter the server modpass.");
sendServerMessage("Incorrect password."); is_logging_in = true;
return;
} }
server->areas.value(current_area)->logLogin(current_char, ipid, authenticated, username);
} }
else { else if (server->auth_type == "advanced") {
qWarning() << "config.ini has an unrecognized auth_type!"; sendServerMessage("Entering login prompt.\nPlease enter your username and password.");
sendServerMessage("Config.ini contains an invalid auth_type, please check your config."); is_logging_in = true;
return;
} }
} }

View File

@ -178,12 +178,12 @@ void AOClient::cmdBans(int argc, QStringList argv)
if (ban.duration == -2) if (ban.duration == -2)
banned_until = "The heat death of the universe"; banned_until = "The heat death of the universe";
else else
banned_until = QDateTime::fromSecsSinceEpoch(ban.time).addSecs(ban.duration).toString("dd.MM.yyyy, hh:mm"); banned_until = QDateTime::fromSecsSinceEpoch(ban.time).addSecs(ban.duration).toString("MM/dd/yyyy, hh:mm");
recent_bans << "Ban ID: " + QString::number(server->db_manager->getBanID(ban.ipid)); recent_bans << "Ban ID: " + QString::number(ban.id);
recent_bans << "Affected IPID: " + ban.ipid; recent_bans << "Affected IPID: " + ban.ipid;
recent_bans << "Affected HDID: " + ban.hdid; recent_bans << "Affected HDID: " + ban.hdid;
recent_bans << "Reason for ban: " + ban.reason; 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 << "Ban lasts until: " + banned_until;
recent_bans << "-----"; recent_bans << "-----";
} }
@ -413,3 +413,26 @@ void AOClient::cmdPermitSaving(int argc, QStringList argv)
} }
client->testimony_saving = true; client->testimony_saving = true;
} }
void AOClient::cmdKickUid(int argc, QStringList argv)
{
QString reason = argv[1];
if (argc > 2) {
for (int i = 2; i < argv.length(); i++) {
reason += " " + argv[i];
}
}
bool conv_ok = false;
int uid = argv[0].toInt(&conv_ok);
if (!conv_ok) {
sendServerMessage("Invalid user ID.");
return;
}
AOClient* target = server->getClientByID(uid);
target->sendPacket("KK", {reason});
target->socket->close();
sendServerMessage("Kicked client with UID " + argv[0] + " for reason: " + reason);
}

View File

@ -126,7 +126,7 @@ long long DBManager::getBanDuration(QHostAddress ip)
int DBManager::getBanID(QString hdid) int DBManager::getBanID(QString hdid)
{ {
QSqlQuery query; 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.addBindValue(hdid);
query.exec(); query.exec();
if (query.first()) { if (query.first()) {
@ -141,7 +141,7 @@ int DBManager::getBanID(QString hdid)
int DBManager::getBanID(QHostAddress ip) int DBManager::getBanID(QHostAddress ip)
{ {
QSqlQuery query; 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.addBindValue(ip.toString());
query.exec(); query.exec();
if (query.first()) { if (query.first()) {
@ -156,17 +156,18 @@ QList<DBManager::BanInfo> DBManager::getRecentBans()
{ {
QList<BanInfo> return_list; QList<BanInfo> return_list;
QSqlQuery query; 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.setForwardOnly(true);
query.exec(); query.exec();
while (query.next()) { while (query.next()) {
BanInfo ban; BanInfo ban;
ban.ipid = query.value(0).toString(); ban.id = query.value(0).toInt();
ban.hdid = query.value(1).toString(); ban.ipid = query.value(1).toString();
ban.ip = QHostAddress(query.value(2).toString()); ban.hdid = query.value(2).toString();
ban.time = static_cast<unsigned long>(query.value(3).toULongLong()); ban.ip = QHostAddress(query.value(3).toString());
ban.reason = query.value(4).toString(); ban.time = static_cast<unsigned long>(query.value(4).toULongLong());
ban.duration = query.value(5).toLongLong(); ban.reason = query.value(5).toString();
ban.duration = query.value(6).toLongLong();
return_list.append(ban); return_list.append(ban);
} }
std::reverse(return_list.begin(), return_list.end()); std::reverse(return_list.begin(), return_list.end());

View File

@ -157,6 +157,10 @@ void AOClient::pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket pa
return; return;
} }
if (!server->can_send_ic_messages) {
return;
}
AOPacket validated_packet = validateIcPacket(packet); AOPacket validated_packet = validateIcPacket(packet);
if (validated_packet.header == "INVALID") if (validated_packet.header == "INVALID")
return; return;
@ -167,6 +171,9 @@ void AOClient::pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket pa
area->log(current_char, ipid, validated_packet); area->log(current_char, ipid, validated_packet);
server->broadcast(validated_packet, current_area); server->broadcast(validated_packet, current_area);
area->updateLastICMessage(validated_packet.contents); area->updateLastICMessage(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) void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket packet)
@ -185,6 +192,11 @@ void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket p
return; return;
} }
if (is_logging_in) {
loginAttempt(argv[1]);
return;
}
QString message = dezalgo(argv[1]); QString message = dezalgo(argv[1]);
if (message.length() == 0 || message.length() > server->max_chars) if (message.length() == 0 || message.length() > server->max_chars)
return; return;
@ -478,7 +490,7 @@ AOPacket AOClient::validateIcPacket(AOPacket packet)
// Spectators cannot use IC // Spectators cannot use IC
return invalid; return invalid;
AreaData* area = server->areas[current_area]; AreaData* area = server->areas[current_area];
if (area->lockStatus() == AreaData::LockStatus::SPECTATABLE && !area->invited().contains(id)) if (area->lockStatus() == AreaData::LockStatus::SPECTATABLE && !area->invited().contains(id) && !checkAuth(ACLFlags.value("BYPASS_LOCKS")))
// Non-invited players cannot speak in spectatable areas // Non-invited players cannot speak in spectatable areas
return invalid; return invalid;
@ -839,3 +851,51 @@ QString AOClient::decodeMessage(QString incoming_message)
.replace("<and>", "&"); .replace("<and>", "&");
return decoded_message; 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)->logLogin(current_char, ipid, 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)->logLogin(current_char, ipid, 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;
}

View File

@ -104,6 +104,7 @@ void Server::start()
QString area_name = raw_area_names[i]; QString area_name = raw_area_names[i];
areas.insert(i, new AreaData(area_name, i)); areas.insert(i, new AreaData(area_name, i));
} }
connect(&next_message_timer, SIGNAL(timeout()), this, SLOT(allowMessage()));
} }
void Server::clientConnected() void Server::clientConnected()
@ -290,6 +291,10 @@ void Server::loadServerConfig()
max_chars = config.value("maximum_characters", "256").toInt(&max_char_conversion_success); max_chars = config.value("maximum_characters", "256").toInt(&max_char_conversion_success);
if (!max_char_conversion_success) if (!max_char_conversion_success)
max_chars = 256; 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(); config.endGroup();
//Load dice values //Load dice values
@ -306,6 +311,11 @@ void Server::loadServerConfig()
config.endGroup(); config.endGroup();
} }
void Server::allowMessage()
{
can_send_ic_messages = true;
}
Server::~Server() Server::~Server()
{ {
for (AOClient* client : clients) { for (AOClient* client : clients) {