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

This commit is contained in:
Cerapter 2021-05-13 07:15:18 +02:00
commit e6b00575d8
10 changed files with 188 additions and 44 deletions

View File

@ -20,6 +20,7 @@ maximum_statements=10
multiclient_limit=15 multiclient_limit=15
maximum_characters=256 maximum_characters=256
message_floodguard=250 message_floodguard=250
asset_url=Your WebAO asset url here.
[Dice] [Dice]
max_value=100 max_value=100

View File

@ -787,6 +787,16 @@ class AOClient : public QObject {
*/ */
void cmdLogout(int argc, QStringList argv); void cmdLogout(int argc, QStringList argv);
/**
* @brief Changes a moderator's password.
*
* @details If it is called with **one argument**, that argument is the **new password** to change to.
*
* If it is called with **two arguments**, the first argument is the **new password** to change to,
* and the second argument is the **username** of the moderator to change the password of.
*/
void cmdChangePassword(int argc, QStringList argv);
///@} ///@}
/** /**
@ -1005,14 +1015,12 @@ class AOClient : public QObject {
* @brief Bans a client from the server, forcibly severing its connection to the server, * @brief Bans a client from the server, forcibly severing its connection to the server,
* and disallowing their return. * and disallowing their return.
* *
* @details The first argument is the **target's IPID**, the second is the **reason** why the client * @details The first argument is the **target's IPID**, the second is the **duration**,
* was banned, the third is the **duration**. * and the third is the **reason** why the client was banned.
* *
* Both the reason and the duration must be in quotation marks. * The duration can be `perma`, meaning a forever ban, otherwise, it must be given in the format of `YYyWWwDDdHHhMMmSSs` to
*
* The duration can be `"perma"`, meaning a forever ban, otherwise, it must be given in the format of `"YYyWWwDDdHHhMMmSSs"` to
* mean a YY years, WW weeks, DD days, HH hours, MM minutes and SS seconds long ban. Any of these may be left out, for example, * mean a YY years, WW weeks, DD days, HH hours, MM minutes and SS seconds long ban. Any of these may be left out, for example,
* `"1h30m"` for a 1.5 hour long ban. * `1h30m` for a 1.5 hour long ban.
* *
* Besides banning, this command kicks all clients having the given IPID, * Besides banning, this command kicks all clients having the given IPID,
* thus a multiclienting user will have all their clients be kicked from the server. * thus a multiclienting user will have all their clients be kicked from the server.
@ -2016,6 +2024,7 @@ class AOClient : public QObject {
{"kickuid", {ACLFlags.value("KICK"), 2, &AOClient::cmdKickUid}}, {"kickuid", {ACLFlags.value("KICK"), 2, &AOClient::cmdKickUid}},
{"kick_uid", {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}},
{"changepass", {ACLFlags.value("NONE"), 1, &AOClient::cmdChangePassword}},
}; };
/** /**

View File

@ -18,6 +18,8 @@
#ifndef BAN_MANAGER_H #ifndef BAN_MANAGER_H
#define BAN_MANAGER_H #define BAN_MANAGER_H
#define DB_VERSION 1
#include <QDebug> #include <QDebug>
#include <QDateTime> #include <QDateTime>
#include <QHostAddress> #include <QHostAddress>
@ -127,6 +129,7 @@ public:
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. int id; //!< The unique ID of the ban.
QString moderator; //!< The moderator who issued the ban.
}; };
/** /**
@ -249,6 +252,17 @@ public:
*/ */
QList<BanInfo> getBanInfo(QString lookup_type, QString id); QList<BanInfo> getBanInfo(QString lookup_type, QString id);
/**
* @brief Updates the password of the given user.
*
* @param username The username to change.
*
* @param password The new password to change to.
*
* @return True if the password change was successful.
*/
bool updatePassword(QString username, QString password);
private: private:
/** /**
* @brief The name of the database connection driver. * @brief The name of the database connection driver.
@ -269,6 +283,25 @@ private:
* @brief The backing database that stores user details. * @brief The backing database that stores user details.
*/ */
QSqlDatabase db; QSqlDatabase db;
/**
* @brief The current server DB version.
*/
int db_version;
/**
* @brief checkVersion Checks the current server DB version.
*
* @return Returns the server DB version.
*/
int checkVersion();
/**
* @brief updateDB Updates the server DB to the latest version.
*
* @param current_version The current DB version.
*/
void updateDB(int current_version);
}; };
#endif // BAN_MANAGER_H #endif // BAN_MANAGER_H

View File

