add validation for 2.4.x packets, and a lot more

This commit is contained in:
scatterflower 2020-10-03 14:57:30 -05:00
parent 6503b514d4
commit fde0c4b78f
9 changed files with 200 additions and 115 deletions

View File

@ -15,6 +15,8 @@ DEFINES += QT_DEPRECATED_WARNINGS
# You can also select to disable deprecated APIs only up to a certain version of Qt. # You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
QMAKE_CXXFLAGS_WARN_OFF -= -Wunused-parameter
DESTDIR = $$PWD/bin DESTDIR = $$PWD/bin
OBJECTS_DIR = $$PWD/build OBJECTS_DIR = $$PWD/build
MOC_DIR = $$PWD/build MOC_DIR = $$PWD/build
@ -28,7 +30,6 @@ SOURCES += src/advertiser.cpp \
src/commands.cpp \ src/commands.cpp \
src/config_manager.cpp \ src/config_manager.cpp \
src/db_manager.cpp \ src/db_manager.cpp \
src/icchatpacket.cpp \
src/main.cpp \ src/main.cpp \
src/packets.cpp \ src/packets.cpp \
src/server.cpp \ src/server.cpp \
@ -42,7 +43,6 @@ HEADERS += include/advertiser.h \
include/area_data.h \ include/area_data.h \
include/config_manager.h \ include/config_manager.h \
include/db_manager.h \ include/db_manager.h \
include/icchatpacket.h \
include/server.h \ include/server.h \
include/ws_client.h \ include/ws_client.h \
include/ws_proxy.h include/ws_proxy.h

View File

