diff options
author | Treeki <treeki@gmail.com> | 2013-12-25 09:00:59 +0100 |
---|---|---|
committer | Treeki <treeki@gmail.com> | 2013-12-25 09:00:59 +0100 |
commit | b7a8b597b00eedde277836eb8530ba742edcad5d (patch) | |
tree | 1b959fa0eec02ff4e22e168aa4379b2b64575ce3 | |
download | bounce_qt-b7a8b597b00eedde277836eb8530ba742edcad5d.tar.gz bounce_qt-b7a8b597b00eedde277836eb8530ba742edcad5d.zip |
Diffstat (limited to '')
-rw-r--r-- | client/bounce_client.pro | 28 | ||||
-rw-r--r-- | client/main.cpp | 11 | ||||
-rw-r--r-- | client/mainwindow.cpp | 44 | ||||
-rw-r--r-- | client/mainwindow.h | 29 | ||||
-rw-r--r-- | client/mainwindow.ui | 34 | ||||
-rw-r--r-- | server/bounce_qt.pro | 38 | ||||
-rw-r--r-- | server/bouncer.cpp | 127 | ||||
-rw-r--r-- | server/bouncer.h | 47 | ||||
-rw-r--r-- | server/client.cpp | 73 | ||||
-rw-r--r-- | server/client.h | 28 | ||||
-rw-r--r-- | server/ircnetwork.cpp | 158 | ||||
-rw-r--r-- | server/ircnetwork.h | 90 | ||||
-rw-r--r-- | server/ircnetworkconfig.cpp | 2 | ||||
-rw-r--r-- | server/ircnetworkconfig.h | 13 | ||||
-rw-r--r-- | server/main.cpp | 25 | ||||
-rw-r--r-- | server/packetreader.cpp | 53 | ||||
-rw-r--r-- | server/packetreader.h | 21 | ||||
-rw-r--r-- | server/packetwriter.cpp | 28 | ||||
-rw-r--r-- | server/packetwriter.h | 21 | ||||
-rw-r--r-- | server/server.cpp | 131 | ||||
-rw-r--r-- | server/server.h | 24 | ||||
-rw-r--r-- | server/socket.cpp | 214 | ||||
-rw-r--r-- | server/socket.h | 110 | ||||
-rw-r--r-- | server/testthing.cpp | 13 | ||||
-rw-r--r-- | server/testthing.h | 18 |
25 files changed, 1380 insertions, 0 deletions
diff --git a/client/bounce_client.pro b/client/bounce_client.pro new file mode 100644 index 0000000..bfb6faf --- /dev/null +++ b/client/bounce_client.pro @@ -0,0 +1,28 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-12-16T07:46:22 +# +#------------------------------------------------- + +QT += core gui network + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = bounce_client +TEMPLATE = app + + +SOURCES += main.cpp\ + mainwindow.cpp \ + ../server/socket.cpp \ + ../server/packetwriter.cpp \ + ../server/packetreader.cpp \ + ../server/client.cpp + +HEADERS += mainwindow.h \ + ../server/socket.h \ + ../server/packetwriter.h \ + ../server/packetreader.h \ + ../server/client.h + +FORMS += mainwindow.ui diff --git a/client/main.cpp b/client/main.cpp new file mode 100644 index 0000000..a080bdd --- /dev/null +++ b/client/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" +#include <QApplication> + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp new file mode 100644 index 0000000..1dfee5e --- /dev/null +++ b/client/mainwindow.cpp @@ -0,0 +1,44 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "../server/client.h" + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + connect(ui->lineEdit, SIGNAL(returnPressed()), this, SLOT(handleLineEntered())); + + m_client = new Client(this); + connect(m_client, SIGNAL(consoleOutput(QString)), this, SLOT(handleConsoleOutput(QString))); + + m_client->connectToHost("localhost", 6744); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + + + +void MainWindow::handleLineEntered() { + QString line = ui->lineEdit->text(); + ui->lineEdit->clear(); + + m_client->consoleInput(line); +} + +void MainWindow::handleConsoleOutput(const QString &str) { + QTextCursor cursor = ui->textEdit->textCursor(); + bool isAtEnd = cursor.atEnd(); + + cursor.movePosition(QTextCursor::End); + cursor.clearSelection(); + cursor.insertText(str); + cursor.insertText("\n"); + + if (isAtEnd) + ui->textEdit->setTextCursor(cursor); +} diff --git a/client/mainwindow.h b/client/mainwindow.h new file mode 100644 index 0000000..9cd58cf --- /dev/null +++ b/client/mainwindow.h @@ -0,0 +1,29 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QMainWindow> +class Client; + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private: + Ui::MainWindow *ui; + + Client *m_client; + +private slots: + void handleLineEntered(); + void handleConsoleOutput(const QString &str); +}; + +#endif // MAINWINDOW_H diff --git a/client/mainwindow.ui b/client/mainwindow.ui new file mode 100644 index 0000000..437f0ca --- /dev/null +++ b/client/mainwindow.ui @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>508</width> + <height>364</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralWidget"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTextEdit" name="textEdit"> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="lineEdit"/> + </item> + </layout> + </widget> + </widget> + <layoutdefault spacing="6" margin="11"/> + <resources/> + <connections/> +</ui> diff --git a/server/bounce_qt.pro b/server/bounce_qt.pro new file mode 100644 index 0000000..2152506 --- /dev/null +++ b/server/bounce_qt.pro @@ -0,0 +1,38 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-12-12T06:13:00 +# +#------------------------------------------------- + +QT += core network + +QT -= gui + +TARGET = bounce_qt +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + + +SOURCES += main.cpp \ + ircnetwork.cpp \ + ircnetworkconfig.cpp \ + bouncer.cpp \ + packetreader.cpp \ + packetwriter.cpp \ + socket.cpp \ + server.cpp \ + client.cpp \ + testthing.cpp + +HEADERS += \ + ircnetwork.h \ + ircnetworkconfig.h \ + bouncer.h \ + packetreader.h \ + packetwriter.h \ + socket.h \ + server.h \ + client.h \ + testthing.h diff --git a/server/bouncer.cpp b/server/bouncer.cpp new file mode 100644 index 0000000..0a38c26 --- /dev/null +++ b/server/bouncer.cpp @@ -0,0 +1,127 @@ +#include "bouncer.h" +#include "ircnetwork.h" +#include "packetwriter.h" +#include "server.h" + +Bouncer *Bouncer::m_instancePtr = NULL; + +Bouncer *Bouncer::instance() { + if (!m_instancePtr) + m_instancePtr = new Bouncer(); + return m_instancePtr; +} + +Bouncer::Bouncer(QObject *parent) : + QObject(parent) +{ + connect(&server, SIGNAL(newConnection()), this, SLOT(connectionReceived())); +} + + +void Bouncer::setupBouncer(int port, bool ssl) { + m_ssl = ssl; + + if (!server.listen(QHostAddress::Any, port)) { + qFatal("Error while listening!"); + return; + } +} + +void Bouncer::addNetwork(IRCNetworkConfig &config) { + IRCNetwork *n = new IRCNetwork(config, this); + networks << n; +} + +void Bouncer::connectionReceived() { + Server *c = new Server(this); + clients << c; + + connect(c, SIGNAL(finished()), this, SLOT(connectionFinished())); + + qDebug("new connection!"); + c->setup(server.nextPendingConnection()); +} + +void Bouncer::connectionFinished() { + Server *c = static_cast<Server *>(sender()); + + disconnect(c, SIGNAL(finished()), this, SLOT(connectionFinished())); + + c->deleteLater(); + clients.remove(c); + qDebug("connection finished and removed from list"); +} + + + + + +Server *Bouncer::findClientBySessionKey(quint8 *sessionKey) const { + foreach (Server *iter, clients) { + if (memcmp(sessionKey, iter->sessionKey(), Server::SessionKeySize) == 0) { + return iter; + } + } + + return NULL; +} + +void Bouncer::generateSessionKey(quint8 *output) { + do { + quint32 now = QDateTime::currentDateTime().toTime_t(); + output[0] = (now & 0xFF); + output[1] = ((now >> 8) & 0xFF); + output[2] = ((now >> 16) & 0xFF); + output[3] = ((now >> 24) & 0xFF); + + for (int i = 4; i < Server::SessionKeySize; i++) + output[i] = (qrand() & 0xFF); + + } while (findClientBySessionKey(output) != NULL); +} + + +Server *Bouncer::authenticate(Server *client, QString &username, QString &password, quint8 *sessionKey) const { + // Check the username/password + if (username != "testtest") + return NULL; + if (password != "optimal") + return NULL; + + // Is this a null session key? + bool isNullSessionKey = true; + for (int i = 0; i < Server::SessionKeySize; i++) + if (sessionKey[i] != 0) { + isNullSessionKey = false; + break; + } + + // Force a new session + if (isNullSessionKey) + return client; + + // Check to see if we have an existing session with this key + Server *takeOver = findClientBySessionKey(sessionKey); + if (takeOver != NULL) { + // Yep, we do. + // TODO: check that the username matches. This is a security hole otherwise. + return takeOver; + } + + // Nope. Use a new session. + return client; +} + + +void Bouncer::broadcast(const Message &message) { + if (clients.empty()) + return; + + PacketWriter writer(4 + (message.text.size() * 2)); + writer.writeString(message.text); + + Packet packet(B2C_ConsoleOutput, writer.buffer()); + + foreach (Server *iter, clients) + iter->sendPacket(packet); +} diff --git a/server/bouncer.h b/server/bouncer.h new file mode 100644 index 0000000..b0eb7f9 --- /dev/null +++ b/server/bouncer.h @@ -0,0 +1,47 @@ +#ifndef BOUNCER_H +#define BOUNCER_H + +#include <QObject> +#include <QSet> +#include <QTcpServer> + +struct IRCNetworkConfig; +class IRCNetwork; +class Server; +struct Message; + +class Bouncer : public QObject { + Q_OBJECT + +public: + static Bouncer *instance(); + + QSet<IRCNetwork *> networks; + QSet<Server *> clients; + QTcpServer server; + + void setupBouncer(int port, bool ssl); + void addNetwork(IRCNetworkConfig &config); + + Server *findClientBySessionKey(quint8 *sessionKey) const; + void generateSessionKey(quint8 *output); + Server *authenticate(Server *client, QString &username, QString &password, quint8 *sessionKey) const; + + void removeClient(Server *client); + + void broadcast(const Message &message); + +private slots: + void connectionReceived(); + void connectionFinished(); + +private: + static Bouncer *m_instancePtr; + + bool m_ssl; + + explicit Bouncer(QObject *parent = 0); + +}; + +#endif // BOUNCER_H diff --git a/server/client.cpp b/server/client.cpp new file mode 100644 index 0000000..3952297 --- /dev/null +++ b/server/client.cpp @@ -0,0 +1,73 @@ +#include "client.h" +#include "packetreader.h" +#include "packetwriter.h" +#include <QTcpSocket> + +Client::Client(QObject *parent) : + Socket(parent) +{ + setup(new QTcpSocket(this)); + + memset(m_sessionKey, 0, sizeof(m_sessionKey)); +} + + +void Client::connectToHost(const QString &hostname, int port) { + socket()->connectToHost(hostname, port); +} + + +void Client::handlePacket(int type, PacketReader &reader) { + switch (type) { + case B2C_OOB_LoginSuccess: + reader.readBytes(m_sessionKey, SessionKeySize); + qDebug("Logged in successfully!"); + + m_handshakeStatus = 1; + flushPacketCache(); + + break; + + case B2C_OOB_LoginFailed: + qDebug("Login failed: code %u", reader.readU32()); + unlinkSocket(true); + emit finished(); + break; + + case B2C_ConsoleOutput: + emit consoleOutput(reader.readString(MaxConsoleStringSize)); + break; + } +} + + +void Client::socketConnected() { + qDebug("Client connected!"); + + sendLoginPacket("testtest", "optimal", m_sessionKey); +} + + +void Client::sendLoginPacket(const QString &username, const QString &password, const quint8 *sessionKey) { + int size = 12 + SessionKeySize + + (username.length() * 2) + (password.length() * 2); + + PacketWriter writer(size); + writer.writeU32(1); + writer.writeU32(lastReceivedPacketID()); + writer.writeBytes(sessionKey, SessionKeySize); + writer.writeString(username); + writer.writeString(password); + + Packet packet(C2B_OOB_Login, writer.buffer()); + sendPacket(packet, /*isHandshakePacket=*/true); +} + + +void Client::consoleInput(const QString &message) { + PacketWriter writer(4 + (message.size() * 2)); + writer.writeString(message); + + Packet packet(C2B_ConsoleInput, writer.buffer()); + sendPacket(packet); +} diff --git a/server/client.h b/server/client.h new file mode 100644 index 0000000..82875d1 --- /dev/null +++ b/server/client.h @@ -0,0 +1,28 @@ +#ifndef CLIENT_H +#define CLIENT_H + +#include "socket.h" + +class Client : public Socket +{ + Q_OBJECT +public: + explicit Client(QObject *parent = 0); + + void connectToHost(const QString &hostname, int port); + +public: + void consoleInput(const QString &message); +signals: + void consoleOutput(const QString &message); + +protected: + virtual void handlePacket(int type, PacketReader &reader); + virtual void socketConnected(); + + +private: + void sendLoginPacket(const QString &username, const QString &password, const quint8 *sessionKey); +}; + +#endif // CLIENT_H diff --git a/server/ircnetwork.cpp b/server/ircnetwork.cpp new file mode 100644 index 0000000..e50a309 --- /dev/null +++ b/server/ircnetwork.cpp @@ -0,0 +1,158 @@ +#include "ircnetwork.h" +#include "bouncer.h" +#include <QTcpSocket> +#include <QSslSocket> + +IRCNetwork::IRCNetwork(IRCNetworkConfig &config, QObject *parent) : + QObject(parent) +{ + m_config = config; + m_loggedIn = false; + m_socket = NULL; + + qDebug("[%p] IRCNetwork::IRCNetwork()", this); +} + + + + +void IRCNetwork::startConnection() { + unlinkSocket(); + qDebug("[%p] IRCNetwork::startConnection()", this); + + m_readBuffer.clear(); + + if (m_config.useSSL) { + QSslSocket *ssl = new QSslSocket(this); + m_socket = ssl; + + connect(ssl, SIGNAL(encrypted()), this, SLOT(socketConnected())); + connect(ssl, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(socketSslErrors(QList<QSslError>))); + + ssl->ignoreSslErrors(); + ssl->connectToHostEncrypted(m_config.hostname, m_config.port); + } else { + m_socket = new QTcpSocket(this); + connect(m_socket, SIGNAL(connected()), this, SLOT(socketConnected())); + m_socket->connectToHost(m_config.hostname, m_config.port); + } + + connect(m_socket, SIGNAL(readyRead()), this, SLOT(socketReceived())); + connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); + connect(m_socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + + qDebug("[%p] Going...", this); +} + +void IRCNetwork::unlinkSocket() { + markAsDisconnected(); + + if (m_socket != NULL) { + QObject::disconnect(m_socket, SIGNAL(connected()), this, SLOT(socketConnected())); + QObject::disconnect(m_socket, SIGNAL(readyRead()), this, SLOT(socketReceived())); + QObject::disconnect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); + QObject::disconnect(m_socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + + QSslSocket *ssl = qobject_cast<QSslSocket *>(m_socket); + if (ssl) { + QObject::disconnect(ssl, SIGNAL(encrypted()), this, SLOT(socketConnected())); + } + + m_socket->close(); + m_socket->deleteLater(); + m_socket = NULL; + } +} + + +void IRCNetwork::sendLine(const QString &line) { + if (m_socket == NULL) { + qWarning("Sending line while m_socket is null!!"); + return; + } + + m_socket->write(line.toUtf8()); + m_socket->write("\r\n"); +} + +void IRCNetwork::socketConnected() { + qDebug("Connected to network socket!"); + sendLine(QString("PASS ") + m_config.password); + sendLine("USER username 0 * :realname"); + sendLine("NICK Ninjifox"); +} + +void IRCNetwork::socketDisconnected() { + qDebug("Socket disconnected!"); + unlinkSocket(); +} + +void IRCNetwork::socketError(QAbstractSocket::SocketError error) { + qDebug("Socket error!"); + unlinkSocket(); +} + +void IRCNetwork::socketSslErrors(const QList<QSslError> &errors) { + qDebug("Socket SSL errors!"); + foreach (const QSslError &error, errors) { + qDebug("- %s", error.errorString().toLatin1().data()); + } + + qobject_cast<QSslSocket *>(m_socket)->ignoreSslErrors(); + //unlinkSocket(); +} + +void IRCNetwork::socketReceived() { + qDebug("Data arrived!"); + + m_readBuffer.append(m_socket->readAll()); + + // Try to process as many lines as we can + char *buf = m_readBuffer.data(); + int bufSize = m_readBuffer.size(); + int lineBegin = 0; + int pos = 0; + + while (pos < bufSize) { + if (buf[pos] == '\r' || buf[pos] == '\n') { + if (pos > lineBegin) { + QString line = QString::fromUtf8(&buf[lineBegin], pos - lineBegin); + parseLine(line); + } + + lineBegin = pos + 1; + } + + pos++; + } + + // If we managed to handle anything, lop it off the buffer + if (pos > 0) { + if (pos >= bufSize) { + m_readBuffer.clear(); + } else { + memmove(buf, &buf[pos], bufSize - pos); + m_readBuffer.resize(bufSize - pos); + } + } +} + + +void IRCNetwork::parseLine(const QString &line) { + Message message; + message.timestamp = QDateTime::currentDateTime(); + message.text = line; + + m_statusBuffer.messages.append(message); + Bouncer::instance()->broadcast(message); +} + + +void IRCNetwork::markAsDisconnected() { + if (!m_loggedIn) + return; + + m_loggedIn = false; + // boop + // have to part user from channels here +} diff --git a/server/ircnetwork.h b/server/ircnetwork.h new file mode 100644 index 0000000..3c4cab5 --- /dev/null +++ b/server/ircnetwork.h @@ -0,0 +1,90 @@ +#ifndef IRCNETWORK_H +#define IRCNETWORK_H + +#include <QObject> +#include <QMap> +#include <QHash> +#include <QList> +#include <QString> +#include <QDateTime> +#include <QTcpSocket> +#include <QSslError> +#include "ircnetworkconfig.h" + + + +struct Message { + QDateTime timestamp; + QString text; +}; + +struct User { + QString nick; + QString hostmask; +}; + +struct UserInChannel { + User *user; + char modePrefix; +}; + +struct Buffer { + QList<Message> messages; +}; + +struct Channel : Buffer { + bool joined; + QString channelName; + QString topic; + QList<UserInChannel> users; +}; + +struct Query : Buffer { + User *partner; +}; + + + +class IRCNetwork : public QObject { + Q_OBJECT +private: + IRCNetworkConfig m_config; + + // stuff sent to us by the server when we log in + QMap<QChar, QChar> m_userPrefixes; + QMap<QChar, int> m_chanModes; + + // all our state + QHash<QString, User *> m_users; + QMap<QString, Channel *> m_channels; + QHash<User *, Query *> m_queries; + Buffer m_statusBuffer; + + bool m_loggedIn; + +public: + explicit IRCNetwork(IRCNetworkConfig &config, QObject *parent = 0); + + + // socket bits +private: + QTcpSocket *m_socket; + QByteArray m_readBuffer; + + void unlinkSocket(); + void markAsDisconnected(); + void sendLine(const QString &line); + void parseLine(const QString &line); +public: + void startConnection(); + +private slots: + void socketConnected(); + void socketReceived(); + void socketError(QAbstractSocket::SocketError error); + void socketDisconnected(); + void socketSslErrors(const QList<QSslError> &errors); + +}; + +#endif // IRCNETWORK_H diff --git a/server/ircnetworkconfig.cpp b/server/ircnetworkconfig.cpp new file mode 100644 index 0000000..53b9671 --- /dev/null +++ b/server/ircnetworkconfig.cpp @@ -0,0 +1,2 @@ +#include "ircnetworkconfig.h" + diff --git a/server/ircnetworkconfig.h b/server/ircnetworkconfig.h new file mode 100644 index 0000000..bfbf098 --- /dev/null +++ b/server/ircnetworkconfig.h @@ -0,0 +1,13 @@ +#ifndef IRCNETWORKCONFIG_H +#define IRCNETWORKCONFIG_H + +#include <QString> + +struct IRCNetworkConfig { + QString hostname; + int port; + QString password; + bool useSSL; +}; + +#endif // IRCNETWORKCONFIG_H diff --git a/server/main.cpp b/server/main.cpp new file mode 100644 index 0000000..f65e6bd --- /dev/null +++ b/server/main.cpp @@ -0,0 +1,25 @@ +#include <QCoreApplication> +#include "bouncer.h" +#include "client.h" +#include "ircnetworkconfig.h" +#include "testthing.h" +#include <stdio.h> +#include <QTimer> + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + printf("[ Starting up ... ]\n"); + + Bouncer::instance()->setupBouncer(6744, false); + + //TestThing *tt = new TestThing(); + //QTimer::singleShot(5000, tt, SLOT(testMe())); + + int retVal = a.exec(); + + printf("[ Complete. ]\n"); + + return retVal; +} diff --git a/server/packetreader.cpp b/server/packetreader.cpp new file mode 100644 index 0000000..c28bcba --- /dev/null +++ b/server/packetreader.cpp @@ -0,0 +1,53 @@ +#include "packetreader.h" + +PacketReader::PacketReader(const QByteArray &buffer) { + m_pointer = (const quint8 *)buffer.constData(); + m_end = m_pointer + buffer.size(); +} + +PacketReader::PacketReader(const quint8 *data, int size) { + m_pointer = data; + m_end = m_pointer + size; +} + + +quint32 PacketReader::readU32() { + if ((m_pointer + 4) > m_end) + return 0; + + quint32 value = + m_pointer[0] | + (m_pointer[1] << 8) | + (m_pointer[2] << 16) | + (m_pointer[3] << 24); + + m_pointer += 4; + return value; +} + +bool PacketReader::readBytes(quint8 *output, int size) { + if ((m_pointer + size) > m_end) + return false; + + memcpy(output, m_pointer, size); + m_pointer += size; + + return true; +} + +QString PacketReader::readString(int maxSize) { + quint32 size = readU32() & ~1; + + if ((m_pointer + size) > m_end) + return QString::null; + if ((size / 2) > maxSize) { + m_pointer += size; + return QString::null; + } + + QString retVal(size / 2, QChar()); + memcpy(retVal.data(), m_pointer, size); + m_pointer += size; + + return retVal; +} diff --git a/server/packetreader.h b/server/packetreader.h new file mode 100644 index 0000000..676981d --- /dev/null +++ b/server/packetreader.h @@ -0,0 +1,21 @@ +#ifndef PACKETREADER_H +#define PACKETREADER_H + +#include <QString> +#include <QByteArray> + +class PacketReader { +public: + PacketReader(const QByteArray &buffer); + PacketReader(const quint8 *data, int size); + + quint32 readU32(); + bool readBytes(quint8 *output, int size); + QString readString(int maxSize); + +private: + const quint8 *m_pointer; + const quint8 *m_end; +}; + +#endif // PACKETREADER_H diff --git a/server/packetwriter.cpp b/server/packetwriter.cpp new file mode 100644 index 0000000..e1fe700 --- /dev/null +++ b/server/packetwriter.cpp @@ -0,0 +1,28 @@ +#include "packetwriter.h" + +PacketWriter::PacketWriter(int reservedSize) { + m_buffer.reserve(reservedSize); +} + +void PacketWriter::writeU32(quint32 value) { + writeBytes((const quint8 *)(&value), 4); +} + +void PacketWriter::writeBytes(const quint8 *input, int size) { + m_buffer.append((const char *)input, size); +} + +void PacketWriter::writeString(const QString &string) { + writeU32(string.length() * 2); + writeBytes((const quint8 *)string.constData(), string.length() * 2); +} + + +/* +inline void writeU32(quint8 *buf, quint32 value) { + buf[0] = (value & 0xFF); + buf[1] = ((value >> 8) & 0xFF); + buf[2] = ((value >> 16) & 0xFF); + buf[3] = ((value >> 24) & 0xFF); +} +*/ diff --git a/server/packetwriter.h b/server/packetwriter.h new file mode 100644 index 0000000..e3be778 --- /dev/null +++ b/server/packetwriter.h @@ -0,0 +1,21 @@ +#ifndef PACKETWRITER_H +#define PACKETWRITER_H + +#include <QString> +#include <QByteArray> + +class PacketWriter { +public: + PacketWriter(int reservedSize = 0); + + const QByteArray &buffer() const { return m_buffer; } + + void writeU32(quint32 value); + void writeBytes(const quint8 *input, int size); + void writeString(const QString &string); + +private: + QByteArray m_buffer; +}; + +#endif // PACKETWRITER_H diff --git a/server/server.cpp b/server/server.cpp new file mode 100644 index 0000000..6c9412b --- /dev/null +++ b/server/server.cpp @@ -0,0 +1,131 @@ +#include "server.h" +#include "packetreader.h" +#include "packetwriter.h" +#include "ircnetworkconfig.h" +#include "ircnetwork.h" +#include "bouncer.h" + +Server::Server(QObject *parent) : + Socket(parent) +{ + Bouncer::instance()->generateSessionKey(m_sessionKey); +} + + + +void Server::handlePacket(int type, PacketReader &reader) { + switch (type) { + case C2B_OOB_Login: + net_login(reader); + break; + + case C2B_ConsoleInput: + net_consoleInput(reader.readString(MaxConsoleStringSize)); + break; + } +} + + + +void Server::net_login(PacketReader &reader) { + if (m_handshakeStatus > 0) + return; + + quint32 errorCode = attemptLogin(reader); + + if (errorCode > 0) { + // Yay, we failed somewhere above, so send out an error code + // and disconnect the client! + Packet packet(B2C_OOB_LoginFailed, + QByteArray((const char *)&errorCode, sizeof(errorCode))); + + sendPacket(packet, /*isHandshakePacket=*/true); + unlinkSocket(/*disconnect=*/true); + } +} + +int Server::attemptLogin(PacketReader &reader) { + quint32 protocolVer = reader.readU32(); + if (protocolVer != ProtocolVersion) + return 1; + + quint32 lastReceivedID = reader.readU32(); + + quint8 checkSessKey[SessionKeySize]; + if (!reader.readBytes(checkSessKey, 64)) + return 2; + + QString username = reader.readString(100); + QString password = reader.readString(100); + if (username.isNull() || password.isNull()) + return 2; + + // Parsed the packet, try out the stuff + Server *auth = Bouncer::instance()->authenticate(this, username, password, checkSessKey); + if (!auth) + return 3; + + // Who are we? + if (auth != this) { + // Taking over another session!! + auth->clearAcknowledgedPackets(lastReceivedID); + auth->takeOverSocket(this); + return 0; + } + + // We've successfully authed here, so go ahead. + m_identified = true; + sendLoginSuccess(); + + // Probably don't need to call flushPacketCache here: + // theoretically it should be empty at this point..... + + return 0; +} + +void Server::takeOverSocket(Socket *donor) { + Socket::takeOverSocket(donor); + sendLoginSuccess(); + + flushPacketCache(); +} + +void Server::sendLoginSuccess() { + PacketWriter writer(4 + SessionKeySize); + writer.writeU32(lastReceivedPacketID()); + writer.writeBytes(m_sessionKey, SessionKeySize); + + Packet packet(B2C_OOB_LoginSuccess, writer.buffer()); + sendPacket(packet, /*isHandshakePacket=*/true); + + m_handshakeStatus = 1; + flushPacketCache(); +} + + + +void Server::net_consoleInput(const QString &string) { + if (string.startsWith("woof")) + sendConsoleOutput(QString("woof back: [%1]").arg(string)); + + if (string.startsWith("addznc ")) { + IRCNetworkConfig config; + config.hostname = "209.20.91.134"; + config.port = 1191; + config.password = string.mid(7); + config.useSSL = true; + Bouncer::instance()->addNetwork(config); + } + + if (string.startsWith("connect")) { + Bouncer::instance()->networks.values()[0]->startConnection(); + } +} + +void Server::sendConsoleOutput(const QString &string) { + PacketWriter writer(4 + (string.size() * 2)); + writer.writeString(string); + + Packet packet(B2C_ConsoleOutput, writer.buffer()); + sendPacket(packet); +} diff --git a/server/server.h b/server/server.h new file mode 100644 index 0000000..5aab270 --- /dev/null +++ b/server/server.h @@ -0,0 +1,24 @@ +#ifndef SERVER_H +#define SERVER_H + +#include "socket.h" + +class Server : public Socket +{ + Q_OBJECT +public: + explicit Server(QObject *parent = 0); + +protected: + virtual void takeOverSocket(Socket *donor); + virtual void handlePacket(int type, PacketReader &reader); + + void net_login(PacketReader &reader); + int attemptLogin(PacketReader &reader); + void sendLoginSuccess(); + + void net_consoleInput(const QString &string); + void sendConsoleOutput(const QString &string); +}; + +#endif // SERVER_H diff --git a/server/socket.cpp b/server/socket.cpp new file mode 100644 index 0000000..56c5258 --- /dev/null +++ b/server/socket.cpp @@ -0,0 +1,214 @@ +#include "socket.h" +#include "bouncer.h" +#include "packetreader.h" + +Socket::Socket(QObject *parent) : + QObject(parent) +{ + m_identified = false; + m_socket = NULL; + + m_lastSentPacketID = 0; + m_lastReceivedPacketID = 0; + qDebug("[%p] Socket::Socket()", this); +} + +Socket::~Socket() { + unlinkSocket(/*disconnect=*/true); + qDebug("[%p] Socket::~Socket()", this); +} + + + + +void Socket::setup(QTcpSocket *socket) { + unlinkSocket(/*disconnect=*/true); + + socket->setParent(this); + m_socket = socket; + + m_readBuffer.clear(); + m_handshakeStatus = 0; + + connect(socket, SIGNAL(connected()), this, SLOT(socketConnected())); + connect(socket, SIGNAL(readyRead()), this, SLOT(socketReceived())); + connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); + connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); +} + +void Socket::unlinkSocket(bool disconnect) { + if (!m_socket) + return; + + qDebug("[%p] Socket::unlinkSocket...", this); + + QObject::disconnect(m_socket, SIGNAL(connected()), this, SLOT(socketConnected())); + QObject::disconnect(m_socket, SIGNAL(readyRead()), this, SLOT(socketReceived())); + QObject::disconnect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); + QObject::disconnect(m_socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + + if (disconnect) { + m_socket->flush(); + m_socket->close(); + m_socket->deleteLater(); + } + m_socket = NULL; +} + + +void Socket::socketReceived() { + QByteArray readIt = m_socket->readAll(); + m_readBuffer.append(readIt); + + quint8 *buf = (quint8 *)m_readBuffer.data(); + int bufSize = m_readBuffer.length(); + int position = 0; + + while ((position + 8) <= bufSize) { + PacketReader reader(&buf[position], bufSize - position); + + quint32 packetType = reader.readU32(); + quint32 packetSize = reader.readU32(); + + if (bufSize > 128000 || packetSize > 128000) { + // Something's not right here... + // (TODO: investigate if this is really a good thing to do?) + m_readBuffer.clear(); + return; + } + + // Out-of-band packets have 8 bytes header, otherwise 16 bytes + int headerSize = 8; + if (!(packetType & 0x80000000)) + headerSize = 16; + + if (bufSize >= (position + packetSize + headerSize)) { + // We have the whole of this packet + // If it's not out-of-band, deal with the IDs + + qDebug("Recv: [%08x] %d bytes", packetType, packetSize); + + bool isUnnecessaryPacket = false; + + if (!(packetType & 0x80000000)) { + quint32 packetID = reader.readU32(); + quint32 lastReceivedID = reader.readU32(); + + // Clear messages they already have from our own cache + clearAcknowledgedPackets(lastReceivedID); + + if (packetID > m_lastReceivedPacketID) { + // New packet, so update this + m_lastReceivedPacketID = packetID; + } else { + // We already got this packet (presumably)... + // ...so ignore it! + isUnnecessaryPacket = true; + } + + qDebug(" packetID=%d, lastReceivedID=%d", packetID, lastReceivedID); + } + + // If we haven't received this message, handle it! + if (!isUnnecessaryPacket) { + PacketReader subReader(&buf[position + headerSize], packetSize); + handlePacket(packetType, subReader); + } + + position += headerSize + packetSize; + } else { + // We don't have the whole of this packet yet, so wait. + break; + } + } + + + // If we managed to handle anything, lop it off the buffer + if (position > 0) { + if (position >= bufSize) { + m_readBuffer.clear(); + } else { + memmove(buf, &buf[position], bufSize - position); + m_readBuffer.resize(bufSize - position); + } + } +} + + +void Socket::socketConnected() { + // we do nothing here, but this is used by Client +} + +void Socket::socketError(QAbstractSocket::SocketError error) { + // welp! + qDebug("Connection error!"); + unlinkSocket(/*disconnect=*/true); + if (!m_identified) + emit finished(); +} + +void Socket::socketDisconnected() { + // welp 2. + qDebug("Connection closed!"); + unlinkSocket(/*disconnect=*/true); + if (!m_identified) + emit finished(); +} + + +void Socket::takeOverSocket(Socket *donor) { + setup(donor->m_socket); + m_readBuffer = donor->m_readBuffer; + donor->unlinkSocket(/*disconnect=*/false); + emit donor->finished(); +} + + +void Socket::sendPacketOverSocket(const Packet &packet) { + quint32 packetHeader[4]; + int headerSize = 8; + packetHeader[0] = packet.type; + packetHeader[1] = packet.data.size(); + + if (!packet.isOutOfBand()) { + headerSize = 16; + packetHeader[2] = packet.id; + packetHeader[3] = m_lastReceivedPacketID; + } + + m_socket->write((const char *)&packetHeader, headerSize); + m_socket->write(packet.data); +} + + +void Socket::clearAcknowledgedPackets(int lastReceivedID) { + while (!m_packetCache.empty() && m_packetCache.at(0).id <= lastReceivedID) + m_packetCache.removeFirst(); +} + +void Socket::flushPacketCache() { + foreach (const Packet &packet, m_packetCache) + sendPacketOverSocket(packet); +} + + +void Socket::handlePacket(int type, PacketReader &reader) { + // stub + (void)type; + (void)reader; +} + + +void Socket::sendPacket(Packet &packet, bool isHandshakePacket) { + if (!packet.isOutOfBand()) { + m_lastSentPacketID++; + packet.id = m_lastSentPacketID; + + m_packetCache.append(packet); + } + + if ((m_socket != NULL) && (isHandshakePacket || (m_handshakeStatus > 0))) + sendPacketOverSocket(packet); + else if (packet.isOutOfBand()) + qWarning("WARNING: Out-of-band message %08x was lost!", packet.type); +} diff --git a/server/socket.h b/server/socket.h new file mode 100644 index 0000000..44c8c99 --- /dev/null +++ b/server/socket.h @@ -0,0 +1,110 @@ +#ifndef SOCKET_H +#define SOCKET_H + +#include <QObject> +#include <QByteArray> +#include <QTcpSocket> +#include <QList> + +class PacketReader; + + +enum { + C2B_OOB_Login = 0x80000001, + + B2C_OOB_LoginSuccess = 0x80000001, + B2C_OOB_LoginFailed = 0x80000002, + + C2B_ConsoleInput = 1, + B2C_ConsoleOutput = 1 +}; + + +struct Packet { + int id; + quint32 type; + QByteArray data; + + Packet(quint32 type_) { + type = type_; + } + Packet(quint32 type_, const QByteArray &data_) { + type = type_; + data = data_; + } + + bool isOutOfBand() const { + return ((type & 0x80000000) != 0); + } +}; + + + +class Socket : public QObject { + Q_OBJECT +public: + enum { + SessionKeySize = 64, + MaxConsoleStringSize = 2000, + ProtocolVersion = 1 + }; + + explicit Socket(QObject *parent = 0); + ~Socket(); + + void setup(QTcpSocket *socket); + + const quint8 *sessionKey() const { return m_sessionKey; } + +signals: + void finished(); + + + + // Socket management +protected: + void unlinkSocket(bool disconnect); + virtual void takeOverSocket(Socket *donor); + +protected slots: + virtual void socketConnected(); +private slots: + void socketReceived(); + void socketError(QAbstractSocket::SocketError error); + void socketDisconnected(); + +protected: + QTcpSocket *socket() const { return m_socket; } +private: + QTcpSocket *m_socket; + QByteArray m_readBuffer; + + void sendPacketOverSocket(const Packet &packet); + + + +protected: + // Connection state + int m_handshakeStatus; + bool m_identified; + + quint8 m_sessionKey[SessionKeySize]; + + // Packets system +public: + void sendPacket(Packet &packet, bool isHandshakePacket = false); +protected: + virtual void handlePacket(int type, PacketReader &reader); + void flushPacketCache(); + void clearAcknowledgedPackets(int lastReceivedID); + + int lastReceivedPacketID() const { return m_lastReceivedPacketID; } + +private: + QList<Packet> m_packetCache; + int m_lastSentPacketID; + int m_lastReceivedPacketID; + +}; + +#endif // CLIENT_H diff --git a/server/testthing.cpp b/server/testthing.cpp new file mode 100644 index 0000000..0c0d5e9 --- /dev/null +++ b/server/testthing.cpp @@ -0,0 +1,13 @@ +#include "testthing.h" +#include <QTcpServer> +#include "client.h" + +TestThing::TestThing(QObject *parent) : QObject(parent) { + c = new Client(this); +} + +void TestThing::testMe() { + c->connectToHost("localhost", 6744); +} + + diff --git a/server/testthing.h b/server/testthing.h new file mode 100644 index 0000000..e402f8d --- /dev/null +++ b/server/testthing.h @@ -0,0 +1,18 @@ +#ifndef TESTTHING_H +#define TESTTHING_H + +#include <QObject> +class Client; + +class TestThing : public QObject { + Q_OBJECT +public: + explicit TestThing(QObject *parent = 0); + Client *c; + +public slots: + void testMe(); +}; + + +#endif // TESTTHING_H |