@ -317,6 +317,10 @@ class Server : public QObject {
* @brief The minimum time between IC messages, in milliseconds. * @brief The minimum time between IC messages, in milliseconds.
*/ */
int message_floodguard; int message_floodguard;
/**
* @brief URL send to the client during handshake to set the remote repository URL.
*/
QUrl asset_url;
public slots: public slots:
/** /**

View File

@ -255,6 +255,10 @@ void AOClient::sendPacket(AOPacket packet)
#ifdef NET_DEBUG #ifdef NET_DEBUG
qDebug() << "Sent packet:" << packet.header << ":" << packet.contents; qDebug() << "Sent packet:" << packet.header << ":" << packet.contents;
#endif #endif
packet.contents.replaceInStrings("#", "<num>")
.replaceInStrings("%", "<percent>")
.replaceInStrings("$", "<dollar>")
.replaceInStrings("&", "<and>");
socket->write(packet.toUtf8()); socket->write(packet.toUtf8());
socket->flush(); socket->flush();
} }

View File

@ -232,3 +232,33 @@ void AOClient::cmdLogout(int argc, QStringList argv)
moderator_name = ""; moderator_name = "";
sendPacket("AUTH", {"-1"}); // Client: "You were logged out." sendPacket("AUTH", {"-1"}); // Client: "You were logged out."
} }
void AOClient::cmdChangePassword(int argc, QStringList argv)
{
QString username;
QString password;
if (argc == 1) {
if (moderator_name.isEmpty()) {
sendServerMessage("You are not logged in.");
return;
}
username = moderator_name;
password = argv[0];
}
else if (argc == 2 && checkAuth(ACLFlags.value("SUPER"))) {
username = argv[0];
password = argv[1];
}
else {
sendServerMessage("Invalid command syntax.");
return;
}
if (server->db_manager->updatePassword(username, password)) {
sendServerMessage("Successfully changed password.");
}
else {
sendServerMessage("There was an error changing the password.");
return;
}
}

View File

@ -22,38 +22,24 @@
void AOClient::cmdBan(int argc, QStringList argv) void AOClient::cmdBan(int argc, QStringList argv)
{ {
QString args_str = argv[1]; QString args_str = argv[2];
if (argc > 2) { if (argc > 3) {
for (int i = 2; i < argc; i++) for (int i = 3; i < argc; i++)
args_str += " " + argv[i]; args_str += " " + argv[i];
} }
DBManager::BanInfo ban; DBManager::BanInfo ban;
QRegularExpression quoteMatcher("['\"](.+?)[\"']"); if (argc < 3) {
QRegularExpressionMatchIterator matches = quoteMatcher.globalMatch(args_str); sendServerMessage("Invalid syntax. Usage:\n/ban <ipid> <duration> <reason>");
QList<QString> unquoted_args;
while (matches.hasNext()) {
QRegularExpressionMatch match = matches.next();
unquoted_args.append(match.captured(1));
}
QString duration = "perma";
if (unquoted_args.length() < 1) {
sendServerMessage("Invalid syntax. Usage:\n/ban <ipid> \"<reason>\" \"<duration>\"");
return; return;
} }
ban.reason = unquoted_args.at(0);
if (unquoted_args.length() > 1)
duration = unquoted_args.at(1);
long long duration_seconds = 0; long long duration_seconds = 0;
if (duration == "perma") if (argv[1] == "perma")
duration_seconds = -2; duration_seconds = -2;
else else
duration_seconds = parseTime(duration); duration_seconds = parseTime(argv[1]);
if (duration_seconds == -1) { if (duration_seconds == -1) {
sendServerMessage("Invalid time format. Format example: 1h30m"); sendServerMessage("Invalid time format. Format example: 1h30m");
@ -61,16 +47,17 @@ void AOClient::cmdBan(int argc, QStringList argv)
} }
ban.duration = duration_seconds; ban.duration = duration_seconds;
ban.ipid = argv[0]; ban.ipid = argv[0];
ban.reason = args_str;
ban.time = QDateTime::currentDateTime().toSecsSinceEpoch(); ban.time = QDateTime::currentDateTime().toSecsSinceEpoch();
bool ban_logged = false; bool ban_logged = false;
int kick_counter = 0; int kick_counter = 0;
if (argc > 2) { if (server->auth_type == "advanced") {
for (int i = 2; i < argv.length(); i++) { ban.moderator = moderator_name;
ban.reason += " " + argv[i];
} }
else {
ban.moderator = "moderator";
} }
for (AOClient* client : server->getClientsByIpid(ban.ipid)) { for (AOClient* client : server->getClientsByIpid(ban.ipid)) {
@ -81,7 +68,14 @@ void AOClient::cmdBan(int argc, QStringList argv)
sendServerMessage("Banned user with ipid " + ban.ipid + " for reason: " + ban.reason); sendServerMessage("Banned user with ipid " + ban.ipid + " for reason: " + ban.reason);
ban_logged = true; ban_logged = true;
} }
client->sendPacket("KB", {ban.reason + "\nID: " + QString::number(server->db_manager->getBanID(ban.ip)) + "\nUntil: " + QDateTime::fromSecsSinceEpoch(ban.time).addSecs(ban.duration).toString("dd.MM.yyyy, hh:mm")}); QString ban_duration;
if (!(ban.duration == -2)) {
ban_duration = QDateTime::fromSecsSinceEpoch(ban.time).addSecs(ban.duration).toString("MM/dd/yyyy, hh:mm");
}
else {
ban_duration = "The heat death of the universe.";
}
client->sendPacket("KB", {ban.reason + "\nID: " + QString::number(server->db_manager->getBanID(ban.ip)) + "\nUntil: " + ban_duration});
client->socket->close(); client->socket->close();
kick_counter++; kick_counter++;
} }
@ -185,6 +179,7 @@ void AOClient::cmdBans(int argc, QStringList argv)
recent_bans << "Reason for ban: " + ban.reason; recent_bans << "Reason for ban: " + ban.reason;
recent_bans << "Date of ban: " + QDateTime::fromSecsSinceEpoch(ban.time).toString("MM/dd/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 << "Moderator: " + ban.moderator;
recent_bans << "-----"; recent_bans << "-----";
} }
sendServerMessage(recent_bans.join("\n")); sendServerMessage(recent_bans.join("\n"));
@ -369,12 +364,14 @@ void AOClient::cmdBanInfo(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");
ban_info << "Ban ID: " + QString::number(ban.id);
ban_info << "Affected IPID: " + ban.ipid; ban_info << "Affected IPID: " + ban.ipid;
ban_info << "Affected HDID: " + ban.hdid; ban_info << "Affected HDID: " + ban.hdid;
ban_info << "Reason for ban: " + ban.reason; ban_info << "Reason for ban: " + ban.reason;
ban_info << "Date of ban: " + QDateTime::fromSecsSinceEpoch(ban.time).toString("dd.MM.yyyy, hh:mm"); ban_info << "Date of ban: " + QDateTime::fromSecsSinceEpoch(ban.time).toString("MM/dd/yyyy, hh:mm");
ban_info << "Ban lasts until: " + banned_until; ban_info << "Ban lasts until: " + banned_until;
ban_info << "Moderator: " + ban.moderator;
ban_info << "-----"; ban_info << "-----";
} }
sendServerMessage(ban_info.join("\n")); sendServerMessage(ban_info.join("\n"));

View File

@ -24,8 +24,11 @@ DBManager::DBManager() :
db.setDatabaseName("config/akashi.db"); db.setDatabaseName("config/akashi.db");
if (!db.open()) if (!db.open())
qCritical() << "Database Error:" << db.lastError(); qCritical() << "Database Error:" << db.lastError();
QSqlQuery create_ban_table("CREATE TABLE IF NOT EXISTS bans ('ID' INTEGER, 'IPID' TEXT, 'HDID' TEXT, 'IP' TEXT, 'TIME' INTEGER, 'REASON' TEXT, 'DURATION' INTEGER, PRIMARY KEY('ID' AUTOINCREMENT))"); db_version = checkVersion();
QSqlQuery create_ban_table("CREATE TABLE IF NOT EXISTS bans ('ID' INTEGER, 'IPID' TEXT, 'HDID' TEXT, 'IP' TEXT, 'TIME' INTEGER, 'REASON' TEXT, 'DURATION' INTEGER, 'MODERATOR' TEXT, PRIMARY KEY('ID' AUTOINCREMENT))");
QSqlQuery create_user_table("CREATE TABLE IF NOT EXISTS users ('ID' INTEGER, 'USERNAME' TEXT, 'SALT' TEXT, 'PASSWORD' TEXT, 'ACL' TEXT, PRIMARY KEY('ID' AUTOINCREMENT))"); QSqlQuery create_user_table("CREATE TABLE IF NOT EXISTS users ('ID' INTEGER, 'USERNAME' TEXT, 'SALT' TEXT, 'PASSWORD' TEXT, 'ACL' TEXT, PRIMARY KEY('ID' AUTOINCREMENT))");
if (db_version != DB_VERSION)
updateDB(db_version);
} }
bool DBManager::isIPBanned(QHostAddress ip) bool DBManager::isIPBanned(QHostAddress ip)
@ -168,6 +171,7 @@ QList<DBManager::BanInfo> DBManager::getRecentBans()
ban.time = static_cast<unsigned long>(query.value(4).toULongLong()); ban.time = static_cast<unsigned long>(query.value(4).toULongLong());
ban.reason = query.value(5).toString(); ban.reason = query.value(5).toString();
ban.duration = query.value(6).toLongLong(); ban.duration = query.value(6).toLongLong();
ban.moderator = query.value(7).toString();
return_list.append(ban); return_list.append(ban);
} }
std::reverse(return_list.begin(), return_list.end()); std::reverse(return_list.begin(), return_list.end());
@ -177,13 +181,14 @@ QList<DBManager::BanInfo> DBManager::getRecentBans()
void DBManager::addBan(BanInfo ban) void DBManager::addBan(BanInfo ban)
{ {
QSqlQuery query; QSqlQuery query;
query.prepare("INSERT INTO BANS(IPID, HDID, IP, TIME, REASON, DURATION) VALUES(?, ?, ?, ?, ?, ?)"); query.prepare("INSERT INTO BANS(IPID, HDID, IP, TIME, REASON, DURATION, MODERATOR) VALUES(?, ?, ?, ?, ?, ?, ?)");
query.addBindValue(ban.ipid); query.addBindValue(ban.ipid);
query.addBindValue(ban.hdid); query.addBindValue(ban.hdid);
query.addBindValue(ban.ip.toString()); query.addBindValue(ban.ip.toString());
query.addBindValue(QString::number(ban.time)); query.addBindValue(QString::number(ban.time));
query.addBindValue(ban.reason); query.addBindValue(ban.reason);
query.addBindValue(ban.duration); query.addBindValue(ban.duration);
query.addBindValue(ban.moderator);
if (!query.exec()) if (!query.exec())
qDebug() << "SQL Error:" << query.lastError().text(); qDebug() << "SQL Error:" << query.lastError().text();
} }
@ -347,21 +352,74 @@ QList<DBManager::BanInfo> DBManager::getBanInfo(QString lookup_type, QString id)
query.addBindValue(id); query.addBindValue(id);
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();
ban.moderator = query.value(7).toString();
return_list.append(ban); return_list.append(ban);
} }
std::reverse(return_list.begin(), return_list.end()); std::reverse(return_list.begin(), return_list.end());
return return_list; return return_list;
} }
bool DBManager::updatePassword(QString username, QString password)
{
QString salt;
QSqlQuery salt_check;
salt_check.prepare("SELECT SALT FROM users WHERE USERNAME = ?");
salt_check.addBindValue(username);
salt_check.exec();
if (!salt_check.first())
return false;
else
salt = salt_check.value(0).toString();
QSqlQuery query;
QString salted_password;
QMessageAuthenticationCode hmac(QCryptographicHash::Sha256);
hmac.setKey(salt.toUtf8());
hmac.addData(password.toUtf8());
salted_password = hmac.result().toHex();
query.prepare("UPDATE users SET PASSWORD = ? WHERE USERNAME = ?");
query.addBindValue(salted_password);
query.addBindValue(username);
query.exec();
return true;
}
int DBManager::checkVersion()
{
QSqlQuery query;
query.prepare("PRAGMA user_version");
query.exec();
if (query.first()) {
return query.value(0).toInt();
}
else {
return 0;
}
}
void DBManager::updateDB(int current_version)
{
switch (current_version) {
case 0:
QSqlQuery("ALTER TABLE bans ADD COLUMN MODERATOR TEXT");
case 1:
QSqlQuery ("PRAGMA user_version = " + QString::number(DB_VERSION));
break;
}
}
DBManager::~DBManager() DBManager::~DBManager()
{ {
db.close(); db.close();

View File

@ -63,6 +63,11 @@ void AOClient::pktSoftwareId(AreaData* area, int argc, QStringList argv, AOPacke
sendPacket("PN", {QString::number(server->player_count), server->max_players}); sendPacket("PN", {QString::number(server->player_count), server->max_players});
sendPacket("FL", feature_list); sendPacket("FL", feature_list);
if (server->asset_url.isValid()) {
QByteArray asset_url = server->asset_url.toEncoded(QUrl::EncodeSpaces);
sendPacket("ASS", {asset_url});
}
} }
void AOClient::pktBeginLoad(AreaData* area, int argc, QStringList argv, AOPacket packet) void AOClient::pktBeginLoad(AreaData* area, int argc, QStringList argv, AOPacket packet)

View File

@ -295,6 +295,9 @@ void Server::loadServerConfig()
message_floodguard = config.value("message_floodguard", "250").toInt(&message_floodguard_conversion_success); message_floodguard = config.value("message_floodguard", "250").toInt(&message_floodguard_conversion_success);
if (!message_floodguard_conversion_success) if (!message_floodguard_conversion_success)
message_floodguard = 30; message_floodguard = 30;
asset_url = config.value("asset_url","").toString().toUtf8();
if (!asset_url.isValid())
asset_url = NULL;
config.endGroup(); config.endGroup();
//Load dice values //Load dice values