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
multiclient_limit=15
maximum_characters=256
message_floodguard=250
[Dice]
max_value=100

View File

@ -24,6 +24,7 @@
#include <QHostAddress>
#include <QString>
#include <QTcpSocket>
#include <QTimer>
/**
* @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},
{"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
*/
@ -1176,6 +1180,20 @@ class AOClient : public QObject {
*/
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.
*/
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}},
{"getarea", {ACLFlags.value("NONE"), 0, &AOClient::cmdGetArea}},
{"ban", {ACLFlags.value("BAN"), 2, &AOClient::cmdBan}},
@ -1995,6 +2013,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("KICK"), 2, &AOClient::cmdKickUid}},
{"kick_uid", {ACLFlags.value("KICK"), 2, &AOClient::cmdKickUid}},
{"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.
*/
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

View File

@ -126,6 +126,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.
};
/**

View File

@ -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:
/**

View File

@ -62,6 +62,15 @@ void Advertiser::socketConnected()
void Advertiser::socketDisconnected()
{
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)

View File

@ -107,7 +107,7 @@ void AOClient::changeArea(int new_area)
sendServerMessage("You are already in area " + server->area_names[current_area]);
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.");
return;
}
@ -301,6 +301,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];

View File

@ -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)->logLogin(current_char, ipid, 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)->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.");
else if (server->auth_type == "advanced") {
sendServerMessage("Entering login prompt.\nPlease enter your username and password.");
is_logging_in = true;
return;
}
}

View File

@ -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 << "-----";
}
@ -413,3 +413,26 @@ void AOClient::cmdPermitSaving(int argc, QStringList argv)
}
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)
{
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()) {
@ -141,7 +141,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()) {
@ -156,17 +156,18 @@ QList<DBManager::BanInfo> DBManager::getRecentBans()
{
QList<BanInfo> 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<unsigned long>(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<unsigned long>(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());

View File

@ -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;
@ -167,6 +171,9 @@ void AOClient::pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket pa
area->log(current_char, ipid, validated_packet);
server->broadcast(validated_packet, current_area);
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)
@ -185,6 +192,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;
@ -478,7 +490,7 @@ AOPacket AOClient::validateIcPacket(AOPacket packet)
// Spectators cannot use IC
return invalid;
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
return invalid;
@ -839,3 +851,51 @@ QString AOClient::decodeMessage(QString incoming_message)
.replace("<and>", "&");
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];
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) {