diff --git a/bin/userslennartappdatalocaltemptmpcdtybt b/bin/userslennartappdatalocaltemptmpcdtybt new file mode 100644 index 0000000..e69de29 diff --git a/core/core.pro b/core/core.pro index 727accd..e59f274 100644 --- a/core/core.pro +++ b/core/core.pro @@ -27,7 +27,8 @@ DESTDIR = $$PWD/../bin SOURCES += \ src/acl_roles_handler.cpp \ src/aoclient.cpp \ - src/aopacket.cpp \ + src/network/aopacket.cpp \ + src/network/network_socket.cpp \ src/area_data.cpp \ src/command_extension.cpp \ src/commands/area.cpp \ @@ -44,8 +45,6 @@ SOURCES += \ src/packets.cpp \ src/server.cpp \ src/testimony_recorder.cpp \ - src/ws_client.cpp \ - src/ws_proxy.cpp \ src/advertiser.cpp \ src/logger/u_logger.cpp \ src/logger/writer_modcall.cpp \ @@ -55,7 +54,8 @@ SOURCES += \ HEADERS += include/aoclient.h \ include/acl_roles_handler.h \ include/akashidefs.h \ - include/aopacket.h \ + include/network/aopacket.h \ + include/network/network_socket.h \ include/area_data.h \ include/command_extension.h \ include/config_manager.h \ @@ -64,8 +64,6 @@ HEADERS += include/aoclient.h \ include/discord.h \ include/server.h \ include/typedefs.h \ - include/ws_client.h \ - include/ws_proxy.h \ include/advertiser.h \ include/logger/u_logger.h \ include/logger/writer_modcall.h \ diff --git a/core/include/aoclient.h b/core/include/aoclient.h index 441d8eb..1e26b4d 100644 --- a/core/include/aoclient.h +++ b/core/include/aoclient.h @@ -31,7 +31,8 @@ #endif #include "include/acl_roles_handler.h" -#include "include/aopacket.h" +#include "include/network/aopacket.h" +#include "include/network/network_socket.h" class AreaData; class DBManager; @@ -85,7 +86,7 @@ class AOClient : public QObject * @param user_id The user ID of the client. * @param parent Qt-based parent, passed along to inherited constructor from QObject. */ - AOClient(Server *p_server, QTcpSocket *p_socket, QObject *parent = nullptr, int user_id = 0, MusicManager *p_manager = nullptr); + AOClient(Server *p_server, NetworkSocket *socket, QObject *parent = nullptr, int user_id = 0, MusicManager *p_manager = nullptr); /** * @brief Destructor for the AOClient instance. @@ -355,16 +356,18 @@ class AOClient : public QObject const int SPECTATOR_ID = -1; public slots: + /** + * @brief Handles an incoming packet, checking for authorisation and minimum argument count. + * + * @param packet The incoming packet. + */ + void handlePacket(AOPacket packet); + /** * @brief A slot for when the client disconnects from the server. */ void clientDisconnected(); - /** - * @brief A slot for when the client sends data to the server. - */ - void clientData(); - /** * @brief A slot for sending a packet to the client. * @@ -395,9 +398,9 @@ class AOClient : public QObject private: /** - * @brief The TCP socket used to communicate with the client. + * @brief The network socket used by the client. Can either be a Websocket or TCP Socket. */ - QTcpSocket *m_socket; + NetworkSocket *m_socket; /** * @brief A pointer to the Server, used for updating server variables that depend on the client (e.g. amount of players in an area). @@ -415,13 +418,6 @@ class AOClient : public QObject LOCKED //!< The packet contains updates about what areas are locked. }; - /** - * @brief Handles an incoming packet, checking for authorisation and minimum argument count. - * - * @param packet The incoming packet. - */ - void handlePacket(AOPacket packet); - /** * @brief Handles an incoming command, checking for authorisation and minimum argument count. * @@ -564,13 +560,6 @@ class AOClient : public QObject /// Implements [penalty bars](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#penalty-health-bars). void pktHpBar(AreaData *area, int argc, QStringList argv, AOPacket packet); - /** - * @brief Implements WebSocket IP handling. This is not on the netcode documentation as of writing. - * - * @todo Link packet details when it gets into the netcode documentation. - */ - void pktWebSocketIp(AreaData *area, int argc, QStringList argv, AOPacket packet); - /// Implements [moderator calling](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#call-mod). void pktModCall(AreaData *area, int argc, QStringList argv, AOPacket packet); @@ -745,7 +734,6 @@ class AOClient : public QObject {"MC", {ACLRole::NONE, 2, &AOClient::pktChangeMusic}}, {"RT", {ACLRole::NONE, 1, &AOClient::pktWtCe}}, {"HP", {ACLRole::NONE, 2, &AOClient::pktHpBar}}, - {"WSIP", {ACLRole::NONE, 1, &AOClient::pktWebSocketIp}}, {"ZZ", {ACLRole::NONE, 0, &AOClient::pktModCall}}, {"PE", {ACLRole::NONE, 3, &AOClient::pktAddEvidence}}, {"DE", {ACLRole::NONE, 1, &AOClient::pktRemoveEvidence}}, diff --git a/core/include/area_data.h b/core/include/area_data.h index 8512d6a..d9b283f 100644 --- a/core/include/area_data.h +++ b/core/include/area_data.h @@ -26,7 +26,7 @@ #include #include -#include "include/aopacket.h" +#include "include/network/aopacket.h" class ConfigManager; class Logger; diff --git a/core/include/music_manager.h b/core/include/music_manager.h index aaa3e44..a1b21d9 100644 --- a/core/include/music_manager.h +++ b/core/include/music_manager.h @@ -23,7 +23,7 @@ #include #include -#include "include/aopacket.h" +#include "include/network/aopacket.h" #include "include/typedefs.h" class ConfigManager; diff --git a/core/include/aopacket.h b/core/include/network/aopacket.h similarity index 100% rename from core/include/aopacket.h rename to core/include/network/aopacket.h diff --git a/core/include/network/network_socket.h b/core/include/network/network_socket.h new file mode 100644 index 0000000..8f9e383 --- /dev/null +++ b/core/include/network/network_socket.h @@ -0,0 +1,145 @@ +////////////////////////////////////////////////////////////////////////////////////// +// 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 . // +////////////////////////////////////////////////////////////////////////////////////// +#ifndef NETWORK_SOCKET_H +#define NETWORK_SOCKET_H + +#include +#include +#include +#include + +#include "include/network/aopacket.h" + +class NetworkSocket : public QObject +{ + Q_OBJECT + + public: + /** + * @brief Constructor for the network socket class. + * @param QTcpSocket for communication with external AO2-Client + * @param Pointer to the server object. + */ + NetworkSocket(QTcpSocket *f_socket, QObject *parent = nullptr); + + /** + * @brief Constructor for the network socket class. + * @param QWebSocket for communication with external AO2-Client or WebAO clients. + * @param Pointer to the server object. + */ + NetworkSocket(QWebSocket *f_socket, QObject *parent = nullptr); + + /** + * @brief Returns the Address of the remote socket. + * + * @return QHostAddress object of the socket. + */ + QHostAddress peerAddress(); + + /** + * @brief Closes the socket by request of the child AOClient object or the server. + */ + void close(); + + /** + * @brief Closes the socket by request of the child AOClient object or the server. + * + * @param The close code to the send to the client. + */ + void close(QWebSocketProtocol::CloseCode f_code); + + /** + * @brief Writes data to the network socket. + * + * @param Packet to be written to the socket. + */ + void write(AOPacket f_packet); + + signals: + + /** + * @brief handlePacket + * @param f_packet + */ + void handlePacket(AOPacket f_packet); + + /** + * @brief Emitted when the socket has been closed and the client is disconnected. + */ + void clientDisconnected(); + + private slots: + /** + * @brief Handles the reading and processing of TCP stream data. + * + * @return Decoded AOPacket to be processed by the child AOClient object. + */ + void readData(); + + /** + * @brief Handles the processing of WebSocket data. + * + * @return Decoded AOPacket to be processed by the child AOClient object. + */ + void ws_readData(QString f_data); + + private: + enum SocketType + { + TCP, + WS + }; + + /** + * @brief Union holding either a TCP- or Websocket. + */ + union { + QTcpSocket *tcp; + QWebSocket *ws; + } m_client_socket; + + /** + * @brief Remote IP of the client. + * + * @details In the case of the WebSocket we also check if this has been proxy forwarded. + */ + QHostAddress m_socket_ip; + + /** + * @brief Defines if the client is a Websocket or TCP client. + */ + SocketType m_socket_type; + + /** + * @brief Filled with part of a packet if said packet could not be read fully from the client's socket. + * + * @details Per AO2's network protocol, a packet is finished with the character `%`. + * + * @see #is_partial + */ + QString m_partial_packet; + + /** + * @brief True when the previous `readAll()` call from the client's socket returned an unfinished packet. + * + * @see #partial_packet + */ + bool m_is_partial; +}; + +#endif diff --git a/core/include/server.h b/core/include/server.h index 4826ac3..fd1404a 100644 --- a/core/include/server.h +++ b/core/include/server.h @@ -28,8 +28,10 @@ #include #include #include +#include +#include -#include "include/aopacket.h" +#include "include/network/aopacket.h" class ACLRolesHandler; class Advertiser; @@ -337,6 +339,14 @@ class Server : public QObject */ void clientConnected(); + /** + * @brief Handles a new connection. + * + * @details The function creates an AOClient to represent the user, assigns a user ID to them, and + * checks if the client is banned. + */ + void ws_clientConnected(); + /** * @brief Method to construct and reconstruct Discord Webhook Integration. * @@ -413,6 +423,11 @@ class Server : public QObject */ QTcpServer *server; + /** + * @brief Listens for incoming websocket connections. + */ + QWebSocketServer *ws_server; + /** * @brief Handles Discord webhooks. */ diff --git a/core/include/ws_client.h b/core/include/ws_client.h deleted file mode 100644 index d947f0e..0000000 --- a/core/include/ws_client.h +++ /dev/null @@ -1,124 +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 . // -////////////////////////////////////////////////////////////////////////////////////// -#ifndef WS_CLIENT_H -#define WS_CLIENT_H - -#include -#include -#include -#include - -/** - * @brief Represents a WebSocket client (generally WebAO) connected to the server. - * - * @details To give a common interface to both desktop AO and WebAO clients, the incoming data from - * WebSocket connections are directed through local TCP sockets. - * - * This class is a very thin layer -- see WSProxy for the actual mechanics of this WebSocket-to-TCP proxy solution. - */ -class WSClient : public QObject -{ - Q_OBJECT - - public: - /** - * @brief Creates an instance of the WSClient class. - * - * @param p_tcp_socket The locally created TCP socket to direct data through. - * @param p_web_socket The WebSocket that actually represents the connecting client. - * @param parent Qt-based parent, passed along to inherited constructor from QObject. - * - * @pre This class will not connect up the ports to each other in any way. Unless some setup is done, this class - * by default will never be prompted to read and/or write from/to either of the sockets. - */ - WSClient(QTcpSocket *p_tcp_socket, QWebSocket *p_web_socket, QObject *parent = nullptr); - - /** - * @brief Destructor for the WSClient class. - * - * @details Marks the TCP and WebSocket for later deletion. - */ - ~WSClient(); - public slots: - /** - * @brief A slot that can be signalled when #tcp_socket has data ready for reading. - * Will read all data in the socket. - * - * @details The incoming data is separated per-packet due to the WebAO bug, and the packets are sent - * through #web_socket. - */ - void onTcpData(); - - /** - * @brief A slot that can be signalled to push packets received from WebSocket into the - * associated local TCP socket. - * - * @param message The incoming packet. - */ - void onWsData(QString message); - - /** - * @brief A slot that can be signalled when the WebSocket client disconnect. - * Disconnects the associated TCP socket. - * - * @see onTcpDisconnect() for the opposite scenario. - */ - void onWsDisconnect(); - - /** - * @brief A slot that can be signalled when the TCP socket is disconnected. - * Severs the connection to the WebSocket. - * - * @see onWsDisconnect() for the opposite scenario. - */ - void onTcpDisconnect(); - - /** - * @brief A slot that can be signalled when the TCP socket is connected. - * Sends identification over the socket. - */ - void onTcpConnect(); - - private: - /** - * @brief The local TCP socket used as a proxy to connect with the server. - */ - QTcpSocket *tcp_socket; - - /** - * @brief The WebSocket representing an incoming connection. - */ - QWebSocket *web_socket; - - /** - * @brief Stores partial packets in case they don't all come through the TCP socket at once - */ - QByteArray partial_packet; - - /** - * @brief Flag that is set when packets are segmented - */ - bool is_segmented = false; - - /** - * @brief The IP send in the WSIP packet - */ - QString websocket_ip; -}; - -#endif // WS_CLIENT_H diff --git a/core/include/ws_proxy.h b/core/include/ws_proxy.h deleted file mode 100644 index e1bd6f3..0000000 --- a/core/include/ws_proxy.h +++ /dev/null @@ -1,93 +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 . // -////////////////////////////////////////////////////////////////////////////////////// -#ifndef WS_PROXY_H -#define WS_PROXY_H - -#include -#include -#include -#include - -class WSClient; - -/** - * @brief Handles WebSocket connections by redirecting data sent through them through a local TCP connection - * for common handling. - */ -class WSProxy : public QObject -{ - Q_OBJECT - - public: - /** - * @brief Creates a WSProxy instance. - * - * @param p_local_port The port through which the TCP connection should be directed. Should the same as with - * non-WebAO connections. - * @param p_ws_port The WebSocket port. Should the same that is opened for WebSockets connections. - * @param parent Qt-based parent, passed along to inherited constructor from QObject. - */ - WSProxy(int p_local_port, int p_ws_port, QObject *parent); - - /** - * @brief Destructor for the WSProxy class. - * - * @details Marks the WebSocket server that is used to handle the proxy process to be deleted later. - */ - ~WSProxy(); - - /** - * @brief Starts listening for WebSocket connections on the given port. - */ - void start(); - - public slots: - /** - * @brief Sets up the proxy process to the newly connected WebSocket. - * - * @details This function creates a TCP socket to establish the proxy, creates a WSClient to represent the client connecting through WebSocket. - */ - void wsConnected(); - - private: - /** - * @brief The WebSocket server listening to incoming WebSocket connections. - */ - QWebSocketServer *server; - - /** - * @brief Every client connected through WebSocket. - */ - QVector clients; - - /** - * @brief The TCP port that the WebSocket connections will be redirected through. - * - * @note Should be the same that desktop clients connect through, and that was announced to the master server. - */ - int local_port; - - /** - * @brief The port for incoming WebSocket connections. - * - * @note Should be the same that was announced to the master server. - */ - int ws_port; -}; - -#endif // WS_PROXY_H diff --git a/core/src/aoclient.cpp b/core/src/aoclient.cpp index 178b4a4..d3e7858 100644 --- a/core/src/aoclient.cpp +++ b/core/src/aoclient.cpp @@ -17,11 +17,11 @@ ////////////////////////////////////////////////////////////////////////////////////// #include "include/aoclient.h" -#include "include/aopacket.h" #include "include/area_data.h" #include "include/command_extension.h" #include "include/config_manager.h" #include "include/db_manager.h" +#include "include/network/aopacket.h" #include "include/server.h" const QMap AOClient::COMMANDS{ @@ -147,40 +147,6 @@ const QMap AOClient::COMMANDS{ {"togglewtce", {{ACLRole::CM}, 0, &AOClient::cmdToggleWtce}}, {"toggleshouts", {{ACLRole::CM}, 0, &AOClient::cmdToggleShouts}}}; -void AOClient::clientData() -{ - if (last_read + m_socket->bytesAvailable() > 30720) { // Client can send a max of 30KB to the server over two sequential reads - m_socket->close(); - } - - if (last_read == 0) { // i.e. this is the first packet we've been sent - if (!m_socket->waitForConnected(1000)) { - m_socket->close(); - } - } - QString l_data = QString::fromUtf8(m_socket->readAll()); - last_read = l_data.size(); - - if (is_partial) { - l_data = partial_packet + l_data; - } - if (!l_data.endsWith("%")) { - is_partial = true; - } - - QStringList l_all_packets = l_data.split("%"); - l_all_packets.removeLast(); // Remove the entry after the last delimiter - - if (l_all_packets.value(0).startsWith("MC", Qt::CaseInsensitive)) { - l_all_packets = QStringList{l_all_packets.value(0)}; - } - - for (const QString &l_single_packet : qAsConst(l_all_packets)) { - AOPacket l_packet(l_single_packet); - handlePacket(l_packet); - } -} - void AOClient::clientDisconnected() { #ifdef NET_DEBUG @@ -225,7 +191,7 @@ void AOClient::handlePacket(AOPacket packet) return; } - if (packet.getHeader() != "CH") { + if (packet.getHeader() != "CH" && m_joined) { if (m_is_afk) sendServerMessage("You are no longer AFK."); m_is_afk = false; @@ -436,8 +402,7 @@ void AOClient::sendPacket(AOPacket packet) #ifdef NET_DEBUG qDebug() << "Sent packet:" << packet.getHeader() << ":" << packet.getContent(); #endif - m_socket->write(packet.toUtf8()); - m_socket->flush(); + m_socket->write(packet); } void AOClient::sendPacket(QString header, QStringList contents) @@ -541,15 +506,15 @@ void AOClient::onAfkTimeout() m_is_afk = true; } -AOClient::AOClient(Server *p_server, QTcpSocket *p_socket, QObject *parent, int user_id, MusicManager *p_manager) : +AOClient::AOClient(Server *p_server, NetworkSocket *socket, QObject *parent, int user_id, MusicManager *p_manager) : QObject(parent), m_id(user_id), - m_remote_ip(p_socket->peerAddress()), + m_remote_ip(socket->peerAddress()), m_password(""), m_joined(false), m_current_area(0), m_current_char(""), - m_socket(p_socket), + m_socket(socket), server(p_server), is_partial(false), m_last_wtce_time(0), diff --git a/core/src/area_data.cpp b/core/src/area_data.cpp index a650829..d1110fc 100644 --- a/core/src/area_data.cpp +++ b/core/src/area_data.cpp @@ -18,10 +18,10 @@ #include -#include "include/aopacket.h" #include "include/area_data.h" #include "include/config_manager.h" #include "include/music_manager.h" +#include "include/network/aopacket.h" AreaData::AreaData(QString p_name, int p_index, MusicManager *p_music_manager = nullptr) : m_index(p_index), diff --git a/core/src/commands/area.cpp b/core/src/commands/area.cpp index e70cd44..d7409ba 100644 --- a/core/src/commands/area.cpp +++ b/core/src/commands/area.cpp @@ -17,9 +17,9 @@ ////////////////////////////////////////////////////////////////////////////////////// #include "include/aoclient.h" -#include "include/aopacket.h" #include "include/area_data.h" #include "include/config_manager.h" +#include "include/network/aopacket.h" #include "include/server.h" // This file is for commands under the area category in aoclient.h diff --git a/core/src/commands/casing.cpp b/core/src/commands/casing.cpp index 4a6141d..ead5865 100644 --- a/core/src/commands/casing.cpp +++ b/core/src/commands/casing.cpp @@ -17,9 +17,9 @@ ////////////////////////////////////////////////////////////////////////////////////// #include "include/aoclient.h" -#include "include/aopacket.h" #include "include/area_data.h" #include "include/config_manager.h" +#include "include/network/aopacket.h" #include "include/server.h" // This file is for commands under the casing category in aoclient.h diff --git a/core/src/commands/command_helper.cpp b/core/src/commands/command_helper.cpp index c6262d8..78c4ca8 100644 --- a/core/src/commands/command_helper.cpp +++ b/core/src/commands/command_helper.cpp @@ -17,9 +17,9 @@ ////////////////////////////////////////////////////////////////////////////////////// #include "include/aoclient.h" -#include "include/aopacket.h" #include "include/area_data.h" #include "include/config_manager.h" +#include "include/network/aopacket.h" #include "include/server.h" // This file is for functions used by various commands, defined in the command helper function category in aoclient.h diff --git a/core/src/commands/messaging.cpp b/core/src/commands/messaging.cpp index 0fb9d96..9100fd0 100644 --- a/core/src/commands/messaging.cpp +++ b/core/src/commands/messaging.cpp @@ -17,8 +17,8 @@ ////////////////////////////////////////////////////////////////////////////////////// #include "include/aoclient.h" -#include "include/aopacket.h" #include "include/area_data.h" +#include "include/network/aopacket.h" #include "include/server.h" // This file is for commands under the messaging category in aoclient.h diff --git a/core/src/commands/music.cpp b/core/src/commands/music.cpp index 5d341ac..af44135 100644 --- a/core/src/commands/music.cpp +++ b/core/src/commands/music.cpp @@ -17,9 +17,9 @@ ////////////////////////////////////////////////////////////////////////////////////// #include "include/aoclient.h" -#include "include/aopacket.h" #include "include/area_data.h" #include "include/music_manager.h" +#include "include/network/aopacket.h" #include "include/server.h" // This file is for commands under the music category in aoclient.h diff --git a/core/src/commands/roleplay.cpp b/core/src/commands/roleplay.cpp index d5fae4e..c10c709 100644 --- a/core/src/commands/roleplay.cpp +++ b/core/src/commands/roleplay.cpp @@ -17,9 +17,9 @@ ////////////////////////////////////////////////////////////////////////////////////// #include "include/aoclient.h" -#include "include/aopacket.h" #include "include/area_data.h" #include "include/config_manager.h" +#include "include/network/aopacket.h" #include "include/server.h" // This file is for commands under the roleplay category in aoclient.h diff --git a/core/src/music_manager.cpp b/core/src/music_manager.cpp index 34171ee..28d63e1 100644 --- a/core/src/music_manager.cpp +++ b/core/src/music_manager.cpp @@ -1,7 +1,7 @@ #include "include/music_manager.h" -#include "include/aopacket.h" #include "include/config_manager.h" +#include "include/network/aopacket.h" MusicManager::MusicManager(QStringList f_root_ordered, QStringList f_cdns, QMap> f_root_list, QObject *parent) : QObject(parent), diff --git a/core/src/aopacket.cpp b/core/src/network/aopacket.cpp similarity index 99% rename from core/src/aopacket.cpp rename to core/src/network/aopacket.cpp index 25c05da..a82d7b1 100644 --- a/core/src/aopacket.cpp +++ b/core/src/network/aopacket.cpp @@ -15,7 +15,7 @@ // You should have received a copy of the GNU Affero General Public License // // along with this program. If not, see . // ////////////////////////////////////////////////////////////////////////////////////// -#include "include/aopacket.h" +#include "include/network/aopacket.h" AOPacket::AOPacket(QString p_header, QStringList p_contents) : m_header(p_header), diff --git a/core/src/network/network_socket.cpp b/core/src/network/network_socket.cpp new file mode 100644 index 0000000..31ab52d --- /dev/null +++ b/core/src/network/network_socket.cpp @@ -0,0 +1,134 @@ +////////////////////////////////////////////////////////////////////////////////////// +// 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 . // +////////////////////////////////////////////////////////////////////////////////////// +#include "include/network/network_socket.h" + +NetworkSocket::NetworkSocket(QTcpSocket *f_socket, QObject *parent) : + QObject(parent) +{ + m_socket_type = TCP; + m_client_socket.tcp = f_socket; + connect(m_client_socket.tcp, &QTcpSocket::readyRead, + this, &NetworkSocket::readData); + connect(m_client_socket.tcp, &QTcpSocket::disconnected, + this, &NetworkSocket::clientDisconnected); +} + +NetworkSocket::NetworkSocket(QWebSocket *f_socket, QObject *parent) : + QObject(parent) +{ + m_socket_type = WS; + m_client_socket.ws = f_socket; + connect(m_client_socket.ws, &QWebSocket::textMessageReceived, + this, &NetworkSocket::ws_readData); + connect(m_client_socket.ws, &QWebSocket::disconnected, + this, &NetworkSocket::clientDisconnected); + + bool l_is_local = (m_client_socket.ws->peerAddress() == QHostAddress::LocalHost) || + (m_client_socket.ws->peerAddress() == QHostAddress::LocalHostIPv6); + // TLDR : We check if the header comes trough a proxy/tunnel running locally. + // This is to ensure nobody can send those headers from the web. + QNetworkRequest l_request = m_client_socket.ws->request(); + if (l_request.hasRawHeader("x-forwarded-for") && l_is_local) { + m_socket_ip = QHostAddress(QString::fromUtf8(l_request.rawHeader("x-forwarded-for"))); + } + else { + m_socket_ip = f_socket->peerAddress(); + } +} + +QHostAddress NetworkSocket::peerAddress() +{ + return m_socket_ip; +} + +void NetworkSocket::close() +{ + if (m_socket_type == TCP) { + m_client_socket.tcp->close(); + } + else { + m_client_socket.ws->close(); + } +} + +void NetworkSocket::close(QWebSocketProtocol::CloseCode f_code) +{ + m_client_socket.ws->close(f_code); +} + +void NetworkSocket::readData() +{ + if (m_client_socket.tcp->bytesAvailable() > 30720) { // Client can send a max of 30KB to the server. + m_client_socket.tcp->close(); + } + + QString l_data = QString::fromUtf8(m_client_socket.tcp->readAll()); + + if (m_is_partial) { + l_data = m_partial_packet + l_data; + } + if (!l_data.endsWith("%")) { + m_is_partial = true; + } + + QStringList l_all_packets = l_data.split("%"); + l_all_packets.removeLast(); // Remove the entry after the last delimiter + + if (l_all_packets.value(0).startsWith("MC", Qt::CaseInsensitive)) { + l_all_packets = QStringList{l_all_packets.value(0)}; + } + + for (const QString &l_single_packet : qAsConst(l_all_packets)) { + AOPacket l_packet(l_single_packet); + qDebug() << "Inbound Header:" << l_packet.getHeader(); + emit handlePacket(l_packet); + } +} + +void NetworkSocket::ws_readData(QString f_data) +{ + QString l_data = f_data; + + if (l_data.toUtf8().size() > 30720) { + m_client_socket.ws->close(QWebSocketProtocol::CloseCodeTooMuchData); + } + + QStringList l_all_packets = l_data.split("%"); + l_all_packets.removeLast(); // Remove the entry after the last delimiter + + if (l_all_packets.value(0).startsWith("MC", Qt::CaseInsensitive)) { + l_all_packets = QStringList{l_all_packets.value(0)}; + } + + for (const QString &l_single_packet : qAsConst(l_all_packets)) { + AOPacket l_packet(l_single_packet); + emit handlePacket(l_packet); + } +} + +void NetworkSocket::write(AOPacket f_packet) +{ + if (m_socket_type == TCP) { + m_client_socket.tcp->write(f_packet.toUtf8()); + m_client_socket.tcp->flush(); + } + else { + m_client_socket.ws->sendTextMessage(f_packet.toString()); + m_client_socket.ws->flush(); + } +} diff --git a/core/src/packets.cpp b/core/src/packets.cpp index dcdcb81..5c19ab6 100644 --- a/core/src/packets.cpp +++ b/core/src/packets.cpp @@ -20,11 +20,11 @@ #include #include "include/akashidefs.h" -#include "include/aopacket.h" #include "include/area_data.h" #include "include/config_manager.h" #include "include/db_manager.h" #include "include/music_manager.h" +#include "include/network/aopacket.h" #include "include/server.h" void AOClient::pktDefault(AreaData *area, int argc, QStringList argv, AOPacket packet) @@ -445,55 +445,6 @@ void AOClient::pktHpBar(AreaData *area, int argc, QStringList argv, AOPacket pac updateJudgeLog(area, this, "updated the penalties"); } -void AOClient::pktWebSocketIp(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(area); - Q_UNUSED(argc); - Q_UNUSED(packet); - - // Special packet to set remote IP from the webao proxy - // Only valid if from a local ip - if (m_remote_ip.isLoopback()) { -#ifdef NET_DEBUG - qDebug() << "ws ip set to" << argv[0]; -#endif - m_remote_ip = QHostAddress(argv[0]); - - QHostAddress l_remote_ip = m_remote_ip; - if (l_remote_ip.protocol() == QAbstractSocket::IPv6Protocol) { - l_remote_ip = server->parseToIPv4(l_remote_ip); - } - - if (server->isIPBanned(l_remote_ip)) { - QString l_reason = "Your IP has been banned by a moderator."; - AOPacket l_ban_reason("BD", {l_reason}); - m_socket->write(l_ban_reason.toUtf8()); - m_socket->close(); - return; - } - - calculateIpid(); - auto l_ban = server->getDatabaseManager()->isIPBanned(m_ipid); - if (l_ban.first) { - sendPacket("BD", {l_ban.second}); - m_socket->close(); - return; - } - - int l_multiclient_count = 0; - const QVector l_clients = server->getClients(); - for (AOClient *l_joined_client : l_clients) { - if (m_remote_ip.isEqual(l_joined_client->m_remote_ip)) - l_multiclient_count++; - } - - if (l_multiclient_count > ConfigManager::multiClientLimit()) { - m_socket->close(); - return; - } - } -} - void AOClient::pktModCall(AreaData *area, int argc, QStringList argv, AOPacket packet) { Q_UNUSED(argc); diff --git a/core/src/server.cpp b/core/src/server.cpp index a1358e1..f8e7d02 100644 --- a/core/src/server.cpp +++ b/core/src/server.cpp @@ -20,7 +20,6 @@ #include "include/acl_roles_handler.h" #include "include/advertiser.h" #include "include/aoclient.h" -#include "include/aopacket.h" #include "include/area_data.h" #include "include/command_extension.h" #include "include/config_manager.h" @@ -28,7 +27,8 @@ #include "include/discord.h" #include "include/logger/u_logger.h" #include "include/music_manager.h" -#include "include/ws_proxy.h" +#include "include/network/aopacket.h" +#include "include/network/network_socket.h" Server::Server(int p_port, int p_ws_port, QObject *parent) : QObject(parent), @@ -37,11 +37,9 @@ Server::Server(int p_port, int p_ws_port, QObject *parent) : m_player_count(0) { server = new QTcpServer(this); - connect(server, SIGNAL(newConnection()), this, SLOT(clientConnected())); - proxy = new WSProxy(port, ws_port, this); - if (ws_port != -1) - proxy->start(); + connect(server, &QTcpServer::newConnection, this, &Server::clientConnected); + timer = new QTimer(this); db_manager = new DBManager; @@ -78,6 +76,19 @@ void Server::start() qDebug() << "Server listening on" << port; } + // Enable WebAO + if (ConfigManager::webaoEnabled()) { + ws_server = new QWebSocketServer("Akashi", QWebSocketServer::NonSecureMode, this); + if (!ws_server->listen(bind_addr, ConfigManager::webaoPort())) { + qDebug() << "Websocket Server error:" << ws_server->errorString(); + } + else { + connect(ws_server, &QWebSocketServer::newConnection, + this, &Server::ws_clientConnected); + qInfo() << "Websocket Server listening on" << ConfigManager::webaoPort(); + } + } + // Checks if any Discord webhooks are enabled. handleDiscordIntegration(); @@ -160,7 +171,8 @@ void Server::clientConnected() } int user_id = m_available_ids.pop(); - AOClient *client = new AOClient(this, socket, this, user_id, music_manager); + NetworkSocket *l_socket = new NetworkSocket(socket, this); + AOClient *client = new AOClient(this, l_socket, l_socket, user_id, music_manager); m_clients_ids.insert(user_id, client); int multiclient_count = 1; @@ -205,15 +217,16 @@ void Server::clientConnected() } m_clients.append(client); - connect(socket, &QTcpSocket::disconnected, client, &AOClient::clientDisconnected); - connect(socket, &QTcpSocket::disconnected, this, [=] { + connect(l_socket, &NetworkSocket::clientDisconnected, this, [=] { if (client->hasJoined()) { decreasePlayerCount(); } m_clients.removeAll(client); client->deleteLater(); }); - connect(socket, &QTcpSocket::readyRead, client, &AOClient::clientData); + connect(l_socket, &NetworkSocket::handlePacket, client, &AOClient::handlePacket); + connect(l_socket, &NetworkSocket::clientDisconnected, + client, &AOClient::clientDisconnected); AOPacket decryptor("decryptor", {"NOENCRYPT"}); // This is the infamous workaround for // tsuserver4. It should disable fantacrypt @@ -225,6 +238,84 @@ void Server::clientConnected() #endif } +void Server::ws_clientConnected() +{ + QWebSocket *socket = ws_server->nextPendingConnection(); + NetworkSocket *l_socket = new NetworkSocket(socket, this); + + // Too many players. Reject connection! + // This also enforces the maximum playercount. + if (m_available_ids.empty()) { + AOPacket disconnect_reason("BD", {"Maximum playercount has been reached."}); + l_socket->write(disconnect_reason); + l_socket->close(); + l_socket->deleteLater(); + return; + } + + int user_id = m_available_ids.pop(); + AOClient *client = new AOClient(this, l_socket, l_socket, user_id, music_manager); + m_clients_ids.insert(user_id, client); + + int multiclient_count = 1; + bool is_at_multiclient_limit = false; + client->calculateIpid(); + auto ban = db_manager->isIPBanned(client->getIpid()); + bool is_banned = ban.first; + for (AOClient *joined_client : qAsConst(m_clients)) { + if (client->m_remote_ip.isEqual(joined_client->m_remote_ip)) + multiclient_count++; + } + + if (multiclient_count > ConfigManager::multiClientLimit() && !client->m_remote_ip.isLoopback()) + is_at_multiclient_limit = true; + + if (is_banned) { + QString reason = ban.second; + AOPacket ban_reason("BD", {reason}); + socket->sendTextMessage(ban_reason.toUtf8()); + } + if (is_banned || is_at_multiclient_limit) { + client->deleteLater(); + l_socket->close(QWebSocketProtocol::CloseCodeNormal); + markIDFree(user_id); + return; + } + + QHostAddress l_remote_ip = client->m_remote_ip; + if (l_remote_ip.protocol() == QAbstractSocket::IPv6Protocol) { + l_remote_ip = parseToIPv4(l_remote_ip); + } + + if (isIPBanned(l_remote_ip)) { + QString l_reason = "Your IP has been banned by a moderator."; + AOPacket l_ban_reason("BD", {l_reason}); + l_socket->write(l_ban_reason); + client->deleteLater(); + l_socket->close(QWebSocketProtocol::CloseCodeNormal); + markIDFree(user_id); + return; + } + + m_clients.append(client); + connect(l_socket, &NetworkSocket::clientDisconnected, this, [=] { + if (client->hasJoined()) { + decreasePlayerCount(); + } + m_clients.removeAll(client); + l_socket->deleteLater(); + }); + connect(l_socket, &NetworkSocket::handlePacket, client, &AOClient::handlePacket); + connect(l_socket, &NetworkSocket::clientDisconnected, + client, &AOClient::clientDisconnected); + + AOPacket decryptor("decryptor", {"NOENCRYPT"}); // This is the infamous workaround for + // tsuserver4. It should disable fantacrypt + // completely in any client 2.4.3 or newer + client->sendPacket(decryptor); + hookupAOClient(client); +} + void Server::updateCharsTaken(AreaData *area) { QStringList chars_taken; @@ -548,7 +639,6 @@ Server::~Server() l_client->deleteLater(); } server->deleteLater(); - proxy->deleteLater(); discord->deleteLater(); acl_roles_handler->deleteLater(); diff --git a/core/src/ws_client.cpp b/core/src/ws_client.cpp deleted file mode 100644 index c3d8d7b..0000000 --- a/core/src/ws_client.cpp +++ /dev/null @@ -1,90 +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 . // -////////////////////////////////////////////////////////////////////////////////////// -#include "include/ws_client.h" - -void WSClient::onWsData(QString message) -{ - tcp_socket->write(message.toUtf8()); - tcp_socket->flush(); -} - -void WSClient::onTcpData() -{ - QByteArray tcp_message = tcp_socket->readAll(); - - if (!tcp_message.endsWith("#%")) { - partial_packet.append(tcp_message); - is_segmented = true; - return; - } - - if (is_segmented) { - partial_packet.append(tcp_message); - tcp_message = partial_packet; - partial_packet.clear(); - is_segmented = false; - } - - // Workaround for WebAO bug needing every packet in its own message - QStringList all_packets = QString::fromUtf8(tcp_message).split("%"); - all_packets.removeLast(); // Remove empty space after final delimiter - for (const QString &packet : qAsConst(all_packets)) { - web_socket->sendTextMessage(packet + "%"); - } -} - -void WSClient::onWsDisconnect() -{ - if (tcp_socket != nullptr) - tcp_socket->disconnectFromHost(); -} - -void WSClient::onTcpDisconnect() -{ - web_socket->close(); -} - -void WSClient::onTcpConnect() -{ - tcp_socket->write(QString("WSIP#" + websocket_ip + "#%").toUtf8()); - tcp_socket->flush(); -} - -WSClient::WSClient(QTcpSocket *p_tcp_socket, QWebSocket *p_web_socket, QObject *parent) : - QObject(parent), - tcp_socket(p_tcp_socket), - web_socket(p_web_socket) -{ - bool l_is_local = (web_socket->peerAddress() == QHostAddress::LocalHost) || - (web_socket->peerAddress() == QHostAddress::LocalHostIPv6); - // TLDR : We check if the header comes trough a proxy/tunnel running locally. - // This is to ensure nobody can send those headers from the web. - QNetworkRequest l_request = web_socket->request(); - if (l_request.hasRawHeader("x-forwarded-for") && l_is_local) { - websocket_ip = l_request.rawHeader("x-forwarded-for"); - } - else { - websocket_ip = web_socket->peerAddress().toString(); - } -} - -WSClient::~WSClient() -{ - tcp_socket->deleteLater(); - web_socket->deleteLater(); -} diff --git a/core/src/ws_proxy.cpp b/core/src/ws_proxy.cpp deleted file mode 100644 index 8fde590..0000000 --- a/core/src/ws_proxy.cpp +++ /dev/null @@ -1,66 +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 . // -////////////////////////////////////////////////////////////////////////////////////// -#include "include/ws_proxy.h" - -#include "include/ws_client.h" - -WSProxy::WSProxy(int p_local_port, int p_ws_port, QObject *parent) : - QObject(parent), - local_port(p_local_port), - ws_port(p_ws_port) -{ - server = new QWebSocketServer(QLatin1String(""), - QWebSocketServer::NonSecureMode, this); - connect(server, &QWebSocketServer::newConnection, this, - &WSProxy::wsConnected); -} - -void WSProxy::start() -{ - if (!server->listen(QHostAddress::Any, ws_port)) { - qDebug() << "WebSocket proxy failed to start: " << server->errorString(); - } - else { - qDebug() << "WebSocket proxy listening"; - } -} - -void WSProxy::wsConnected() -{ - QWebSocket *new_ws = server->nextPendingConnection(); - QTcpSocket *new_tcp = new QTcpSocket(this); - WSClient *client = new WSClient(new_tcp, new_ws, this); - clients.append(client); - - connect(new_ws, &QWebSocket::textMessageReceived, client, &WSClient::onWsData); - connect(new_tcp, &QTcpSocket::readyRead, client, &WSClient::onTcpData); - connect(new_ws, &QWebSocket::disconnected, client, &WSClient::onWsDisconnect); - connect(new_tcp, &QTcpSocket::disconnected, this, [=] { - client->onTcpDisconnect(); - clients.removeAll(client); - client->deleteLater(); - }); - connect(new_tcp, &QTcpSocket::connected, client, &WSClient::onTcpConnect); - - new_tcp->connectToHost(QHostAddress::LocalHost, local_port); -} - -WSProxy::~WSProxy() -{ - server->deleteLater(); -} diff --git a/tests/unittest_aopacket/tst_unittest_aopacket.cpp b/tests/unittest_aopacket/tst_unittest_aopacket.cpp index 018b982..0fe8a6a 100644 --- a/tests/unittest_aopacket/tst_unittest_aopacket.cpp +++ b/tests/unittest_aopacket/tst_unittest_aopacket.cpp @@ -1,7 +1,7 @@ #include #include -#include "include/aopacket.h" +#include "include/network/aopacket.h" namespace tests { namespace unittests {