summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/bounce_qt.pro38
-rw-r--r--server/bouncer.cpp127
-rw-r--r--server/bouncer.h47
-rw-r--r--server/client.cpp73
-rw-r--r--server/client.h28
-rw-r--r--server/ircnetwork.cpp158
-rw-r--r--server/ircnetwork.h90
-rw-r--r--server/ircnetworkconfig.cpp2
-rw-r--r--server/ircnetworkconfig.h13
-rw-r--r--server/main.cpp25
-rw-r--r--server/packetreader.cpp53
-rw-r--r--server/packetreader.h21
-rw-r--r--server/packetwriter.cpp28
-rw-r--r--server/packetwriter.h21
-rw-r--r--server/server.cpp131
-rw-r--r--server/server.h24
-rw-r--r--server/socket.cpp214
-rw-r--r--server/socket.h110
-rw-r--r--server/testthing.cpp13
-rw-r--r--server/testthing.h18
20 files changed, 1234 insertions, 0 deletions
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