diff --git a/akashi.pro b/akashi.pro index 3ff90f9..dac5e79 100644 --- a/akashi.pro +++ b/akashi.pro @@ -25,9 +25,9 @@ SOURCES += src/advertiser.cpp \ src/aoclient.cpp \ src/aopacket.cpp \ src/area_data.cpp \ - src/ban_manager.cpp \ src/commands.cpp \ src/config_manager.cpp \ + src/db_manager.cpp \ src/icchatpacket.cpp \ src/main.cpp \ src/packets.cpp \ @@ -40,8 +40,8 @@ HEADERS += include/advertiser.h \ include/aoclient.h \ include/aopacket.h \ include/area_data.h \ - include/ban_manager.h \ include/config_manager.h \ + include/db_manager.h \ include/icchatpacket.h \ include/server.h \ include/ws_client.h \ diff --git a/bin/config_sample/config.ini b/bin/config_sample/config.ini index 06a05a8..552cf15 100644 --- a/bin/config_sample/config.ini +++ b/bin/config_sample/config.ini @@ -7,7 +7,6 @@ hostname=$H language=en max_message_size=256 max_players=100 -modpass=password ms_ip=master.aceattorneyonline.com ms_port=27016 multiclient_limit=16 @@ -16,7 +15,5 @@ server_description=This is a placeholder server description. Tell the world of A server_name=An Unnamed Server webao_enable=true webao_port=27017 - -[Moderators] -; Make sure you change this before running your server! -password_change_me=user_change_me \ No newline at end of file +auth=simple +modpass=changeme \ No newline at end of file diff --git a/include/aoclient.h b/include/aoclient.h index 53c70cd..00a8c34 100644 --- a/include/aoclient.h +++ b/include/aoclient.h @@ -22,12 +22,14 @@ #include "include/server.h" #include "include/icchatpacket.h" #include "include/area_data.h" +#include "include/db_manager.h" #include #include #include #include +#include class Server; @@ -48,8 +50,16 @@ class AOClient : public QObject { int current_area; QString current_char; bool authenticated = false; + QString moderator_name = ""; QString ooc_name = ""; + enum ACLFlags { + NONE = 0ULL, + KICK = 1ULL << 0, + BAN = 1ULL << 1, + SUPER = ~0ULL + }; + public slots: void clientDisconnected(); void clientData(); @@ -74,6 +84,7 @@ class AOClient : public QObject { void arup(ARUPType type, bool broadcast); void fullArup(); void sendServerMessage(QString message); + bool checkAuth(unsigned long long acl_mask); // Packet headers void pktDefault(AreaData* area, int argc, QStringList argv, AOPacket packet); @@ -94,26 +105,27 @@ class AOClient : public QObject { void pktWebSocketIp(AreaData* area, int argc, QStringList argv, AOPacket packet); struct PacketInfo { + unsigned long long acl_mask; int minArgs; void (AOClient::*action)(AreaData*, int, QStringList, AOPacket); }; const QMap packets { - {"HI", {1, &AOClient::pktHardwareId}}, - {"ID", {2, &AOClient::pktSoftwareId}}, - {"askchaa", {0, &AOClient::pktBeginLoad}}, - {"RC", {0, &AOClient::pktRequestChars}}, - {"RM", {0, &AOClient::pktRequestMusic}}, - {"RD", {0, &AOClient::pktLoadingDone}}, - {"PW", {1, &AOClient::pktCharPassword}}, - {"CC", {3, &AOClient::pktSelectChar}}, - {"MS", {1, &AOClient::pktIcChat}}, // TODO: doublecheck - {"CT", {2, &AOClient::pktOocChat}}, - {"CH", {1, &AOClient::pktPing}}, - {"MC", {2, &AOClient::pktChangeMusic}}, - {"RT", {1, &AOClient::pktWtCe}}, - {"HP", {2, &AOClient::pktHpBar}}, - {"WSIP", {1, &AOClient::pktWebSocketIp}} + {"HI", {ACLFlags::NONE, 1, &AOClient::pktHardwareId}}, + {"ID", {ACLFlags::NONE, 2, &AOClient::pktSoftwareId}}, + {"askchaa", {ACLFlags::NONE, 0, &AOClient::pktBeginLoad}}, + {"RC", {ACLFlags::NONE, 0, &AOClient::pktRequestChars}}, + {"RM", {ACLFlags::NONE, 0, &AOClient::pktRequestMusic}}, + {"RD", {ACLFlags::NONE, 0, &AOClient::pktLoadingDone}}, + {"PW", {ACLFlags::NONE, 1, &AOClient::pktCharPassword}}, + {"CC", {ACLFlags::NONE, 3, &AOClient::pktSelectChar}}, + {"MS", {ACLFlags::NONE, 1, &AOClient::pktIcChat}}, // TODO: doublecheck + {"CT", {ACLFlags::NONE, 2, &AOClient::pktOocChat}}, + {"CH", {ACLFlags::NONE, 1, &AOClient::pktPing}}, + {"MC", {ACLFlags::NONE, 2, &AOClient::pktChangeMusic}}, + {"RT", {ACLFlags::NONE, 1, &AOClient::pktWtCe}}, + {"HP", {ACLFlags::NONE, 2, &AOClient::pktHpBar}}, + {"WSIP", {ACLFlags::NONE, 1, &AOClient::pktWebSocketIp}} }; // Commands @@ -123,22 +135,29 @@ class AOClient : public QObject { void cmdGetArea(int argc, QStringList argv); void cmdBan(int argc, QStringList argv); void cmdKick(int argc, QStringList argv); + void cmdChangeAuth(int argc, QStringList argv); + void cmdSetRootPass(int argc, QStringList argv); // Command helper functions QStringList buildAreaList(int area_idx); + // Command function global variables + bool change_auth_started = false; + struct CommandInfo { - bool privileged; + unsigned long long acl_mask; int minArgs; void (AOClient::*action)(int, QStringList); }; const QMap commands { - {"login", {false, 1, &AOClient::cmdLogin}}, - {"getareas", {false, 0 , &AOClient::cmdGetAreas}}, - {"getarea", {false, 0, &AOClient::cmdGetArea}}, - {"ban", {true, 2, &AOClient::cmdBan}}, - {"kick", {true, 2, &AOClient::cmdKick}} + {"login", {ACLFlags::NONE, 1, &AOClient::cmdLogin}}, + {"getareas", {ACLFlags::NONE, 0 , &AOClient::cmdGetAreas}}, + {"getarea", {ACLFlags::NONE, 0, &AOClient::cmdGetArea}}, + {"ban", {ACLFlags::BAN, 2, &AOClient::cmdBan}}, + {"kick", {ACLFlags::KICK, 2, &AOClient::cmdKick}}, + {"changeauth", {ACLFlags::SUPER, 0, &AOClient::cmdChangeAuth}}, + {"rootpass", {ACLFlags::SUPER, 1, &AOClient::cmdSetRootPass}} }; QString partial_packet; diff --git a/include/ban_manager.h b/include/db_manager.h similarity index 84% rename from include/ban_manager.h rename to include/db_manager.h index 42ceb40..d1a126e 100644 --- a/include/ban_manager.h +++ b/include/db_manager.h @@ -18,18 +18,21 @@ #ifndef BAN_MANAGER_H #define BAN_MANAGER_H +#include "include/aoclient.h" + #include #include +#include #include #include #include #include #include -class BanManager{ +class DBManager{ public: - BanManager(); - ~BanManager(); + DBManager(); + ~DBManager(); bool isIPBanned(QHostAddress ip); bool isHDIDBanned(QString hdid); @@ -39,8 +42,16 @@ public: void addBan(QString ipid, QHostAddress ip, QString hdid, unsigned long time, QString reason); + void createUser(QString username, QString salt, QString password, unsigned long long acl); + unsigned long long getACL(QString moderator_name); + bool authenticate(QString username, QString password); + private: const QString DRIVER; + const QString CONN_NAME; + + void openDB(); + QSqlDatabase db; }; diff --git a/include/server.h b/include/server.h index 01b5fb9..11b1ec0 100644 --- a/include/server.h +++ b/include/server.h @@ -22,7 +22,7 @@ #include "include/aopacket.h" #include "include/area_data.h" #include "include/ws_proxy.h" -#include "include/ban_manager.h" +#include "include/db_manager.h" #include #include @@ -34,6 +34,7 @@ #include class AOClient; +class DBManager; class Server : public QObject { Q_OBJECT @@ -55,7 +56,7 @@ class Server : public QObject { QVector areas; QStringList area_names; QStringList music_list; - BanManager* ban_manager; + DBManager* db_manager; signals: diff --git a/src/aoclient.cpp b/src/aoclient.cpp index f40dfdf..79af190 100644 --- a/src/aoclient.cpp +++ b/src/aoclient.cpp @@ -73,7 +73,12 @@ void AOClient::handlePacket(AOPacket packet) // TODO: like everything here should send a signal // qDebug() << "Received packet:" << packet.header << ":" << packet.contents; AreaData* area = server->areas[current_area]; - PacketInfo info = packets.value(packet.header, {0, &AOClient::pktDefault}); + PacketInfo info = packets.value(packet.header, {false, 0, &AOClient::pktDefault}); + + if (!checkAuth(info.acl_mask)) { + qDebug() << "Unauthenticated client" << getIpid() << "attempted to use privileged packet" << packet.header; + return; + } if (packet.contents.length() < info.minArgs) { qDebug() << "Invalid packet args length. Minimum is" << info.minArgs << "but only" << packet.contents.length() << "were given."; @@ -122,7 +127,7 @@ void AOClient::handleCommand(QString command, int argc, QStringList argv) { CommandInfo info = commands.value(command, {false, -1, &AOClient::cmdDefault}); - if (info.privileged && !authenticated) { + if (!checkAuth(info.acl_mask)) { sendServerMessage("You do not have permission to use that command."); return; } @@ -210,6 +215,30 @@ void AOClient::sendServerMessage(QString message) sendPacket("CT", {"Server", message, "1"}); } +bool AOClient::checkAuth(unsigned long long acl_mask) +{ + if (acl_mask != ACLFlags::NONE) { + if (!authenticated) { + return false; + } + + QSettings settings("config/config.ini", QSettings::IniFormat); + settings.beginGroup("Options"); + QString auth_type = settings.value("auth", "simple").toString(); + qDebug() << "auth type" << auth_type; + if (auth_type == "advanced") { + unsigned long long user_acl = server->db_manager->getACL(moderator_name); + qDebug() << "checking with advanced auth"; + qDebug() << "got acl" << QString::number(user_acl, 16).toUpper() << "for user" << moderator_name; + return (user_acl & acl_mask) != 0; + } + else if (auth_type == "simple") { + return authenticated; + } + } + return true; +} + QString AOClient::getIpid() { return ipid; } AOClient::~AOClient() { diff --git a/src/commands.cpp b/src/commands.cpp index 89f8658..38ade98 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -29,14 +29,34 @@ void AOClient::cmdLogin(int argc, QStringList argv) { QSettings config("config/config.ini", QSettings::IniFormat); config.beginGroup("Options"); - QString modpass = config.value("modpass", "default").toString();; + QString modpass = config.value("modpass", "default").toString(); + QString auth_type = config.value("auth", "simple").toString(); + // TODO: tell the user if no modpass is set - if(argv[0] == modpass) { - sendServerMessage("Logged in as a moderator."); // This string has to be exactly this, because it is hardcoded in the client - authenticated = true; - } else { - sendServerMessage("Incorrect password."); - return; + if (auth_type == "simple") { + if(argv[0] == modpass) { + sendServerMessage("Logged in as a moderator."); // This string has to be exactly this, because it is hardcoded in the client + authenticated = true; + } else { + sendServerMessage("Incorrect password."); + return; + } + } + else { + if (argc < 2) { + sendServerMessage("You must specify a username and a password"); + return; + } + QString username = argv[0]; + QString password = argv[1]; + if (server->db_manager->authenticate(username, password)) { + moderator_name = username; + authenticated = true; + sendServerMessage("Logged in as " + username); + } + else { + sendServerMessage("Incorrect password."); + } } } @@ -77,7 +97,7 @@ void AOClient::cmdBan(int argc, QStringList argv) if (!ban_logged) { ip = client->remote_ip; hdid = client->hwid; - server->ban_manager->addBan(target_ipid, ip, hdid, time, reason); + server->db_manager->addBan(target_ipid, ip, hdid, time, reason); sendServerMessage("Banned user with ipid " + target_ipid + " for reason: " + reason); ban_logged = true; } @@ -116,6 +136,35 @@ void AOClient::cmdKick(int argc, QStringList argv) sendServerMessage("User with ipid not found!"); } +void AOClient::cmdChangeAuth(int argc, QStringList argv) +{ + QSettings settings("config/config.ini", QSettings::IniFormat); + settings.beginGroup("Options"); + QString auth_type = settings.value("auth", "simple").toString(); + + if (auth_type == "simple") { + change_auth_started = true; + sendServerMessage("WARNING!\nThis command will change how logging in as a moderator works.\nOnly proceed if you know what you are doing\nUse the command /rootpass to set the password for your root account."); + } +} + +void AOClient::cmdSetRootPass(int argc, QStringList argv) +{ + if (!change_auth_started) + return; + + sendServerMessage("Changing auth type and setting root password.\nLogin again with /login root [password]"); + authenticated = false; + QSettings settings("config/config.ini", QSettings::IniFormat); + settings.beginGroup("Options"); + settings.setValue("auth", "advanced"); + + quint64 salt_number = QRandomGenerator::system()->generate64(); + QString salt = QStringLiteral("%1").arg(salt_number, 16, 16, QLatin1Char('0')); + + server->db_manager->createUser("root", salt, argv[0], ACLFlags::SUPER); +} + QStringList AOClient::buildAreaList(int area_idx) { QStringList entries; diff --git a/src/ban_manager.cpp b/src/db_manager.cpp similarity index 50% rename from src/ban_manager.cpp rename to src/db_manager.cpp index a18a2a7..9a083f6 100644 --- a/src/ban_manager.cpp +++ b/src/db_manager.cpp @@ -15,19 +15,20 @@ // You should have received a copy of the GNU Affero General Public License // // along with this program. If not, see . // ////////////////////////////////////////////////////////////////////////////////////// -#include "include/ban_manager.h" +#include "include/db_manager.h" -BanManager::BanManager() : +DBManager::DBManager() : DRIVER("QSQLITE") { db = QSqlDatabase::addDatabase(DRIVER); - db.setDatabaseName("config/bans.db"); + db.setDatabaseName("config/akashi.db"); if (!db.open()) qCritical() << "Database Error:" << db.lastError(); - QSqlQuery create_table_query("CREATE TABLE IF NOT EXISTS bans ('ID' INTEGER, 'IPID' TEXT, 'HDID' TEXT, 'IP' TEXT, 'TIME' INTEGER, 'REASON' TEXT, PRIMARY KEY('ID' AUTOINCREMENT));"); + QSqlQuery create_ban_table("CREATE TABLE IF NOT EXISTS bans ('ID' INTEGER, 'IPID' TEXT, 'HDID' TEXT, 'IP' TEXT, 'TIME' INTEGER, 'REASON' 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))"); } -bool BanManager::isIPBanned(QHostAddress ip) +bool DBManager::isIPBanned(QHostAddress ip) { QSqlQuery query; query.prepare("SELECT ID FROM BANS WHERE IP = ?"); @@ -36,7 +37,7 @@ bool BanManager::isIPBanned(QHostAddress ip) return query.first(); } -bool BanManager::isHDIDBanned(QString hdid) +bool DBManager::isHDIDBanned(QString hdid) { QSqlQuery query; query.prepare("SELECT ID FROM BANS WHERE HDID = ?"); @@ -45,7 +46,7 @@ bool BanManager::isHDIDBanned(QString hdid) return query.first(); } -QString BanManager::getBanReason(QHostAddress ip) +QString DBManager::getBanReason(QHostAddress ip) { QSqlQuery query; query.prepare("SELECT REASON FROM BANS WHERE IP = ?"); @@ -59,7 +60,7 @@ QString BanManager::getBanReason(QHostAddress ip) } } -QString BanManager::getBanReason(QString hdid) +QString DBManager::getBanReason(QString hdid) { QSqlQuery query; query.prepare("SELECT REASON FROM BANS WHERE HDID = ?"); @@ -73,7 +74,7 @@ QString BanManager::getBanReason(QString hdid) } } -void BanManager::addBan(QString ipid, QHostAddress ip, QString hdid, unsigned long time, QString reason) +void DBManager::addBan(QString ipid, QHostAddress ip, QString hdid, unsigned long time, QString reason) { QSqlQuery query; query.prepare("INSERT INTO BANS(IPID, HDID, IP, TIME, REASON) VALUES(?, ?, ?, ?, ?)"); @@ -86,7 +87,66 @@ void BanManager::addBan(QString ipid, QHostAddress ip, QString hdid, unsigned lo qDebug() << "SQL Error:" << query.lastError().text(); } -BanManager::~BanManager() +void DBManager::createUser(QString username, QString salt, QString password, unsigned long long acl) +{ + QSqlQuery query; + + QString salted_password; + QMessageAuthenticationCode hmac(QCryptographicHash::Sha256); + hmac.setKey(salt.toUtf8()); + hmac.addData(password.toUtf8()); + salted_password = hmac.result().toHex(); + + query.prepare("INSERT INTO users(USERNAME, SALT, PASSWORD, ACL) VALUES(?, ?, ?, ?)"); + query.addBindValue(username); + query.addBindValue(salt); + query.addBindValue(salted_password); + query.addBindValue(acl); + query.exec(); + + qDebug() << "Created user" << username << "with password" << password << "and salted with value" << salt << ": stored as" << salted_password; +} + +unsigned long long DBManager::getACL(QString moderator_name) +{ + if (moderator_name == "") + return AOClient::ACLFlags::NONE; + QSqlQuery query("SELECT ACL FROM users WHERE USERNAME = ?"); + query.addBindValue(moderator_name); + query.exec(); + if (!query.first()) + return AOClient::ACLFlags::NONE; + return query.value(0).toULongLong(); +} + +bool DBManager::authenticate(QString username, QString password) +{ + QSqlQuery query_salt("SELECT SALT FROM users WHERE USERNAME = ?"); + query_salt.addBindValue(username); + query_salt.exec(); + if (!query_salt.first()) + return false; + QString salt = query_salt.value(0).toString(); + + QString salted_password; + QMessageAuthenticationCode hmac(QCryptographicHash::Sha256); + hmac.setKey(salt.toUtf8()); + hmac.addData(password.toUtf8()); + salted_password = hmac.result().toHex(); + + QSqlQuery query_pass("SELECT PASSWORD FROM users WHERE SALT = ?"); + query_pass.addBindValue(salt); + query_pass.exec(); + if (!query_pass.first()) + return false; + QString stored_pass = query_pass.value(0).toString(); + + qDebug() << "Found DB entry Salt:" << salt << "Stored Password:" << stored_pass << "Calculated Password:" << salted_password; + + return salted_password == stored_pass; +} + +DBManager::~DBManager() { db.close(); } diff --git a/src/packets.cpp b/src/packets.cpp index 9cad840..a031299 100644 --- a/src/packets.cpp +++ b/src/packets.cpp @@ -26,8 +26,8 @@ void AOClient::pktDefault(AreaData* area, int argc, QStringList argv, AOPacket p void AOClient::pktHardwareId(AreaData* area, int argc, QStringList argv, AOPacket packet) { setHwid(argv[0]); - if(server->ban_manager->isHDIDBanned(getHwid())) { - sendPacket("BD", {server->ban_manager->getBanReason(getHwid())}); + if(server->db_manager->isHDIDBanned(getHwid())) { + sendPacket("BD", {server->db_manager->getBanReason(getHwid())}); socket->close(); return; } @@ -141,7 +141,7 @@ void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket p QString command = cmd_argv[0].trimmed().toLower(); command = command.right(command.length() - 1); cmd_argv.removeFirst(); - int cmd_argc = argv.length(); + int cmd_argc = cmd_argv.length(); handleCommand(command, cmd_argc, cmd_argv); return; } @@ -209,8 +209,8 @@ void AOClient::pktWebSocketIp(AreaData* area, int argc, QStringList argv, AOPack // Special packet to set remote IP from the webao proxy // Only valid if from a local ip if (remote_ip.isLoopback()) { - if(server->ban_manager->isIPBanned(QHostAddress(argv[0]))) { - sendPacket("BD", {server->ban_manager->getBanReason(QHostAddress(argv[0]))}); + if(server->db_manager->isIPBanned(QHostAddress(argv[0]))) { + sendPacket("BD", {server->db_manager->getBanReason(QHostAddress(argv[0]))}); socket->close(); return; } diff --git a/src/server.cpp b/src/server.cpp index 842242a..8362f3c 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -27,7 +27,7 @@ Server::Server(int p_port, int p_ws_port, QObject* parent) : QObject(parent) player_count = 0; - ban_manager = new BanManager(); + db_manager = new DBManager(); } void Server::start() @@ -79,8 +79,8 @@ void Server::clientConnected() { QTcpSocket* socket = server->nextPendingConnection(); AOClient* client = new AOClient(this, socket, this); - if (ban_manager->isIPBanned(socket->peerAddress())) { - AOPacket ban_reason("BD", {ban_manager->getBanReason(socket->peerAddress())}); + if (db_manager->isIPBanned(socket->peerAddress())) { + AOPacket ban_reason("BD", {db_manager->getBanReason(socket->peerAddress())}); socket->write(ban_reason.toUtf8()); socket->flush(); client->deleteLater(); @@ -149,4 +149,6 @@ Server::~Server() } server->deleteLater(); proxy->deleteLater(); + + delete db_manager; }