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 /server | |
| download | bounce_qt-master.tar.gz bounce_qt-master.zip  | |
Diffstat (limited to 'server')
| -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 | 
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  | 
