diff --git a/README.md b/README.md index 3681abc..862d627 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,8 @@ Requires Qt >= 5.10, and Qt websockets make ``` -Copyright © scatterflower 2021 \ No newline at end of file +Copyright © scatterflower 2020-2021 + +Copyright © Salanto 2021 + +Copyright © in1tiate 2021 diff --git a/bin/config_sample/areas.ini b/bin/config_sample/areas.ini index 14f79ca..5e687da 100644 --- a/bin/config_sample/areas.ini +++ b/bin/config_sample/areas.ini @@ -1,5 +1,11 @@ -[Basement] +[0:Basement] background=gs4 +protected_area=true +iniswap_allowed=false +evidence_mod=cm -[Courtroom 1] +[1:Courtroom 1] background=gs4 +protected_area=false +iniswap_allowed=true +evidence_mod=ffa \ No newline at end of file diff --git a/bin/config_sample/config.ini b/bin/config_sample/config.ini index a33f95d..eaf63ba 100644 --- a/bin/config_sample/config.ini +++ b/bin/config_sample/config.ini @@ -9,6 +9,7 @@ ms_port=27016 port=27016 server_description=This is a placeholder server description. Tell the world of AO who you are here! server_name=An Unnamed Server +motd=MOTD is not set. webao_enable=true webao_port=27017 auth=simple diff --git a/include/aoclient.h b/include/aoclient.h index 0444abf..690adbb 100644 --- a/include/aoclient.h +++ b/include/aoclient.h @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #if QT_VERSION > QT_VERSION_CHECK(5, 10, 0) #include @@ -42,10 +42,9 @@ class AOClient : public QObject { AOClient(Server* p_server, QTcpSocket* p_socket, QObject* parent = nullptr, int user_id = 0); ~AOClient(); - QString getHwid(); - QString getIpid(); Server* getServer(); - void setHwid(QString p_hwid); + QString getIpid(); + void calculateIpid(); int id; @@ -57,6 +56,16 @@ class AOClient : public QObject { bool authenticated = false; QString moderator_name = ""; QString ooc_name = ""; + QString showname = ""; + bool global_enabled = true; + bool is_muted = false; + struct ClientVersion { + QString string; + int release = -1; + int major = -1; + int minor = -1; + }; + ClientVersion version; QMap ACLFlags { {"NONE", 0ULL}, @@ -66,6 +75,11 @@ class AOClient : public QObject { {"MODIFY_USERS", 1ULL << 3}, {"CM", 1ULL << 4}, {"GLOBAL_TIMER", 1ULL << 5}, + {"EVI_MOD", 1ULL << 6}, + {"MOTD", 1ULL << 7}, + {"ANNOUNCE", 1ULL << 8}, + {"MODCHAT", 1ULL << 9}, + {"MUTE", 1ULL << 10}, {"SUPER", ~0ULL} }; @@ -92,14 +106,16 @@ class AOClient : public QObject { }; enum RollType { - ROLL, - ROLLP, - ROLLA + ROLL, + ROLLP, + ROLLA }; void handlePacket(AOPacket packet); void handleCommand(QString command, int argc, QStringList argv); void changeArea(int new_area); + void changeCharacter(int char_id); + void changePosition(QString new_pos); void arup(ARUPType type, bool broadcast); void fullArup(); void sendServerMessage(QString message); @@ -131,8 +147,10 @@ class AOClient : public QObject { // Packet helper functions void sendEvidenceList(AreaData* area); + void updateEvidenceList(AreaData* area); AOPacket validateIcPacket(AOPacket packet); QString dezalgo(QString p_text); + bool checkEvidenceAccess(AreaData* area); // Packet helper global variables int char_id = -1; @@ -172,11 +190,15 @@ class AOClient : public QObject { //// Commands void cmdDefault(int argc, QStringList argv); + void cmdHelp(int argc, QStringList argv); + void cmdMOTD(int argc, QStringList argv); + void cmdAbout(int argc, QStringList argv); // Authentication void cmdLogin(int argc, QStringList argv); void cmdChangeAuth(int argc, QStringList argv); void cmdSetRootPass(int argc, QStringList argv); void cmdAddUser(int argc, QStringList argv); + void cmdRemoveUser(int argc, QStringList argv); void cmdListPerms(int argc, QStringList argv); void cmdAddPerms(int argc, QStringList argv); void cmdRemovePerms(int argc, QStringList argv); @@ -192,13 +214,26 @@ class AOClient : public QObject { void cmdUnLock(int argc, QStringList argv); void cmdGetAreas(int argc, QStringList argv); void cmdGetArea(int argc, QStringList argv); + void cmdArea(int argc, QStringList argv); + void cmdAreaKick(int argc, QStringList argv); void cmdSetBackground(int argc, QStringList argv); void cmdBgLock(int argc, QStringList argv); void cmdBgUnlock(int argc, QStringList argv); + void cmdStatus(int argc, QStringList argv); + void cmdCurrentMusic(int argc, QStringList argv); // Moderation + void cmdMods(int argc, QStringList argv); void cmdBan(int argc, QStringList argv); + void cmdUnBan(int argc, QStringList argv); void cmdKick(int argc, QStringList argv); + void cmdAnnounce(int argc, QStringList argv); + void cmdM(int argc, QStringList argv); + void cmdGM(int argc, QStringList argv); + void cmdMute(int argc, QStringList argv); + void cmdUnmute(int argc, QStringList argv); + void cmdBans(int argc, QStringList argv); // Casing/RP + void cmdPlay(int argc, QStringList argv); void cmdNeed(int argc, QStringList argv); void cmdFlip(int argc, QStringList argv); void cmdRoll(int argc, QStringList argv); @@ -206,15 +241,23 @@ class AOClient : public QObject { void cmdDoc(int argc, QStringList argv); void cmdClearDoc(int argc, QStringList argv); void cmdTimer(int argc, QStringList argv); + void cmdEvidenceMod(int argc, QStringList argv); + void cmdSubTheme(int argc, QStringList argv); // Messaging/Client void cmdPos(int argc, QStringList argv); + void cmdForcePos(int argc, QStringList argv); + void cmdSwitch(int argc, QStringList argv); + void cmdRandomChar(int argc, QStringList argv); void cmdG(int argc, QStringList argv); + void cmdToggleGlobal(int argc, QStringList argv); + void cmdPM(int argc, QStringList argv); // Command helper functions QString getAreaTimer(int area_idx, QTimer* timer); QStringList buildAreaList(int area_idx); int genRand(int min, int max); void diceThrower(int argc, QStringList argv, RollType Type); + long long parseTime(QString input); // Command function global variables bool change_auth_started = false; @@ -246,7 +289,7 @@ class AOClient : public QObject { {"pos", {ACLFlags.value("NONE"), 1, &AOClient::cmdPos}}, {"g", {ACLFlags.value("NONE"), 1, &AOClient::cmdG}}, {"need", {ACLFlags.value("NONE"), 1, &AOClient::cmdNeed}}, - {"flip", {ACLFlags.value("NONE"), 0, &AOClient::cmdFlip}}, + {"coinflip", {ACLFlags.value("NONE"), 0, &AOClient::cmdFlip}}, {"roll", {ACLFlags.value("NONE"), 0, &AOClient::cmdRoll}}, {"rollp", {ACLFlags.value("NONE"), 0, &AOClient::cmdRollP}}, {"doc", {ACLFlags.value("NONE"), 0, &AOClient::cmdDoc}}, @@ -256,9 +299,37 @@ class AOClient : public QObject { {"invite", {ACLFlags.value("CM"), 1, &AOClient::cmdInvite}}, {"uninvite", {ACLFlags.value("CM"), 1, &AOClient::cmdUnInvite}}, {"lock", {ACLFlags.value("CM"), 0, &AOClient::cmdLock}}, + {"area_lock", {ACLFlags.value("CM"), 0, &AOClient::cmdLock}}, {"spectatable", {ACLFlags.value("CM"), 0, &AOClient::cmdSpectatable}}, + {"area_spectate", {ACLFlags.value("CM"), 0, &AOClient::cmdSpectatable}}, {"unlock", {ACLFlags.value("CM"), 0, &AOClient::cmdUnLock}}, + {"area_unlock", {ACLFlags.value("CM"), 0, &AOClient::cmdUnLock}}, {"timer", {ACLFlags.value("CM"), 0, &AOClient::cmdTimer}}, + {"area", {ACLFlags.value("NONE"), 1, &AOClient::cmdArea}}, + {"play", {ACLFlags.value("CM"), 1, &AOClient::cmdPlay}}, + {"areakick", {ACLFlags.value("CM"), 1, &AOClient::cmdAreaKick}}, + {"area_kick", {ACLFlags.value("CM"), 1, &AOClient::cmdAreaKick}}, + {"randomchar", {ACLFlags.value("NONE"), 0, &AOClient::cmdRandomChar}}, + {"switch", {ACLFlags.value("NONE"), 1, &AOClient::cmdSwitch}}, + {"toggleglobal", {ACLFlags.value("NONE"), 0, &AOClient::cmdToggleGlobal}}, + {"mods", {ACLFlags.value("NONE"), 0, &AOClient::cmdMods}}, + {"help", {ACLFlags.value("NONE"), 0, &AOClient::cmdHelp}}, + {"status", {ACLFlags.value("NONE"), 1, &AOClient::cmdStatus}}, + {"forcepos", {ACLFlags.value("CM"), 2, &AOClient::cmdForcePos}}, + {"currentmusic", {ACLFlags.value("NONE"), 0, &AOClient::cmdCurrentMusic}}, + {"pm", {ACLFlags.value("NONE"), 2, &AOClient::cmdPM}}, + {"evidence_mod", {ACLFlags.value("EVI_MOD"), 1, &AOClient::cmdEvidenceMod}}, + {"motd", {ACLFlags.value("NONE"), 0, &AOClient::cmdMOTD}}, + {"announce", {ACLFlags.value("ANNOUNCE"), 1, &AOClient::cmdAnnounce}}, + {"m", {ACLFlags.value("MODCHAT"), 1, &AOClient::cmdM}}, + {"gm", {ACLFlags.value("MODCHAT"), 1, &AOClient::cmdGM}}, + {"mute", {ACLFlags.value("MUTE"), 1, &AOClient::cmdMute}}, + {"unmute", {ACLFlags.value("MUTE"), 1, &AOClient::cmdUnmute}}, + {"bans", {ACLFlags.value("BAN"), 0, &AOClient::cmdBans}}, + {"unban", {ACLFlags.value("BAN"), 1, &AOClient::cmdUnBan}}, + {"removeuser", {ACLFlags.value("MODIFY_USERS"), 1, &AOClient::cmdRemoveUser}}, + {"subtheme", {ACLFlags.value("CM"), 1, &AOClient::cmdSubTheme}}, + {"about", {ACLFlags.value("NONE"), 0, &AOClient::cmdAbout}} }; QString partial_packet; diff --git a/include/area_data.h b/include/area_data.h index 1697ec6..49f0a28 100644 --- a/include/area_data.h +++ b/include/area_data.h @@ -28,9 +28,10 @@ #include class Logger; -class AreaData { +class AreaData : public QObject { + Q_OBJECT public: - AreaData(QStringList p_characters, QString p_name, int p_index); + AreaData(QString p_name, int p_index); struct Evidence { QString name; @@ -40,10 +41,19 @@ class AreaData { QList timers; QString name; int index; - QMap characters_taken; + QList characters_taken; QList evidence; int player_count; - QString status; + enum Status { + IDLE, + RP, + CASING, + LOOKING_FOR_PLAYERS, + RECESS, + GAMING + }; + Q_ENUM(Status); + Status status; QList owners; QList invited; enum LockStatus { @@ -51,6 +61,7 @@ class AreaData { LOCKED, SPECTATABLE }; + Q_ENUM(LockStatus); LockStatus locked; QString background; bool is_protected; @@ -60,7 +71,16 @@ class AreaData { QString document; int def_hp; int pro_hp; + QString current_music; + QString music_played_by; Logger* logger; + enum EvidenceMod{ + FFA, + MOD, + CM, + HIDDEN_CM + }; + EvidenceMod evi_mod; }; #endif // AREA_DATA_H diff --git a/include/config_manager.h b/include/config_manager.h index 7c5deb0..4c1baa2 100644 --- a/include/config_manager.h +++ b/include/config_manager.h @@ -36,7 +36,7 @@ class ConfigManager { QString ms_ip; int port; int ws_port; - int local_port; + int ms_port; QString name; QString description; bool advertise_server; diff --git a/include/db_manager.h b/include/db_manager.h index 150d751..636f3f1 100644 --- a/include/db_manager.h +++ b/include/db_manager.h @@ -19,6 +19,7 @@ #define BAN_MANAGER_H #include +#include #include #include #include @@ -37,10 +38,26 @@ public: QString getBanReason(QHostAddress ip); QString getBanReason(QString hdid); + long long getBanDuration(QString hdid); + long long getBanDuration(QHostAddress ip); + int getBanID(QString hdid); + int getBanID(QHostAddress ip); - void addBan(QString ipid, QHostAddress ip, QString hdid, unsigned long time, QString reason); + struct BanInfo { + QString ipid; + QHostAddress ip; + QString hdid; + unsigned long time; + QString reason; + long long duration; + }; + QList getRecentBans(); + + void addBan(BanInfo ban); + bool invalidateBan(int id); bool createUser(QString username, QString salt, QString password, unsigned long long acl); + bool deleteUser(QString username); unsigned long long getACL(QString moderator_name); bool authenticate(QString username, QString password); bool updateACL(QString username, unsigned long long acl, bool mode); diff --git a/include/server.h b/include/server.h index ae7db9c..cac743e 100644 --- a/include/server.h +++ b/include/server.h @@ -46,13 +46,14 @@ class Server : public QObject { ~Server(); void start(); - AOClient* getClient(QString ipid); + QList getClientsByIpid(QString ipid); AOClient* getClientByID(int id); void updateCharsTaken(AreaData* area); void broadcast(AOPacket packet, int area_index); void broadcast(AOPacket packet); QString getServerName(); int getDiceValue(QString value_type); + int getCharID(QString char_name); QVector clients; @@ -64,6 +65,7 @@ class Server : public QObject { QStringList backgrounds; DBManager* db_manager; QString server_name; + QString MOTD; QTimer* timer; diff --git a/src/advertiser.cpp b/src/advertiser.cpp index 31d2860..a45c33d 100644 --- a/src/advertiser.cpp +++ b/src/advertiser.cpp @@ -62,7 +62,7 @@ void Advertiser::socketConnected() AOPacket ao_packet("SCC", {concat_ports, name, description, - "akashi v" + QCoreApplication::applicationVersion()}); + "akashi " + QCoreApplication::applicationVersion()}); QByteArray data = ao_packet.toUtf8(); socket->write(data); diff --git a/src/aoclient.cpp b/src/aoclient.cpp index 7d3a90c..10521af 100644 --- a/src/aoclient.cpp +++ b/src/aoclient.cpp @@ -28,6 +28,7 @@ AOClient::AOClient(Server* p_server, QTcpSocket* p_socket, QObject* parent, int current_area = 0; current_char = ""; remote_ip = p_socket->peerAddress(); + calculateIpid(); is_partial = false; last_wtce_time = 0; last_message = ""; @@ -64,14 +65,20 @@ void AOClient::clientDisconnected() arup(ARUPType::PLAYER_COUNT, true); } if (current_char != "") { - server->areas[current_area]->characters_taken[current_char] = - false; + server->areas[current_area]->characters_taken.removeAll(server->getCharID(current_char)); server->updateCharsTaken(server->areas[current_area]); } + bool update_locks; for (AreaData* area : server->areas) { area->owners.removeAll(id); area->invited.removeAll(id); + if (area->owners.isEmpty() && area->locked != AreaData::FREE) { + area->locked = AreaData::FREE; + update_locks = true; + } } + if (update_locks) + arup(ARUPType::LOCKED, true); arup(ARUPType::CM, true); } @@ -109,8 +116,7 @@ void AOClient::changeArea(int new_area) } if (current_char != "") { - server->areas[current_area]->characters_taken[current_char] = - false; + server->areas[current_area]->characters_taken.removeAll(server->getCharID(current_char)); server->updateCharsTaken(server->areas[current_area]); } server->areas[new_area]->player_count++; @@ -121,23 +127,23 @@ void AOClient::changeArea(int new_area) sendPacket("HP", {"1", QString::number(server->areas[new_area]->def_hp)}); sendPacket("HP", {"2", QString::number(server->areas[new_area]->pro_hp)}); sendPacket("BN", {server->areas[new_area]->background}); - if (server->areas[current_area]->characters_taken[current_char]) { + if (server->areas[current_area]->characters_taken.contains(server->getCharID(current_char))) { server->updateCharsTaken(server->areas[current_area]); current_char = ""; sendPacket("DONE"); } else { - server->areas[current_area]->characters_taken[current_char] = true; + server->areas[current_area]->characters_taken.append(server->getCharID(current_char)); server->updateCharsTaken(server->areas[current_area]); } for (QTimer* timer : server->areas[current_area]->timers) { int timer_id = server->areas[current_area]->timers.indexOf(timer) + 1; if (timer->isActive()) { - sendPacket("TI", {QString::number(timer_id), QString::number(2)}); - sendPacket("TI", {QString::number(timer_id), QString::number(0), QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(timer->remainingTime())))}); + sendPacket("TI", {QString::number(timer_id), "2"}); + sendPacket("TI", {QString::number(timer_id), "0", QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(timer->remainingTime())))}); } else { - sendPacket("TI", {QString::number(timer_id), QString::number(3)}); + sendPacket("TI", {QString::number(timer_id), "3"}); } } sendServerMessage("You moved to area " + server->area_names[current_area]); @@ -145,6 +151,43 @@ void AOClient::changeArea(int new_area) sendServerMessage("Area " + server->area_names[current_area] + " is spectate-only; to chat IC you will need to be invited by the CM."); } +void AOClient::changeCharacter(int char_id) +{ + AreaData* area = server->areas[current_area]; + + if (current_char != "") { + area->characters_taken.removeAll(server->getCharID(current_char)); + } + + if(char_id > server->characters.length()) + return; + + if (char_id >= 0) { + QString char_selected = server->characters[char_id]; + bool taken = area->characters_taken.contains(char_id); + if (taken || char_selected == "") + return; + + area->characters_taken.append(char_id); + current_char = char_selected; + } + else { + current_char = ""; + } + + pos = ""; + + server->updateCharsTaken(area); + sendPacket("PV", {QString::number(id), "CID", QString::number(char_id)}); +} + +void AOClient::changePosition(QString new_pos) +{ + pos = new_pos; + sendServerMessage("Position changed to " + pos + "."); + sendPacket("SP", {pos}); +} + void AOClient::handleCommand(QString command, int argc, QStringList argv) { CommandInfo info = commands.value(command, {false, -1, &AOClient::cmdDefault}); @@ -167,42 +210,38 @@ void AOClient::arup(ARUPType type, bool broadcast) QStringList arup_data; arup_data.append(QString::number(type)); for (AreaData* area : server->areas) { - if (type == ARUPType::PLAYER_COUNT) { - arup_data.append(QString::number(area->player_count)); - } - else if (type == ARUPType::STATUS) { - arup_data.append(area->status); - } - else if (type == ARUPType::CM) { - if (area->owners.isEmpty()) - arup_data.append("FREE"); - else { - QStringList area_owners; - for (int owner_id : area->owners) { - AOClient* owner = server->getClientByID(owner_id); - area_owners.append("[" + QString::number(owner->id) + "] " + owner->current_char); + switch(type) { + case ARUPType::PLAYER_COUNT: { + arup_data.append(QString::number(area->player_count)); + break; + } + case ARUPType::STATUS: { + QString area_status = QVariant::fromValue(area->status).toString().replace("_", "-"); // LOOKING_FOR_PLAYERS to LOOKING-FOR-PLAYERS + arup_data.append(area_status); + break; + } + case ARUPType::CM: { + if (area->owners.isEmpty()) + arup_data.append("FREE"); + else { + QStringList area_owners; + for (int owner_id : area->owners) { + AOClient* owner = server->getClientByID(owner_id); + area_owners.append("[" + QString::number(owner->id) + "] " + owner->current_char); + } + arup_data.append(area_owners.join(", ")); } - arup_data.append(area_owners.join(", ")); + break; + } + case ARUPType::LOCKED: { + QString lock_status = QVariant::fromValue(area->locked).toString(); + arup_data.append(lock_status); + break; + } + default: { + return; } } - else if (type == ARUPType::LOCKED) { - QString lock_status; - switch (area->locked) { - case AreaData::LockStatus::FREE: - lock_status = "FREE"; - break; - case AreaData::LockStatus::LOCKED: - lock_status = "LOCKED"; - break; - case AreaData::LockStatus::SPECTATABLE: - lock_status = "SPECTATABLE"; - break; - default: - break; - } - arup_data.append(lock_status); - } - else return; } if (broadcast) server->broadcast(AOPacket("ARUP", arup_data)); @@ -236,22 +275,17 @@ void AOClient::sendPacket(QString header) sendPacket(AOPacket(header, {})); } -QString AOClient::getHwid() { return hwid; } - -void AOClient::setHwid(QString p_hwid) +void AOClient::calculateIpid() { - // TODO: add support for longer hwids? + // TODO: add support for longer ipids? // This reduces the (fairly high) chance of // birthday paradox issues arising. However, // typing more than 8 characters might be a // bit cumbersome. - hwid = p_hwid; - QCryptographicHash hash( - QCryptographicHash::Md5); // Don't need security, just - // hashing for uniqueness - QString concat_ip_id = remote_ip.toString() + p_hwid; - hash.addData(concat_ip_id.toUtf8()); + QCryptographicHash hash(QCryptographicHash::Md5); // Don't need security, just hashing for uniqueness + + hash.addData(remote_ip.toString().toUtf8()); ipid = hash.result().toHex().right(8); // Use the last 8 characters (4 bytes) } diff --git a/src/area_data.cpp b/src/area_data.cpp index 0036af3..b03500d 100644 --- a/src/area_data.cpp +++ b/src/area_data.cpp @@ -17,22 +17,23 @@ ////////////////////////////////////////////////////////////////////////////////////// #include "include/area_data.h" -AreaData::AreaData(QStringList characters, QString p_name, int p_index) +AreaData::AreaData(QString p_name, int p_index) { - name = p_name; + QStringList name_split = p_name.split(":"); + name_split.removeFirst(); + name = name_split.join(":"); index = p_index; - for (QString cur_char : characters) { - characters_taken.insert(cur_char, false); - } QSettings areas_ini("config/areas.ini", QSettings::IniFormat); areas_ini.beginGroup(p_name); background = areas_ini.value("background", "gs4").toString(); - is_protected = areas_ini.value("protected_area").toBool(); + is_protected = areas_ini.value("protected_area", "false").toBool(); + iniswap_allowed = areas_ini.value("iniswap_allowed", "true").toBool(); bg_locked = areas_ini.value("bg_locked", "false").toBool(); + QString configured_evi_mod = areas_ini.value("evidence_mod", "FFA").toString().toLower(); areas_ini.endGroup(); player_count = 0; locked = FREE; - status = "FREE"; + status = IDLE; def_hp = 10; pro_hp = 10; document = "No document."; @@ -51,4 +52,13 @@ AreaData::AreaData(QStringList characters, QString p_name, int p_index) timers.append(timer3); QTimer* timer4 = new QTimer(); timers.append(timer4); + + if (configured_evi_mod == "cm") + evi_mod = EvidenceMod::CM; + else if (configured_evi_mod == "mod") + evi_mod = EvidenceMod::MOD; + else if (configured_evi_mod == "hiddencm") + evi_mod = EvidenceMod::HIDDEN_CM; + else + evi_mod = EvidenceMod::FFA; } diff --git a/src/commands.cpp b/src/commands.cpp index d7b8173..5e778ee 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -93,33 +93,72 @@ void AOClient::cmdGetArea(int argc, QStringList argv) void AOClient::cmdBan(int argc, QStringList argv) { - QString target_ipid = argv[0]; - QHostAddress ip; - QString hdid; - unsigned long time = QDateTime::currentDateTime().toTime_t(); - QString reason = argv[1]; + QString args_str = argv[1]; + if (argc > 2) { + for (int i = 2; i < argc; i++) + args_str += " " + argv[i]; + } + + DBManager::BanInfo ban; + + QRegularExpression quoteMatcher("['\"](.+?)[\"']"); + QRegularExpressionMatchIterator matches = quoteMatcher.globalMatch(args_str); + QList 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 \"\" \"\""); + return; + } + + ban.reason = unquoted_args.at(0); + if (unquoted_args.length() > 1) + duration = unquoted_args.at(1); + + long long duration_seconds = 0; + if (duration == "perma") + duration_seconds = -2; + else + duration_seconds = parseTime(duration); + + if (duration_seconds == -1) { + sendServerMessage("Invalid time format. Format example: 1h30m"); + return; + } + + ban.duration = duration_seconds; + + ban.ipid = argv[0]; + ban.time = QDateTime::currentDateTime().toSecsSinceEpoch(); bool ban_logged = false; + int kick_counter = 0; if (argc > 2) { for (int i = 2; i < argv.length(); i++) { - reason += " " + argv[i]; + ban.reason += " " + argv[i]; } } - for (AOClient* client : server->clients) { - if (client->getIpid() == target_ipid) { - if (!ban_logged) { - ip = client->remote_ip; - hdid = client->hwid; - server->db_manager->addBan(target_ipid, ip, hdid, time, reason); - sendServerMessage("Banned user with ipid " + target_ipid + " for reason: " + reason); - ban_logged = true; - } - client->sendPacket("KB", {reason}); - client->socket->close(); + for (AOClient* client : server->getClientsByIpid(ban.ipid)) { + if (!ban_logged) { + ban.ip = client->remote_ip; + ban.hdid = client->hwid; + server->db_manager->addBan(ban); + sendServerMessage("Banned user with ipid " + ban.ipid + " for reason: " + ban.reason); + 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")}); + client->socket->close(); + kick_counter++; } + if (kick_counter > 1) + sendServerMessage("Kicked " + QString::number(kick_counter) + " clients with matching ipids."); if (!ban_logged) sendServerMessage("User with ipid not found!"); } @@ -128,7 +167,7 @@ void AOClient::cmdKick(int argc, QStringList argv) { QString target_ipid = argv[0]; QString reason = argv[1]; - bool did_kick = false; + int kick_counter = 0; if (argc > 2) { for (int i = 2; i < argv.length(); i++) { @@ -136,16 +175,14 @@ void AOClient::cmdKick(int argc, QStringList argv) } } - for (AOClient* client : server->clients) { - if (client->getIpid() == target_ipid) { - client->sendPacket("KK", {reason}); - client->socket->close(); - did_kick = true; - } + for (AOClient* client : server->getClientsByIpid(target_ipid)) { + client->sendPacket("KK", {reason}); + client->socket->close(); + kick_counter++; } - if (did_kick) - sendServerMessage("Kicked user with ipid " + target_ipid + " for reason: " + reason); + if (kick_counter > 0) + sendServerMessage("Kicked " + QString::number(kick_counter) + " client(s) with ipid " + target_ipid + " for reason: " + reason); else sendServerMessage("User with ipid not found!"); } @@ -236,6 +273,14 @@ void AOClient::cmdAddUser(int argc, QStringList argv) sendServerMessage("Unable to create user " + argv[0] + ".\nDoes a user with that name already exist?"); } +void AOClient::cmdRemoveUser(int argc, QStringList argv) +{ + if (server->db_manager->deleteUser(argv[0])) + sendServerMessage("Successfully removed user " + argv[0] + "."); + else + sendServerMessage("Unable to remove user " + argv[0] + ".\nDoes it exist?"); +} + void AOClient::cmdListPerms(int argc, QStringList argv) { unsigned long long user_acl = server->db_manager->getACL(moderator_name); @@ -368,8 +413,43 @@ void AOClient::cmdLogout(int argc, QStringList argv) void AOClient::cmdPos(int argc, QStringList argv) { - pos = argv[0]; - sendServerMessage("Position changed to " + pos + "."); + changePosition(argv[0]); + updateEvidenceList(server->areas[current_area]); +} + +void AOClient::cmdForcePos(int argc, QStringList argv) +{ + bool ok; + QList targets; + AreaData* area = server->areas[current_area]; + int target_id = argv[1].toInt(&ok); + int forced_clients = 0; + if (!ok && argv[1] != "*") { + sendServerMessage("That does not look like a valid ID."); + return; + } + else if (ok) { + AOClient* target_client = server->getClientByID(target_id); + if (target_client != nullptr) + targets.append(target_client); + else { + sendServerMessage("Target ID not found!"); + return; + } + } + + else if (argv[1] == "*") { // force all clients in the area + for (AOClient* client : server->clients) { + if (client->current_area == current_area) + targets.append(client); + } + } + for (AOClient* target : targets) { + target->sendServerMessage("Position forcibly changed by CM."); + target->changePosition(argv[0]); + forced_clients++; + } + sendServerMessage("Forced " + QString::number(forced_clients) + " into pos " + argv[0] + "."); } void AOClient::cmdG(int argc, QStringList argv) @@ -377,7 +457,10 @@ void AOClient::cmdG(int argc, QStringList argv) QString sender_name = ooc_name; QString sender_area = server->area_names.value(current_area); QString sender_message = argv.join(" "); - server->broadcast(AOPacket("CT", {"[" + sender_area + "]" + sender_name, sender_message})); + for (AOClient* client : server->clients) { + if (client->global_enabled) + client->sendPacket("CT", {"[G][" + sender_area + "]" + sender_name, sender_message}); + } return; } @@ -471,8 +554,13 @@ void AOClient::cmdUnCM(int argc, QStringList argv) area->invited.removeAll(id); sendServerMessage("You are no longer CM in this area."); arup(ARUPType::CM, true); - if (area->owners.isEmpty()) + if (area->owners.isEmpty()) { area->invited.clear(); + if (area->locked != AreaData::FREE) { + area->locked = AreaData::FREE; + arup(ARUPType::LOCKED, true); + } + } } void AOClient::cmdInvite(int argc, QStringList argv) @@ -484,6 +572,10 @@ void AOClient::cmdInvite(int argc, QStringList argv) sendServerMessage("That does not look like a valid ID."); return; } + else if (server->getClientByID(invited_id) == nullptr) { + sendServerMessage("No client with that ID found."); + return; + } else if (area->invited.contains(invited_id)) { sendServerMessage("That ID is already on the invite list."); return; @@ -501,6 +593,10 @@ void AOClient::cmdUnInvite(int argc, QStringList argv) sendServerMessage("That does not look like a valid ID."); return; } + else if (server->getClientByID(uninvited_id) == nullptr) { + sendServerMessage("No client with that ID found."); + return; + } else if (area->owners.contains(uninvited_id)) { sendServerMessage("You cannot uninvite a CM!"); return; @@ -615,32 +711,329 @@ void AOClient::cmdTimer(int argc, QStringList argv) requested_timer->setInterval(QTime(0,0).msecsTo(requested_time)); requested_timer->start(); sendServerMessage("Set timer " + QString::number(timer_id) + " to " + argv[1] + "."); - sendPacket("TI", {QString::number(timer_id), QString::number(2)}); - sendPacket("TI", {QString::number(timer_id), QString::number(0), QString::number(QTime(0,0).msecsTo(requested_time))}); + sendPacket("TI", {QString::number(timer_id), "2"}); // Show the timer + sendPacket("TI", {QString::number(timer_id), "0", QString::number(QTime(0,0).msecsTo(requested_time))}); return; } else { if (argv[1] == "start") { requested_timer->start(); sendServerMessage("Started timer " + QString::number(timer_id) + "."); - sendPacket("TI", {QString::number(timer_id), QString::number(2)}); - sendPacket("TI", {QString::number(timer_id), QString::number(0), QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(requested_timer->remainingTime())))}); + sendPacket("TI", {QString::number(timer_id), "2"}); // Show the timer + sendPacket("TI", {QString::number(timer_id), "0", QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(requested_timer->remainingTime())))}); } else if (argv[1] == "pause" || argv[1] == "stop") { requested_timer->setInterval(requested_timer->remainingTime()); requested_timer->stop(); sendServerMessage("Stopped timer " + QString::number(timer_id) + "."); - sendPacket("TI", {QString::number(timer_id), QString::number(1), QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(requested_timer->interval())))}); + sendPacket("TI", {QString::number(timer_id), "1", QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(requested_timer->interval())))}); } else if (argv[1] == "hide" || argv[1] == "unset") { requested_timer->setInterval(0); requested_timer->stop(); sendServerMessage("Hid timer " + QString::number(timer_id) + "."); - sendPacket("TI", {QString::number(timer_id), QString::number(3)}); + sendPacket("TI", {QString::number(timer_id), "3"}); // Hide the timer } } } +void AOClient::cmdEvidenceMod(int argc, QStringList argv) +{ + AreaData* area = server->areas[current_area]; + argv[0] = argv[0].toLower(); + if (argv[0] == "cm") + area->evi_mod = AreaData::EvidenceMod::CM; + else if (argv[0] == "mod") + area->evi_mod = AreaData::EvidenceMod::MOD; + else if (argv[0] == "hiddencm") + area->evi_mod = AreaData::EvidenceMod::HIDDEN_CM; + else if (argv[0] == "ffa") + area->evi_mod = AreaData::EvidenceMod::FFA; + else { + sendServerMessage("Invalid evidence mod."); + return; + } + sendServerMessage("Changed evidence mod."); + + // Resend evidence lists to everyone in the area + sendEvidenceList(area); +} + +void AOClient::cmdArea(int argc, QStringList argv) +{ + bool ok; + int new_area = argv[0].toInt(&ok); + if (!ok || new_area >= server->areas.size() || new_area < 0) { + sendServerMessage("That does not look like a valid area ID."); + return; + } + changeArea(new_area); +} + +void AOClient::cmdPlay(int argc, QStringList argv) +{ + AreaData* area = server->areas[current_area]; + QString song = argv.join(" "); + area->current_music = song; + area->music_played_by = showname; + AOPacket music_change("MC", {song, QString::number(server->getCharID(current_char)), showname, "1", "0"}); + server->broadcast(music_change, current_area); +} + +void AOClient::cmdAreaKick(int argc, QStringList argv) +{ + bool ok; + int idx = argv[0].toInt(&ok); + if (!ok) { + sendServerMessage("That does not look like a valid ID."); + return; + } + AOClient* client_to_kick = server->getClientByID(idx); + if (client_to_kick == nullptr) { + sendServerMessage("No client with that ID found."); + return; + } + client_to_kick->changeArea(0); + sendServerMessage("Client " + argv[0] + " kicked back to area 0."); +} + +void AOClient::cmdSwitch(int argc, QStringList argv) +{ + int char_id = server->getCharID(argv.join(" ")); + if (char_id == -1) { + sendServerMessage("That does not look like a valid character."); + return; + } + changeCharacter(char_id); +} + +void AOClient::cmdRandomChar(int argc, QStringList argv) +{ + int char_id = genRand(0, server->characters.size() - 1); + changeCharacter(char_id); +} + +void AOClient::cmdToggleGlobal(int argc, QStringList argv) +{ + global_enabled = !global_enabled; + QString str_en = global_enabled ? "shown" : "hidden"; + sendServerMessage("Global chat set to " + str_en); +} + +void AOClient::cmdMods(int argc, QStringList argv) +{ + QStringList entries; + QSettings config("config/config.ini", QSettings::IniFormat); + config.beginGroup("Options"); + QString auth_type = config.value("auth", "simple").toString(); + int online_count = 0; + for (AOClient* client : server->clients) { + if (client->authenticated) { + entries << "---"; + if (auth_type != "simple") + entries << "Moderator: " + client->moderator_name; + entries << "OOC name: " + client->ooc_name; + entries << "ID: " + QString::number(client->id); + entries << "Area: " + QString::number(client->current_area); + entries << "Character: " + client->current_char; + online_count++; + } + } + entries << "---"; + entries << "Total online: " << QString::number(online_count); + sendServerMessage(entries.join("\n")); +} + +void AOClient::cmdHelp(int argc, QStringList argv) +{ + QStringList entries; + entries << "Allowed commands:"; + QMap::const_iterator i; + for (i = commands.constBegin(); i!= commands.constEnd(); ++i) { + CommandInfo info = i.value(); + if (checkAuth(info.acl_mask)) { // if we are allowed to use this command + entries << "/" + i.key(); + } + } + sendServerMessage(entries.join("\n")); +} + +void AOClient::cmdStatus(int argc, QStringList argv) +{ + AreaData* area = server->areas[current_area]; + QString arg = argv[0].toLower(); + if (arg == "idle") + area->status = AreaData::IDLE; + else if (arg == "rp") + area->status = AreaData::RP; + else if (arg == "casing") + area->status = AreaData::CASING; + else if (arg == "looking-for-players" || arg == "lfp") + area->status = AreaData::LOOKING_FOR_PLAYERS; + else if (arg == "recess") + area->status = AreaData::RECESS; + else if (arg == "gaming") + area->status = AreaData::GAMING; + else { + sendServerMessage("That does not look like a valid status. Valid statuses are idle, rp, casing, lfp, recess, gaming"); + return; + } + arup(ARUPType::STATUS, true); +} + +void AOClient::cmdCurrentMusic(int argc, QStringList argv) +{ + AreaData* area = server->areas[current_area]; + if (area->current_music != "" && area->current_music != "~stop.mp3") // dummy track for stopping music + sendServerMessage("The current song is " + area->current_music + " played by " + area->music_played_by); + else + sendServerMessage("There is no music playing."); +} + +void AOClient::cmdPM(int arc, QStringList argv) +{ + bool ok; + int target_id = argv.takeFirst().toInt(&ok); // using takeFirst removes the ID from our list of arguments... + if (!ok) { + sendServerMessage("That does not look like a valid ID."); + return; + } + AOClient* target_client = server->getClientByID(target_id); + if (target_client == nullptr) { + sendServerMessage("No client with that ID found."); + return; + } + QString message = argv.join(" "); //...which means it will not end up as part of the message + target_client->sendServerMessage("Message from " + ooc_name + " (" + QString::number(id) + "): " + message); +} + +void AOClient::cmdMOTD(int argc, QStringList argv) +{ + if (argc == 0) { + sendServerMessage("=== MOTD ===\r\n" + server->MOTD + "\r\n============="); + } + else if (argc > 0) { + if (checkAuth(ACLFlags.value("MOTD"))) { + QString MOTD = argv.join(" "); + server->MOTD = MOTD; + sendServerMessage("MOTD has been changed."); + } + else { + sendServerMessage("You do not have permission to change the MOTD"); + } + } +} + +void AOClient::cmdAnnounce(int argc, QStringList argv) +{ + sendServerBroadcast("=== Announcement ===\r\n" + argv.join(" ") + "\r\n============="); +} + +void AOClient::cmdM(int argc, QStringList argv) +{ + QString sender_name = ooc_name; + QString sender_message = argv.join(" "); + for (AOClient* client : server->clients) { + if (client->checkAuth(ACLFlags.value("MODCHAT"))) + client->sendPacket("CT", {"[M]" + sender_name, sender_message}); + } + return; +} + +void AOClient::cmdGM(int argc, QStringList argv) +{ + QString sender_name = ooc_name; + QString sender_area = server->area_names.value(current_area); + QString sender_message = argv.join(" "); + for (AOClient* client : server->clients) { + if (client->global_enabled) { + client->sendPacket("CT", {"[G][" + sender_area + "]" + "["+sender_name+"][M]", sender_message}); + } + } +} + +void AOClient::cmdMute(int argc, QStringList argv) +{ + bool conv_ok = false; + int uid = argv[0].toInt(&conv_ok); + if (!conv_ok) { + sendServerMessage("Invalid user ID."); + return; + } + + if (server->getClientByID(uid)->is_muted) + sendServerMessage("That player is already muted!"); + else + sendServerMessage("Muted player."); + server->getClientByID(uid)->is_muted = true; +} + +void AOClient::cmdUnmute(int argc, QStringList argv) +{ + bool conv_ok = false; + int uid = argv[0].toInt(&conv_ok); + if (!conv_ok) { + sendServerMessage("Invalid user ID."); + return; + } + + if (!server->getClientByID(uid)->is_muted) + sendServerMessage("That player is already unmuted!"); + else + sendServerMessage("Unmuted player."); + server->getClientByID(uid)->is_muted = false; +} + +void AOClient::cmdBans(int argc, QStringList argv) +{ + QStringList recent_bans; + recent_bans << "Last 5 bans:"; + recent_bans << "-----"; + for (DBManager::BanInfo ban : server->db_manager->getRecentBans()) { + QString banned_until; + 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 << "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 << "Ban lasts until: " + banned_until; + recent_bans << "-----"; + } + sendServerMessage(recent_bans.join("\n")); +} + +void AOClient::cmdUnBan(int argc, QStringList argv) +{ + bool ok; + int target_ban = argv[0].toInt(&ok); + if (!ok) { + sendServerMessage("Invalid ban ID."); + return; + } + else if (server->db_manager->invalidateBan(target_ban)) + sendServerMessage("Successfully invalidated ban " + argv[0] + "."); + else + sendServerMessage("Couldn't invalidate ban " + argv[0] + ", are you sure it exists?"); +} + +void AOClient::cmdSubTheme(int argc, QStringList argv) +{ + QString subtheme = argv.join(" "); + for (AOClient* client : server->clients) { + if (client->current_area == current_area) + client->sendPacket("ST", {subtheme, "1"}); + } + sendServerMessageArea("Subtheme was set to " + subtheme); +} + +void AOClient::cmdAbout(int argc, QStringList argv) +{ + sendPacket("CT", {"The akashi dev team", "Thank you for using akashi! Made with love by scatterflower, with help from in1tiate and Salanto. akashi " + QCoreApplication::applicationVersion()}); +} + QStringList AOClient::buildAreaList(int area_idx) { QStringList entries; @@ -658,7 +1051,7 @@ QStringList AOClient::buildAreaList(int area_idx) default: break; } - entries.append("[" + QString::number(area->player_count) + " users][" + area->status + "]"); + entries.append("[" + QString::number(area->player_count) + " users][" + QVariant::fromValue(area->status).toString().replace("_", "-") + "]"); for (AOClient* client : server->clients) { if (client->current_area == area_idx && client->joined) { QString char_entry = "[" + QString::number(client->id) + "] " + client->current_char; @@ -757,3 +1150,45 @@ QString AOClient::getAreaTimer(int area_idx, QTimer* timer) } } +long long AOClient::parseTime(QString input) +{ + QRegularExpression regex("(?:(?:(?.*?)y)*(?:(?.*?)w)*(?:(?.*?)d)*(?:(?
.*?)h)*(?:(?.*?)m)*(?:(?.*?)s)*)"); + QRegularExpressionMatch match = regex.match(input); + QString str_year, str_week, str_hour, str_day, str_minute, str_second; + int year, week, day, hour, minute, second; + + str_year = match.captured("year"); + str_week = match.captured("week"); + str_day = match.captured("day"); + str_hour = match.captured("hr"); + str_minute = match.captured("min"); + str_second = match.captured("sec"); + + bool is_well_formed = false; + QString concat_str(str_year + str_week + str_day + str_hour + str_minute + str_second); + concat_str.toInt(&is_well_formed); + + if (!is_well_formed) { + return -1; + } + + year = str_year.toInt(); + week = str_week.toInt(); + day = str_day.toInt(); + hour = str_hour.toInt(); + minute = str_minute.toInt(); + second = str_second.toInt(); + + long long total = 0; + total += 31622400 * year; + total += 604800 * week; + total += 86400 * day; + total += 3600 * hour; + total += 60 * minute; + total += second; + + if (total < 0) + return -1; + + return total; +} diff --git a/src/config_manager.cpp b/src/config_manager.cpp index 7f0f93a..7364d9d 100644 --- a/src/config_manager.cpp +++ b/src/config_manager.cpp @@ -139,8 +139,8 @@ bool ConfigManager::loadServerSettings(server_settings* settings) config.value("port", "27016").toInt(&port_conversion_success); settings->ws_port = config.value("webao_port", "27017").toInt(&ws_port_conversion_success); - settings->local_port = - config.value("port", "27016").toInt(&local_port_conversion_success); + settings->ms_port = + config.value("ms_port", "27016").toInt(&local_port_conversion_success); settings->name = config.value("server_name", "My First Server").toString(); settings->description = config.value("server_description", "This is my flashy new server") diff --git a/src/db_manager.cpp b/src/db_manager.cpp index f23f20e..bd2ba32 100644 --- a/src/db_manager.cpp +++ b/src/db_manager.cpp @@ -24,32 +24,52 @@ DBManager::DBManager() : db.setDatabaseName("config/akashi.db"); if (!db.open()) 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, 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, 'DURATION' INTEGER, 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 DBManager::isIPBanned(QHostAddress ip) { QSqlQuery query; - query.prepare("SELECT ID FROM BANS WHERE IP = ?"); + query.prepare("SELECT TIME FROM BANS WHERE IP = ? ORDER BY TIME DESC"); query.addBindValue(ip.toString()); query.exec(); - return query.first(); + if (query.first()) { + long long duration = getBanDuration(ip); + long long ban_time = query.value(0).toLongLong(); + if (duration == -2) + return true; + long long current_time = QDateTime::currentDateTime().toSecsSinceEpoch(); + if (ban_time + duration > current_time) + return true; + else return false; + } + else return false; } bool DBManager::isHDIDBanned(QString hdid) { QSqlQuery query; - query.prepare("SELECT ID FROM BANS WHERE HDID = ?"); + query.prepare("SELECT TIME FROM BANS WHERE HDID = ? ORDER BY TIME DESC"); query.addBindValue(hdid); query.exec(); - return query.first(); + if (query.first()) { + long long duration = getBanDuration(hdid); + long long ban_time = query.value(0).toLongLong(); + if (duration == -2) + return true; + long long current_time = QDateTime::currentDateTime().toSecsSinceEpoch(); + if (ban_time + duration > current_time) + return true; + else return false; + } + else return false; } QString DBManager::getBanReason(QHostAddress ip) { QSqlQuery query; - query.prepare("SELECT REASON FROM BANS WHERE IP = ?"); + query.prepare("SELECT REASON FROM BANS WHERE IP = ? ORDER BY TIME DESC"); query.addBindValue(ip.toString()); query.exec(); if (query.first()) { @@ -63,7 +83,7 @@ QString DBManager::getBanReason(QHostAddress ip) QString DBManager::getBanReason(QString hdid) { QSqlQuery query; - query.prepare("SELECT REASON FROM BANS WHERE HDID = ?"); + query.prepare("SELECT REASON FROM BANS WHERE HDID = ? ORDER BY TIME DESC"); query.addBindValue(hdid); query.exec(); if (query.first()) { @@ -74,19 +94,116 @@ QString DBManager::getBanReason(QString hdid) } } -void DBManager::addBan(QString ipid, QHostAddress ip, QString hdid, unsigned long time, QString reason) +long long DBManager::getBanDuration(QString hdid) { QSqlQuery query; - query.prepare("INSERT INTO BANS(IPID, HDID, IP, TIME, REASON) VALUES(?, ?, ?, ?, ?)"); - query.addBindValue(ipid); + query.prepare("SELECT DURATION FROM BANS WHERE HDID = ? ORDER BY TIME DESC"); query.addBindValue(hdid); + query.exec(); + if (query.first()) { + return query.value(0).toLongLong(); + } + else { + return -1; + } +} + +long long DBManager::getBanDuration(QHostAddress ip) +{ + QSqlQuery query; + query.prepare("SELECT DURATION FROM BANS WHERE IP = ? ORDER BY TIME DESC"); query.addBindValue(ip.toString()); - query.addBindValue(QString::number(time)); - query.addBindValue(reason); + query.exec(); + if (query.first()) { + return query.value(0).toLongLong(); + } + else { + return -1; + } +} + + +int DBManager::getBanID(QString hdid) +{ + QSqlQuery query; + query.prepare("SELECT ID FROM BANS WHERE HDID = ?"); + query.addBindValue(hdid); + query.exec(); + if (query.first()) { + return query.value(0).toInt(); + } + else { + return -1; + } +} + + +int DBManager::getBanID(QHostAddress ip) +{ + QSqlQuery query; + query.prepare("SELECT ID FROM BANS WHERE IP = ?"); + query.addBindValue(ip.toString()); + query.exec(); + if (query.first()) { + return query.value(0).toInt(); + } + else { + return -1; + } +} + +QList DBManager::getRecentBans() +{ + QList return_list; + QSqlQuery query; + query.prepare("SELECT TOP(5) * FROM BANS ORDER BY TIME DESC"); + 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(); + return_list.append(ban); + } + std::reverse(return_list.begin(), return_list.end()); + return return_list; +} + +void DBManager::addBan(BanInfo ban) +{ + QSqlQuery query; + query.prepare("INSERT INTO BANS(IPID, HDID, IP, TIME, REASON, DURATION) VALUES(?, ?, ?, ?, ?, ?)"); + query.addBindValue(ban.ipid); + query.addBindValue(ban.hdid); + query.addBindValue(ban.ip.toString()); + query.addBindValue(QString::number(ban.time)); + query.addBindValue(ban.reason); + query.addBindValue(ban.duration); if (!query.exec()) qDebug() << "SQL Error:" << query.lastError().text(); } +bool DBManager::invalidateBan(int id) +{ + QSqlQuery ban_exists; + ban_exists.prepare("SELECT DURATION FROM bans WHERE ID = ?"); + ban_exists.addBindValue(id); + ban_exists.exec(); + + if (ban_exists.first()) + return false; + + QSqlQuery query; + query.prepare("UPDATE bans SET DURATION = 0 WHERE ID = ?"); + query.addBindValue(id); + query.exec(); + return true; +} + bool DBManager::createUser(QString username, QString salt, QString password, unsigned long long acl) { QSqlQuery username_exists; @@ -115,6 +232,23 @@ bool DBManager::createUser(QString username, QString salt, QString password, uns return true; } +bool DBManager::deleteUser(QString username) +{ + QSqlQuery username_exists; + username_exists.prepare("SELECT ACL FROM users WHERE USERNAME = ?"); + username_exists.addBindValue(username); + username_exists.exec(); + + if (username_exists.first()) + return false; + + QSqlQuery query; + query.prepare("DELETE FROM users WHERE USERNAME = ?"); + username_exists.addBindValue(username); + username_exists.exec(); + return true; +} + unsigned long long DBManager::getACL(QString moderator_name) { if (moderator_name == "") diff --git a/src/main.cpp b/src/main.cpp index 630965b..8560141 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,7 +36,7 @@ int main(int argc, char* argv[]) { QCoreApplication app(argc, argv); QCoreApplication::setApplicationName("akashi"); - QCoreApplication::setApplicationVersion("0.0.1"); + QCoreApplication::setApplicationVersion("apricot r2 prerelease"); std::atexit(cleanup); ConfigManager config_manager; @@ -55,8 +55,8 @@ int main(int argc, char* argv[]) else { if (settings.advertise_server) { advertiser = - new Advertiser(settings.ms_ip, settings.port, - settings.ws_port, settings.local_port, + new Advertiser(settings.ms_ip, settings.ms_port, + settings.ws_port, settings.port, settings.name, settings.description); advertiser->contactMasterServer(); } diff --git a/src/packets.cpp b/src/packets.cpp index c20b79e..80e89ce 100644 --- a/src/packets.cpp +++ b/src/packets.cpp @@ -26,9 +26,9 @@ 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->db_manager->isHDIDBanned(getHwid())) { - sendPacket("BD", {server->db_manager->getBanReason(getHwid())}); + hwid = argv[0]; + if(server->db_manager->isHDIDBanned(hwid)) { + sendPacket("BD", {server->db_manager->getBanReason(hwid)}); socket->close(); return; } @@ -54,6 +54,16 @@ void AOClient::pktSoftwareId(AreaData* area, int argc, QStringList argv, AOPacke "y_offset", "expanded_desk_mods", "auth_packet" }; + + version.string = argv[1]; + QRegularExpression rx("\\b(\\d+)\\.(\\d+)\\.(\\d+)\\b"); // matches X.X.X (e.g. 2.9.0, 2.4.10, etc.) + QRegularExpressionMatch match = rx.match(version.string); + if (match.hasMatch()) { + version.release = match.captured(1).toInt(); + version.major = match.captured(2).toInt(); + version.minor = match.captured(3).toInt(); + } + sendPacket("PN", {QString::number(server->player_count), max_players}); sendPacket("FL", feature_list); } @@ -78,7 +88,7 @@ void AOClient::pktRequestMusic(AreaData* area, int argc, QStringList argv, AOPac void AOClient::pktLoadingDone(AreaData* area, int argc, QStringList argv, AOPacket packet) { - if (getHwid() == "") { + if (hwid == "") { // No early connecting! socket->close(); return; @@ -92,16 +102,37 @@ void AOClient::pktLoadingDone(AreaData* area, int argc, QStringList argv, AOPack area->player_count++; joined = true; server->updateCharsTaken(area); - fullArup(); // Give client all the area data + arup(ARUPType::PLAYER_COUNT, true); // Tell everyone there is a new player sendEvidenceList(area); sendPacket("HP", {"1", QString::number(area->def_hp)}); sendPacket("HP", {"2", QString::number(area->pro_hp)}); sendPacket("FA", server->area_names); - sendPacket("BN", {area->background}); sendPacket("OPPASS", {"DEADBEEF"}); sendPacket("DONE"); + sendPacket("BN", {area->background}); + + sendServerMessage("=== MOTD ===\r\n" + server->MOTD + "\r\n============="); + + fullArup(); // Give client all the area data + if (server->timer->isActive()) { + sendPacket("TI", {"0", "2"}); + sendPacket("TI", {"0", "0", QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(server->timer->remainingTime())))}); + } + else { + sendPacket("TI", {"0", "3"}); + } + for (QTimer* timer : area->timers) { + int timer_id = area->timers.indexOf(timer) + 1; + if (timer->isActive()) { + sendPacket("TI", {QString::number(timer_id), "2"}); + sendPacket("TI", {QString::number(timer_id), "0", QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(timer->remainingTime())))}); + } + else { + sendPacket("TI", {QString::number(timer_id), "3"}); + } + } } void AOClient::pktCharPassword(AreaData* area, int argc, QStringList argv, AOPacket packet) @@ -118,52 +149,16 @@ void AOClient::pktSelectChar(AreaData* area, int argc, QStringList argv, AOPacke return; } - if (current_char != "") { - area->characters_taken[current_char] = false; - } - - if(char_id > server->characters.length()) - return; - - if (char_id >= 0) { - QString char_selected = server->characters[char_id]; - bool taken = area->characters_taken.value(char_selected); - if (taken || char_selected == "") - return; - - area->characters_taken[char_selected] = true; - current_char = char_selected; - } - else { - current_char = ""; - } - - pos = ""; - - server->updateCharsTaken(area); - sendPacket("PV", {QString::number(id), "CID", argv[1]}); - fullArup(); - if (server->timer->isActive()) { - sendPacket("TI", {QString::number(0), QString::number(2)}); - sendPacket("TI", {QString::number(0), QString::number(0), QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(server->timer->remainingTime())))}); - } - else { - sendPacket("TI", {QString::number(0), QString::number(3)}); - } - for (QTimer* timer : area->timers) { - int timer_id = area->timers.indexOf(timer) + 1; - if (timer->isActive()) { - sendPacket("TI", {QString::number(timer_id), QString::number(2)}); - sendPacket("TI", {QString::number(timer_id), QString::number(0), QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(timer->remainingTime())))}); - } - else { - sendPacket("TI", {QString::number(timer_id), QString::number(3)}); - } - } + changeCharacter(char_id); } void AOClient::pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket packet) { + if (is_muted) { + sendServerMessage("You cannot speak while muted."); + return; + } + AOPacket validated_packet = validateIcPacket(packet); if (validated_packet.header == "INVALID") return; @@ -177,7 +172,10 @@ void AOClient::pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket pa void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket packet) { - ooc_name = dezalgo(argv[0]); + ooc_name = dezalgo(argv[0]).replace(QRegExp("\\[|\\]|\\{|\\}|\\#|\\$|\\%|\\&"), ""); // no fucky wucky shit here + if (ooc_name.isEmpty() || ooc_name == server->getServerName()) // impersonation & empty name protection + return; + QString message = dezalgo(argv[1]); AOPacket final_packet("CT", {ooc_name, message, "0"}); if(message.at(0) == '/') { @@ -214,9 +212,21 @@ void AOClient::pktChangeMusic(AreaData* area, int argc, QStringList argv, AOPack QString argument = argv[0]; for (QString song : server->music_list) { - if (song == argument) { + if (song == argument || song == "~stop.mp3") { // ~stop.mp3 is a dummy track used by 2.9+ // We have a song here - AOPacket music_change("MC", {song, argv[1], argv[2], "1", "0", argv[3]}); + QString effects; + if (argc >= 4) + effects = argv[3]; + else + effects = "0"; + QString final_song; + if (!argument.contains(".")) + final_song = "~stop.mp3"; + else + final_song = argument; + AOPacket music_change("MC", {final_song, argv[1], showname, "1", "0", effects}); + area->current_music = final_song; + area->music_played_by = showname; server->broadcast(music_change, current_area); return; } @@ -265,6 +275,7 @@ void AOClient::pktWebSocketIp(AreaData* area, int argc, QStringList argv, AOPack qDebug() << "ws ip set to" << argv[0]; #endif remote_ip = QHostAddress(argv[0]); + calculateIpid(); } } @@ -280,6 +291,8 @@ void AOClient::pktModCall(AreaData* area, int argc, QStringList argv, AOPacket p void AOClient::pktAddEvidence(AreaData* area, int argc, QStringList argv, AOPacket packet) { + if (!checkEvidenceAccess(area)) + return; AreaData::Evidence evi = {argv[0], argv[1], argv[2]}; area->evidence.append(evi); sendEvidenceList(area); @@ -287,9 +300,11 @@ void AOClient::pktAddEvidence(AreaData* area, int argc, QStringList argv, AOPack void AOClient::pktRemoveEvidence(AreaData* area, int argc, QStringList argv, AOPacket packet) { + if (!checkEvidenceAccess(area)) + return; bool is_int = false; int idx = argv[0].toInt(&is_int); - if (is_int) { + if (is_int && idx <= area->evidence.size() && idx >= 0) { area->evidence.removeAt(idx); } sendEvidenceList(area); @@ -297,28 +312,49 @@ void AOClient::pktRemoveEvidence(AreaData* area, int argc, QStringList argv, AOP void AOClient::pktEditEvidence(AreaData* area, int argc, QStringList argv, AOPacket packet) { + if (!checkEvidenceAccess(area)) + return; bool is_int = false; int idx = argv[0].toInt(&is_int); AreaData::Evidence evi = {argv[1], argv[2], argv[3]}; - if (is_int) { + if (is_int && idx <= area->evidence.size() && idx >= 0) { area->evidence.replace(idx, evi); } sendEvidenceList(area); } void AOClient::sendEvidenceList(AreaData* area) +{ + for (AOClient* client : server->clients) { + if (client->current_area == current_area) + client->updateEvidenceList(area); + } +} + +void AOClient::updateEvidenceList(AreaData* area) { QStringList evidence_list; QString evidence_format("%1&%2&%3"); for (AreaData::Evidence evidence : area->evidence) { + if (!checkAuth(ACLFlags.value("CM")) && area->evi_mod == AreaData::EvidenceMod::HIDDEN_CM) { + QRegularExpression regex(""); + QRegularExpressionMatch match = regex.match(evidence.description); + if (match.hasMatch()) { + QStringList owners = match.captured(1).split(","); + if (!owners.contains("all", Qt::CaseSensitivity::CaseInsensitive) && !owners.contains(pos, Qt::CaseSensitivity::CaseInsensitive)) { + continue; + } + } + // no match = show it to all + } evidence_list.append(evidence_format - .arg(evidence.name) - .arg(evidence.description) - .arg(evidence.image)); + .arg(evidence.name) + .arg(evidence.description) + .arg(evidence.image)); } - server->broadcast(AOPacket("LE", evidence_list), current_area); + sendPacket(AOPacket("LE", evidence_list)); } AOPacket AOClient::validateIcPacket(AOPacket packet) @@ -362,10 +398,10 @@ AOPacket AOClient::validateIcPacket(AOPacket packet) if (current_char != incoming_args[2].toString()) { // Selected char is different from supplied folder name // This means the user is INI-swapped - // TODO: ini swap locking - // if no iniswap allowed then - // if (!server->characters.contains(incoming_args[2].toString())) - // return invalid; + if (!area->iniswap_allowed) { + if (!server->characters.contains(incoming_args[2].toString())) + return invalid; + } qDebug() << "INI swap detected from " << getIpid(); } args.append(incoming_args[2].toString()); @@ -385,6 +421,10 @@ AOPacket AOClient::validateIcPacket(AOPacket packet) // side // this is validated clientside so w/e args.append(incoming_args[5].toString()); + if (pos != incoming_args[5].toString()) { + pos = incoming_args[5].toString(); + updateEvidenceList(server->areas[current_area]); + } // sfx name args.append(incoming_args[6].toString()); @@ -453,8 +493,9 @@ AOPacket AOClient::validateIcPacket(AOPacket packet) // 2.6 packet extensions if (incoming_args.length() > 15) { // showname - QString showname = dezalgo(incoming_args[15].toString().trimmed()); - args.append(showname); + QString incoming_showname = dezalgo(incoming_args[15].toString().trimmed()); + args.append(incoming_showname); + showname = incoming_showname; // other char id // things get a bit hairy here @@ -489,8 +530,17 @@ AOPacket AOClient::validateIcPacket(AOPacket packet) // self offset offset = incoming_args[17].toString(); - args.append(offset); - args.append(other_offset); + // versions 2.6-2.8 cannot validate y-offset so we send them just the x-offset + if ((version.release == 2) && (version.major == 6 || version.major == 7 || version.major == 8)) { + QString x_offset = offset.split("&")[0]; + args.append(x_offset); + QString other_x_offset = other_offset.split("&")[0]; + args.append(other_x_offset); + } + else { + args.append(offset); + args.append(other_offset); + } args.append(other_flip); // noninterrupting preanim @@ -549,3 +599,18 @@ QString AOClient::dezalgo(QString p_text) QString filtered = p_text.replace(rxp, ""); return filtered; } + +bool AOClient::checkEvidenceAccess(AreaData *area) +{ + switch(area->evi_mod) { + case AreaData::EvidenceMod::FFA: + return true; + case AreaData::EvidenceMod::CM: + case AreaData::EvidenceMod::HIDDEN_CM: + return checkAuth(ACLFlags.value("CM")); + case AreaData::EvidenceMod::MOD: + return authenticated; + default: + return false; + } +} diff --git a/src/server.cpp b/src/server.cpp index b9acff4..17c6efc 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -51,6 +51,8 @@ void Server::start() qDebug() << "Server listening on" << port; } + MOTD = config.value("motd","MOTD is not set.").toString(); + proxy = new WSProxy(port, ws_port, this); if(ws_port != -1) proxy->start(); @@ -79,10 +81,20 @@ void Server::start() bg_file.close(); QSettings areas_ini("config/areas.ini", QSettings::IniFormat); - area_names = areas_ini.childGroups(); - for (int i = 0; i < area_names.length(); i++) { - QString area_name = area_names[i]; - areas.insert(i, new AreaData(characters, area_name, i)); + area_names = areas_ini.childGroups(); // invisibly does a lexicographical sort, because Qt is great like that + std::sort(area_names.begin(), area_names.end(), [] (const QString &a, const QString &b) {return a.split(":")[0].toInt() < b.split(":")[0].toInt();}); + QStringList sanitized_area_names; + QStringList raw_area_names = area_names; + for (QString area_name : area_names) { + QStringList name_split = area_name.split(":"); + name_split.removeFirst(); + QString area_name_sanitized = name_split.join(":"); + sanitized_area_names.append(area_name_sanitized); + } + area_names = sanitized_area_names; + for (int i = 0; i < raw_area_names.length(); i++) { + QString area_name = raw_area_names[i]; + areas.insert(i, new AreaData(area_name, i)); } } @@ -130,8 +142,8 @@ void Server::clientConnected() void Server::updateCharsTaken(AreaData* area) { QStringList chars_taken; - for (QString cur_char : area->characters_taken.keys()) { - chars_taken.append(area->characters_taken.value(cur_char) + for (QString cur_char : characters) { + chars_taken.append(area->characters_taken.contains(getCharID(cur_char)) ? QStringLiteral("-1") : QStringLiteral("0")); } @@ -173,13 +185,14 @@ int Server::getDiceValue(QString value_type) return value; } -AOClient* Server::getClient(QString ipid) +QList Server::getClientsByIpid(QString ipid) { + QList return_clients; for (AOClient* client : clients) { if (client->getIpid() == ipid) - return client; + return_clients.append(client); } - return nullptr; + return return_clients; } AOClient* Server::getClientByID(int id) @@ -191,6 +204,16 @@ AOClient* Server::getClientByID(int id) return nullptr; } +int Server::getCharID(QString char_name) +{ + for (QString character : characters) { + if (character.toLower() == char_name.toLower()) { + return characters.indexOf(QRegExp(character, Qt::CaseInsensitive)); + } + } + return -1; // character does not exist +} + Server::~Server() { for (AOClient* client : clients) {