@ -20,7 +20,6 @@
#include "include/aopacket.h" #include "include/aopacket.h"
#include "include/server.h" #include "include/server.h"
#include "include/icchatpacket.h"
#include "include/area_data.h" #include "include/area_data.h"
#include "include/db_manager.h" #include "include/db_manager.h"
@ -106,6 +105,13 @@ class AOClient : public QObject {
void pktHpBar(AreaData* area, int argc, QStringList argv, AOPacket packet); void pktHpBar(AreaData* area, int argc, QStringList argv, AOPacket packet);
void pktWebSocketIp(AreaData* area, int argc, QStringList argv, AOPacket packet); void pktWebSocketIp(AreaData* area, int argc, QStringList argv, AOPacket packet);
// Packet helper functions
AOPacket validateIcPacket(AOPacket packet);
// Packet helper global variables
bool last_msg_blankpost = false;
int char_id = -1;
struct PacketInfo { struct PacketInfo {
unsigned long long acl_mask; unsigned long long acl_mask;
int minArgs; int minArgs;
@ -121,7 +127,7 @@ class AOClient : public QObject {
{"RD", {ACLFlags.value("NONE"), 0, &AOClient::pktLoadingDone}}, {"RD", {ACLFlags.value("NONE"), 0, &AOClient::pktLoadingDone}},
{"PW", {ACLFlags.value("NONE"), 1, &AOClient::pktCharPassword}}, {"PW", {ACLFlags.value("NONE"), 1, &AOClient::pktCharPassword}},
{"CC", {ACLFlags.value("NONE"), 3, &AOClient::pktSelectChar}}, {"CC", {ACLFlags.value("NONE"), 3, &AOClient::pktSelectChar}},
{"MS", {ACLFlags.value("NONE"), 1, &AOClient::pktIcChat}}, // TODO: doublecheck {"MS", {ACLFlags.value("NONE"), 15, &AOClient::pktIcChat}},
{"CT", {ACLFlags.value("NONE"), 2, &AOClient::pktOocChat}}, {"CT", {ACLFlags.value("NONE"), 2, &AOClient::pktOocChat}},
{"CH", {ACLFlags.value("NONE"), 1, &AOClient::pktPing}}, {"CH", {ACLFlags.value("NONE"), 1, &AOClient::pktPing}},
{"MC", {ACLFlags.value("NONE"), 2, &AOClient::pktChangeMusic}}, {"MC", {ACLFlags.value("NONE"), 2, &AOClient::pktChangeMusic}},
@ -147,6 +153,7 @@ class AOClient : public QObject {
void cmdAddPerms(int argc, QStringList argv); void cmdAddPerms(int argc, QStringList argv);
void cmdRemovePerms(int argc, QStringList argv); void cmdRemovePerms(int argc, QStringList argv);
void cmdListUsers(int argc, QStringList argv); void cmdListUsers(int argc, QStringList argv);
void cmdLogout(int argc, QStringList argv);
// Command helper functions // Command helper functions
QStringList buildAreaList(int area_idx); QStringList buildAreaList(int area_idx);
@ -176,7 +183,8 @@ class AOClient : public QObject {
{"listperms", {ACLFlags.value("NONE"), 0, &AOClient::cmdListPerms}}, {"listperms", {ACLFlags.value("NONE"), 0, &AOClient::cmdListPerms}},
{"addperm", {ACLFlags.value("MODIFY_USERS"), 2, &AOClient::cmdAddPerms}}, {"addperm", {ACLFlags.value("MODIFY_USERS"), 2, &AOClient::cmdAddPerms}},
{"removeperm", {ACLFlags.value("MODIFY_USERS"), 2, &AOClient::cmdRemovePerms}}, {"removeperm", {ACLFlags.value("MODIFY_USERS"), 2, &AOClient::cmdRemovePerms}},
{"listusers", {ACLFlags.value("MODIFY_USERS"), 0, &AOClient::cmdListUsers}} {"listusers", {ACLFlags.value("MODIFY_USERS"), 0, &AOClient::cmdListUsers}},
{"logout", {ACLFlags.value("NONE"), 0, &AOClient::cmdLogout}}
}; };
QString partial_packet; QString partial_packet;

View File

@ -40,7 +40,7 @@ public:
void addBan(QString ipid, QHostAddress ip, QString hdid, unsigned long time, QString reason); 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); bool createUser(QString username, QString salt, QString password, unsigned long long acl);
unsigned long long getACL(QString moderator_name); unsigned long long getACL(QString moderator_name);
bool authenticate(QString username, QString password); bool authenticate(QString username, QString password);
bool updateACL(QString username, unsigned long long acl, bool mode); bool updateACL(QString username, unsigned long long acl, bool mode);

View File

@ -1,65 +0,0 @@
//////////////////////////////////////////////////////////////////////////////////////
// akashi - a server for Attorney Online 2 //
// Copyright (C) 2020 scatterflower //
// //
// This program is free software: you can redistribute it and/or modify //
// it under the terms of the GNU Affero General Public License as //
// published by the Free Software Foundation, either version 3 of the //
// License, or (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU Affero General Public License for more details. //
// //
// You should have received a copy of the GNU Affero General Public License //
// along with this program. If not, see <https://www.gnu.org/licenses/>. //
//////////////////////////////////////////////////////////////////////////////////////
#ifndef ICCHATPACKET_H
#define ICCHATPACKET_H
#endif // ICCHATPACKET_H
#include "include/aopacket.h"
class ICChatPacket : public AOPacket
{
public:
ICChatPacket(AOPacket packet);
const int MIN_FIELDS = 15;
const int MAX_FIELDS = 30;
bool is_valid;
QString desk_override;
QString preanim;
QString character;
QString emote;
QString message;
QString side;
QString sfx_name;
int emote_modifier;
int char_id;
QString sfx_delay;
int objection_modifier;
QString evidence;
bool flip;
bool realization;
int text_color;
QString showname;
int other_charid;
QString other_name;
QString other_emote;
int self_offset;
int other_offset;
bool other_flip;
bool noninterrupting_preanim;
bool sfx_looping;
bool screenshake;
QString frames_shake;
QString frames_realization;
QString frames_sfx;
bool additive;
QString effect;
};

View File

@ -70,8 +70,7 @@ void AOClient::clientDisconnected()
void AOClient::handlePacket(AOPacket packet) void AOClient::handlePacket(AOPacket packet)
{ {
// TODO: like everything here should send a signal qDebug() << "Received packet:" << packet.header << ":" << packet.contents << "args length:" << packet.contents.length();
// qDebug() << "Received packet:" << packet.header << ":" << packet.contents;
AreaData* area = server->areas[current_area]; AreaData* area = server->areas[current_area];
PacketInfo info = packets.value(packet.header, {false, 0, &AOClient::pktDefault}); PacketInfo info = packets.value(packet.header, {false, 0, &AOClient::pktDefault});
@ -175,7 +174,7 @@ void AOClient::fullArup() {
void AOClient::sendPacket(AOPacket packet) void AOClient::sendPacket(AOPacket packet)
{ {
// qDebug() << "Sent packet:" << packet.header << ":" << packet.contents; qDebug() << "Sent packet:" << packet.header << ":" << packet.contents;
socket->write(packet.toUtf8()); socket->write(packet.toUtf8());
socket->flush(); socket->flush();
} }

View File

@ -209,8 +209,10 @@ void AOClient::cmdAddUser(int argc, QStringList argv)
quint64 salt_number = QRandomGenerator::system()->generate64(); quint64 salt_number = QRandomGenerator::system()->generate64();
QString salt = QStringLiteral("%1").arg(salt_number, 16, 16, QLatin1Char('0')); QString salt = QStringLiteral("%1").arg(salt_number, 16, 16, QLatin1Char('0'));
server->db_manager->createUser(argv[0], salt, argv[1], ACLFlags.value("NONE")); if (server->db_manager->createUser(argv[0], salt, argv[1], ACLFlags.value("NONE")))
sendServerMessage("Created user " + argv[0] + ".\nUse /addperm to modify their permissions."); sendServerMessage("Created user " + argv[0] + ".\nUse /addperm to modify their permissions.");
else
sendServerMessage("Unable to create user " + argv[0] + ".\nDoes a user with that name already exist?");
} }
void AOClient::cmdListPerms(int argc, QStringList argv) void AOClient::cmdListPerms(int argc, QStringList argv)
@ -232,7 +234,7 @@ void AOClient::cmdListPerms(int argc, QStringList argv)
} }
} }
else { else {
if ((user_acl & ACLFlags.value("MANAGE_USERS")) == 0) { if ((user_acl & ACLFlags.value("MODIFY_USERS")) == 0) {
sendServerMessage("You do not have permission to view other users' permissions."); sendServerMessage("You do not have permission to view other users' permissions.");
return; return;
} }
@ -297,6 +299,11 @@ void AOClient::cmdRemovePerms(int argc, QStringList argv)
return; return;
} }
if (argv[0] == "root") {
sendServerMessage("You cannot change the permissions of the root account!");
return;
}
if (argv[1] == "SUPER") { if (argv[1] == "SUPER") {
if (user_acl != ACLFlags.value("SUPER")) { if (user_acl != ACLFlags.value("SUPER")) {
// This has to be checked separately, because SUPER & anything will always be truthy // This has to be checked separately, because SUPER & anything will always be truthy
@ -327,6 +334,18 @@ void AOClient::cmdListUsers(int argc, QStringList argv)
sendServerMessage("All users:\n" + users.join("\n")); sendServerMessage("All users:\n" + users.join("\n"));
} }
void AOClient::cmdLogout(int argc, QStringList argv)
{
if (!authenticated) {
sendServerMessage("You are not logged in!");
return;
}
authenticated = false;
moderator_name = "";
sendServerMessage("You have been logged out.");
return;
}
QStringList AOClient::buildAreaList(int area_idx) QStringList AOClient::buildAreaList(int area_idx)
{ {
QStringList entries; QStringList entries;
@ -335,7 +354,7 @@ QStringList AOClient::buildAreaList(int area_idx)
entries.append("=== " + area_name + " ==="); entries.append("=== " + area_name + " ===");
entries.append("[" + QString::number(area->player_count) + " users][" + area->status + "]"); entries.append("[" + QString::number(area->player_count) + " users][" + area->status + "]");
for (AOClient* client : server->clients) { for (AOClient* client : server->clients) {
if (client->current_area == area_idx && client->authenticated) { if (client->current_area == area_idx && client->joined) {
QString char_entry = client->current_char; QString char_entry = client->current_char;
if (char_entry == "") if (char_entry == "")
char_entry = "Spectator"; char_entry = "Spectator";

View File

@ -87,8 +87,16 @@ void DBManager::addBan(QString ipid, QHostAddress ip, QString hdid, unsigned lon
qDebug() << "SQL Error:" << query.lastError().text(); qDebug() << "SQL Error:" << query.lastError().text();
} }
void DBManager::createUser(QString username, QString salt, QString password, unsigned long long acl) bool DBManager::createUser(QString username, QString salt, QString password, unsigned long long acl)
{ {
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; QSqlQuery query;
QString salted_password; QString salted_password;
@ -105,6 +113,7 @@ void DBManager::createUser(QString username, QString salt, QString password, uns
query.exec(); query.exec();
qDebug() << "Created user" << username << "with password" << password << "and salted with value" << salt << ": stored as" << salted_password; qDebug() << "Created user" << username << "with password" << password << "and salted with value" << salt << ": stored as" << salted_password;
return true;
} }
unsigned long long DBManager::getACL(QString moderator_name) unsigned long long DBManager::getACL(QString moderator_name)

View File

@ -1,31 +0,0 @@
//////////////////////////////////////////////////////////////////////////////////////
// akashi - a server for Attorney Online 2 //
// Copyright (C) 2020 scatterflower //
// //
// This program is free software: you can redistribute it and/or modify //
// it under the terms of the GNU Affero General Public License as //
// published by the Free Software Foundation, either version 3 of the //
// License, or (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU Affero General Public License for more details. //
// //
// You should have received a copy of the GNU Affero General Public License //
// along with this program. If not, see <https://www.gnu.org/licenses/>. //
//////////////////////////////////////////////////////////////////////////////////////
#include "include/icchatpacket.h"
ICChatPacket::ICChatPacket(AOPacket packet)
: AOPacket(packet.header, packet.contents)
{
// Perform some basic validation
if (packet.contents.length() > MAX_FIELDS || packet.contents.length() < MIN_FIELDS) {
is_valid = false;
return;
}
// Populate information about the message
is_valid = true;
}

View File

@ -108,9 +108,11 @@ void AOClient::pktCharPassword(AreaData* area, int argc, QStringList argv, AOPac
void AOClient::pktSelectChar(AreaData* area, int argc, QStringList argv, AOPacket packet) void AOClient::pktSelectChar(AreaData* area, int argc, QStringList argv, AOPacket packet)
{ {
bool argument_ok; bool argument_ok;
int char_id = argv[1].toInt(&argument_ok); char_id = argv[1].toInt(&argument_ok);
if (!argument_ok) if (!argument_ok) {
char_id = -1;
return; return;
}
if (current_char != "") { if (current_char != "") {
area->characters_taken[current_char] = false; area->characters_taken[current_char] = false;
@ -139,9 +141,11 @@ void AOClient::pktSelectChar(AreaData* area, int argc, QStringList argv, AOPacke
void AOClient::pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket packet) void AOClient::pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket packet)
{ {
// TODO: validate, validate, validate // TODO: validate, validate, validate
ICChatPacket ic_packet(packet); AOPacket validated_packet = validateIcPacket(packet);
if (ic_packet.is_valid) if (validated_packet.header == "INVALID")
server->broadcast(ic_packet, current_area); return;
server->broadcast(validated_packet, current_area);
} }
void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket packet) void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket packet)
@ -229,3 +233,145 @@ void AOClient::pktWebSocketIp(AreaData* area, int argc, QStringList argv, AOPack
remote_ip = QHostAddress(argv[0]); remote_ip = QHostAddress(argv[0]);
} }
} }
AOPacket AOClient::validateIcPacket(AOPacket packet)
{
// Welcome to the super cursed server-side IC chat validation hell
// I wanted to use enums or #defines here to make the
// indicies of the args arrays more readable. But,
// in typical AO fasion, the indicies for the incoming
// and outgoing packets are different. Just RTFM.
AOPacket invalid("INVALID", {});
QStringList args;
if (current_char == "" || !joined)
// Spectators cannot use IC
return invalid;
QList<QVariant> incoming_args;
for (QString arg : packet.contents) {
incoming_args.append(QVariant(arg));
}
// message type
if (incoming_args[0].toInt() == 1)
args.append("1");
else if (incoming_args[0].toInt() == 0) {
if (incoming_args[0].toString() == "chat")
args.append("chat");
else
args.append("0");
}
// preanim
args.append(incoming_args[1].toString());
// char name
if (!server->characters.contains(incoming_args[2].toString()))
return invalid;
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
qDebug() << "INI swap detected from " << getIpid();
}
args.append(incoming_args[2].toString());
// emote
args.append(incoming_args[3].toString());
// message text
QString incoming_msg = incoming_args[4].toString().trimmed();
if (incoming_msg == "") {
if (last_msg_blankpost)
return invalid;
last_msg_blankpost = true;
}
else
last_msg_blankpost = false;
if (incoming_msg == last_message)
return invalid;
last_message = incoming_msg;
args.append(incoming_msg);
// side
// this is validated clientside so w/e
args.append(incoming_args[5].toString());
// sfx name
args.append(incoming_args[6].toString());
// emote modifier
// Now, gather round, y'all. Here is a story that is truly a microcosm of the AO dev experience.
// If this value is a 4, it will crash the client. Why? Who knows, but it does.
// Now here is the kicker: in certain versions, the client would incorrectly send a 4 here
// For a long time, by configuring the client to do a zoom with a preanim, it would send 4
// This would crash everyone else's client, and the feature had to be disabled
// But, for some reason, nobody traced the cause of this issue for many many years.
// The serverside fix is needed to ensure invalid values are not sent, because the client sucks
int emote_mod = incoming_args[7].toInt();
if (emote_mod == 4)
emote_mod = 6;
if (emote_mod != 0 && emote_mod != 1 && emote_mod != 2 && emote_mod != 5 && emote_mod != 6)
return invalid;
args.append(QString::number(emote_mod));
// char id
if (incoming_args[8].toInt() != char_id)
return invalid;
args.append(incoming_args[8].toString());
// sfx delay
args.append(incoming_args[9].toString());
// objection modifier
if (incoming_args[10].toString().contains("4")) {
// custom shout includes text metadata
args.append(incoming_args[10].toString());
}
else {
int obj_mod = incoming_args[10].toInt();
if (obj_mod != 0 && obj_mod != 1 && obj_mod != 2 && obj_mod != 3)
return invalid;
args.append(QString::number(obj_mod));
}
// evidence
// TODO: add to this once evidence is implemented
args.append(incoming_args[11].toString());
// flipping
int flip = incoming_args[12].toInt();
if (flip != 0 && flip != 1)
return invalid;
args.append(QString::number(flip));
// realization
int realization = incoming_args[13].toInt();
if (realization != 0 && realization != 1)
return invalid;
args.append(QString::number(realization));
// text color
int text_color = incoming_args[14].toInt();
if (text_color != 0 && text_color != 1 && text_color != 2 && text_color != 3 && text_color != 4 && text_color != 5 && text_color != 6)
return invalid;
args.append(QString::number(text_color));
// 2.6 packet extensions
if (incoming_args.length() > 15) {
}
// 2.8 packet extensions
if (incoming_args.length() > 19) {
}
return AOPacket("MS", args);
}