diff options
-rwxr-xr-x | build.sh | 2 | ||||
-rw-r--r-- | core.h | 60 | ||||
-rw-r--r-- | ircserver.cpp | 24 | ||||
-rw-r--r-- | mobileclient.cpp | 38 | ||||
-rw-r--r-- | netcore.cpp | 49 | ||||
-rw-r--r-- | python_client.py | 206 | ||||
-rw-r--r-- | socketcommon.cpp | 2 | ||||
-rw-r--r-- | window.cpp | 79 |
8 files changed, 397 insertions, 63 deletions
@@ -2,7 +2,7 @@ mkdir -p binary NETCODE="socketcommon.cpp client.cpp mobileclient.cpp server.cpp ircserver.cpp netcore.cpp" -SOURCES="$NETCODE main.cpp dns.cpp" +SOURCES="$NETCODE main.cpp window.cpp dns.cpp" FLAGS="-std=c++11 -lgnutls -pthread -g" g++ -o binary/nb4 $FLAGS $SOURCES @@ -15,6 +15,7 @@ #include <netinet/in.h> #include <gnutls/gnutls.h> #include <list> +#include <string> #include "buffer.h" @@ -31,6 +32,39 @@ class NetCore; class Bouncer; +class IRCServer; + + +class Window { +public: + NetCore *core; + + Window(NetCore *_core); + virtual ~Window() { } + + int id; + std::list<std::string> messages; + + virtual const char *getTitle() const = 0; + virtual int getType() const = 0; + virtual void syncStateForClient(Buffer &output); + virtual void handleUserInput(const char *str) { } + + void pushMessage(const char *str); +}; + +class StatusWindow : public Window { +public: + StatusWindow(IRCServer *_server); + + IRCServer *server; + + virtual const char *getTitle() const; + virtual int getType() const; + virtual void handleUserInput(const char *str); +}; + + class SocketRWCommon { public: @@ -80,6 +114,12 @@ struct Packet { C2B_COMMAND = 1, B2C_STATUS = 1, + B2C_WINDOW_ADD = 0x100, + B2C_WINDOW_REMOVE = 0x101, + B2C_WINDOW_MESSAGE = 0x102, + + C2B_WINDOW_INPUT = 0x102, + C2B_OOB_LOGIN = 0x8001, B2C_OOB_LOGIN_SUCCESS = 0x8001, @@ -160,7 +200,7 @@ class Server : private SocketRWCommon { public: Server(NetCore *_netCore); - ~Server(); + virtual ~Server(); protected: void connect(const char *hostname, int _port, bool _useTls); @@ -174,10 +214,11 @@ private: void connectionSuccessful(); void processReadBuffer(); -private: virtual void connectedEvent() = 0; virtual void disconnectedEvent() = 0; virtual void lineReceivedEvent(char *line, int size) = 0; + + virtual void attachedToCore() { } }; struct IRCNetworkConfig { @@ -191,11 +232,15 @@ struct IRCNetworkConfig { }; class IRCServer : public Server { - Bouncer *bouncer; public: + Bouncer *bouncer; + + StatusWindow status; + IRCNetworkConfig config; IRCServer(Bouncer *_bouncer); + ~IRCServer(); void connect(); @@ -204,6 +249,8 @@ private: virtual void connectedEvent(); virtual void disconnectedEvent(); virtual void lineReceivedEvent(char *line, int size); + + virtual void attachedToCore(); }; @@ -216,6 +263,13 @@ public: int clientCount; int serverCount; + std::list<Window *> windows; + int nextWindowID; + + int registerWindow(Window *window); + void deregisterWindow(Window *window); + Window *findWindow(int id) const; + bool quitFlag; int execute(); diff --git a/ircserver.cpp b/ircserver.cpp index 830c82b..f2189b2 100644 --- a/ircserver.cpp +++ b/ircserver.cpp @@ -1,16 +1,29 @@ #include "core.h" -IRCServer::IRCServer(Bouncer *_bouncer) : Server(_bouncer) { - bouncer = _bouncer; +IRCServer::IRCServer(Bouncer *_bouncer) : + Server(_bouncer), + bouncer(_bouncer), + status(this) +{ +} + +IRCServer::~IRCServer() { + bouncer->deregisterWindow(&status); +} + +void IRCServer::attachedToCore() { + bouncer->registerWindow(&status); } void IRCServer::connect() { + status.pushMessage("Connecting..."); Server::connect(config.hostname, config.port, config.useTls); } void IRCServer::connectedEvent() { printf("[IRCServer:%p] connectedEvent\n", this); + status.pushMessage("Connected, identifying to IRC..."); char buf[2048]; @@ -25,13 +38,10 @@ void IRCServer::connectedEvent() { } void IRCServer::disconnectedEvent() { printf("[IRCServer:%p] disconnectedEvent\n", this); + status.pushMessage("Disconnected."); } void IRCServer::lineReceivedEvent(char *line, int size) { printf("[%d] { %s }\n", size, line); - Buffer pkt; - pkt.writeStr(line, size); - for (int i = 0; i < bouncer->clientCount; i++) - if (bouncer->clients[i]->isAuthed()) - bouncer->clients[i]->sendPacket(Packet::B2C_STATUS, pkt); + status.pushMessage(line); } diff --git a/mobileclient.cpp b/mobileclient.cpp index bbfac73..fb8b3c1 100644 --- a/mobileclient.cpp +++ b/mobileclient.cpp @@ -6,6 +6,18 @@ MobileClient::MobileClient(Bouncer *_bouncer) : Client(_bouncer) { void MobileClient::sessionStartEvent() { printf("{Session started}\n"); + + Buffer syncPacket; + syncPacket.writeU32(bouncer->windows.size()); + + std::list<Window *>::iterator + i = bouncer->windows.begin(), + e = bouncer->windows.end(); + + for (; i != e; ++i) + (*i)->syncStateForClient(syncPacket); + + sendPacket(Packet::B2C_WINDOW_ADD, syncPacket); } void MobileClient::sessionEndEvent() { printf("{Session ended}\n"); @@ -16,6 +28,19 @@ void MobileClient::packetReceivedEvent(Packet::Type type, Buffer &pkt) { pkt.readStr(cmd, sizeof(cmd)); handleDebugCommand(cmd, strlen(cmd)); + } else if (type == Packet::C2B_WINDOW_INPUT) { + int winID = pkt.readU32(); + Window *window = bouncer->findWindow(winID); + if (!window) { + printf("[MobileClient:%p] Message for unknown window %d\n", this, winID); + return; + } + + char text[8192]; + pkt.readStr(text, sizeof(text)); + + window->handleUserInput(text); + } else { printf("[MobileClient:%p] Unrecognised packet for MobileClient: type %d, size %d\n", this, type, pkt.size()); @@ -47,19 +72,6 @@ void MobileClient::handleDebugCommand(char *line, int size) { pkt.writeStr("Your wish is my command!"); for (int i = 0; i < bouncer->clientCount; i++) bouncer->clients[i]->sendPacket(Packet::B2C_STATUS, pkt); - - } else if (strncmp(line, "srvpw", 5) == 0) { - int sid = line[5] - '0'; - // ugly hack, fuck casting, will fix later - strcpy(((IRCServer*)bouncer->servers[sid])->config.password, &line[7]); - - } else if (strncmp(line, "connsrv", 7) == 0) { - int sid = line[7] - '0'; - // ugly hack, fuck casting, will fix later - ((IRCServer*)bouncer->servers[sid])->connect(); - } else if (line[0] >= '0' && line[0] <= '9') { - int sid = line[0] - '0'; - bouncer->servers[sid]->sendLine(&line[1]); } } diff --git a/netcore.cpp b/netcore.cpp index 27769a6..4341787 100644 --- a/netcore.cpp +++ b/netcore.cpp @@ -8,6 +8,8 @@ NetCore::NetCore() { serverCount = 0; for (int i = 0; i < SERVER_LIMIT; i++) servers[i] = NULL; + + nextWindowID = 1; } Client *NetCore::findClientWithSessionKey(uint8_t *key) const { @@ -24,6 +26,7 @@ int NetCore::registerServer(Server *server) { int id = serverCount++; servers[id] = server; + server->attachedToCore(); return id; } void NetCore::deregisterServer(int id) { @@ -251,8 +254,54 @@ int NetCore::execute() { } + +int NetCore::registerWindow(Window *window) { + window->id = nextWindowID; + nextWindowID++; + + windows.push_back(window); + + + Buffer pkt; + pkt.writeU32(1); + window->syncStateForClient(pkt); + + for (int i = 0; i < clientCount; i++) + if (clients[i]->isAuthed()) + clients[i]->sendPacket(Packet::B2C_WINDOW_ADD, pkt); +} + +void NetCore::deregisterWindow(Window *window) { + Buffer pkt; + pkt.writeU32(1); + pkt.writeU32(window->id); + + for (int i = 0; i < clientCount; i++) + if (clients[i]->isAuthed()) + clients[i]->sendPacket(Packet::B2C_WINDOW_REMOVE, pkt); + + windows.remove(window); +} + +Window *NetCore::findWindow(int id) const { + std::list<Window *>::const_iterator + i = windows.begin(), + e = windows.end(); + + for (; i != e; ++i) + if ((*i)->id == id) + return *i; + + return 0; +} + + + + Client *Bouncer::constructClient() { return new MobileClient(this); } + + diff --git a/python_client.py b/python_client.py index 24c8a3e..88ac307 100644 --- a/python_client.py +++ b/python_client.py @@ -1,4 +1,9 @@ -import socket, ssl, threading, struct +# Yes, this source code is terrible. +# It's just something I've put together to test the server +# before I write a *real* client. + +import sys, socket, ssl, threading, struct +from PyQt5 import QtCore, QtGui, QtWidgets protocolVer = 1 sock = None @@ -9,6 +14,8 @@ lastReceivedPacketID = 0 packetCache = [] packetLock = threading.Lock() +u32 = struct.Struct('<I') + class Packet: def __init__(self, type, data): global nextID @@ -19,7 +26,7 @@ class Packet: self.id = nextID nextID = nextID + 1 - def sendOverWire(self): + def sendOverWire(self, sock): header = struct.pack('<HHI', self.type, 0, len(self.data)) if (self.type & 0x8000) == 0: extHeader = struct.pack('<II', self.id, lastReceivedPacketID) @@ -41,9 +48,11 @@ def reader(): global lastReceivedPacketID, authed, sessionKey readbuf = b'' + sockCopy = sock + print('(Connected)') while True: - data = sock.recv(1024) + data = sockCopy.recv(1024) if not data: print('(Disconnected)') break @@ -78,18 +87,23 @@ def reader(): if type == 0x8001: sessionKey = packetdata authed = True + elif type == 0x8002: + print('FAILED!') elif type == 0x8003: authed = True - pid = struct.unpack('<I', packetdata)[0] + pid = u32.unpack(packetdata)[0] clearCachedPackets(pid) try: for packet in packetCache: - packet.sendOverWire() + packet.sendOverWire(sockCopy) except: pass - elif type == 1: - strlen = struct.unpack_from('<I', packetdata, 0)[0] - print(packetdata[4:4+strlen].decode('utf-8')) + else: + # Horrible kludge. I'm sorry. + # I didn't feel like rewriting this to use + # QObject and QThread. :( + packetEvent = PacketEvent(type, packetdata) + app.postEvent(mainwin, packetEvent) pos += size @@ -102,40 +116,156 @@ def writePacket(type, data, allowUnauthed=False): packetCache.append(packet) try: if authed or allowUnauthed: - packet.sendOverWire() + packet.sendOverWire(sock) except: pass +class PacketEvent(QtCore.QEvent): + def __init__(self, ptype, pdata): + QtCore.QEvent.__init__(self, QtCore.QEvent.User) + self.packetType = ptype + self.packetData = pdata + + +class WindowTab(QtWidgets.QWidget): + def __init__(self, parent=None): + QtWidgets.QWidget.__init__(self, parent) + + self.output = QtWidgets.QTextEdit(self) + self.output.setReadOnly(True) + self.input = QtWidgets.QLineEdit(self) + self.input.returnPressed.connect(self.handleLineEntered) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(self.output) + layout.addWidget(self.input) + + enteredMessage = QtCore.pyqtSignal(str) + def handleLineEntered(self): + line = self.input.text() + self.input.setText('') + + self.enteredMessage.emit(line) + + def pushMessage(self, msg): + cursor = self.output.textCursor() + + isAtEnd = cursor.atEnd() + cursor.movePosition(QtGui.QTextCursor.End) + cursor.clearSelection() + cursor.insertText(msg) + cursor.insertText('\n') + + if isAtEnd: + self.output.setTextCursor(cursor) + + +class MainWindow(QtWidgets.QMainWindow): + def __init__(self, parent=None): + QtWidgets.QMainWindow.__init__(self, parent) + + self.setWindowTitle('Ninjifox\'s IRC Client Test') + + tb = self.addToolBar('Main') + tb.addAction('Connect', self.handleConnect) + tb.addAction('Disconnect', self.handleDisconnect) + tb.addAction('Login', self.handleLogin) + + self.tabs = QtWidgets.QTabWidget(self) + self.tabLookup = {} + self.setCentralWidget(self.tabs) -while True: - bit = input() - bits = bit.split(' ', 1) - cmd = bits[0] - - with packetLock: - print('{') - if cmd == 'connect': - try: - basesock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - basesock.connect(('localhost', 5454)) - #sock = ssl.wrap_socket(basesock) - sock = basesock - thd = threading.Thread(None, reader) - thd.start() - except Exception as e: - print(e) - elif cmd == 'disconnect': - sock.shutdown(socket.SHUT_RDWR) - sock.close() - sock = None - authed = False - elif cmd == 'login': - writePacket(0x8001, struct.pack('<II 16s', protocolVer, lastReceivedPacketID, sessionKey), True) - elif cmd == 'cmd': - data = bits[1].encode('utf-8') + self.debugTab = WindowTab(self) + self.debugTab.enteredMessage.connect(self.handleDebug) + self.tabs.addTab(self.debugTab, 'Debug') + + def event(self, event): + if event.type() == QtCore.QEvent.User: + event.accept() + + ptype = event.packetType + pdata = event.packetData + + if ptype == 1: + strlen = u32.unpack_from(pdata, 0)[0] + msg = pdata[4:4+strlen].decode('utf-8', 'replace') + self.debugTab.pushMessage(msg) + elif ptype == 0x100: + # ADD WINDOWS + wndCount = u32.unpack_from(pdata, 0)[0] + pos = 4 + + for i in range(wndCount): + wtype, wid, wtlen = struct.unpack_from('<III', pdata, pos) + pos += 12 + wtitle = pdata[pos:pos+wtlen].decode('utf-8', 'replace') + pos += wtlen + msgCount = u32.unpack_from(pdata, pos)[0] + pos += 4 + msgs = [] + for j in range(msgCount): + msglen = u32.unpack_from(pdata, pos)[0] + pos += 4 + msg = pdata[pos:pos+msglen].decode('utf-8', 'replace') + pos += msglen + msgs.append(msg) + + tab = WindowTab(self) + tab.winID = wid + tab.enteredMessage.connect(self.handleWindowInput) + self.tabs.addTab(tab, wtitle) + self.tabLookup[wid] = tab + tab.pushMessage('\n'.join(msgs)) + elif ptype == 0x102: + # WINDOW MESSAGES + wndID, msglen = struct.unpack_from('<II', pdata, 0) + msg = pdata[8:8+msglen].decode('utf-8', 'replace') + self.tabLookup[wndID].pushMessage(msg) + + return True + else: + return QtWidgets.QMainWindow.event(self, event) + + def handleConnect(self): + global sock + try: + basesock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + basesock.connect(('localhost', 5454)) + #sock = ssl.wrap_socket(basesock) + sock = basesock + thd = threading.Thread(None, reader) + thd.daemon = True + thd.start() + except Exception as e: + print(e) + + def handleDisconnect(self): + global sock, authed + sock.shutdown(socket.SHUT_RDWR) + sock.close() + sock = None + authed = False + + def handleLogin(self): + writePacket(0x8001, struct.pack('<II 16s', protocolVer, lastReceivedPacketID, sessionKey), True) + + def handleDebug(self, text): + with packetLock: + data = str(text).encode('utf-8') writePacket(1, struct.pack('<I', len(data)) + data) - elif cmd == 'quit': - break - print('}') + + def handleWindowInput(self, text): + wid = self.sender().winID + with packetLock: + data = str(text).encode('utf-8') + writePacket(0x102, struct.pack('<II', wid, len(data)) + data) + + +app = QtWidgets.QApplication(sys.argv) + +mainwin = MainWindow() +mainwin.show() + +app.exec_() diff --git a/socketcommon.cpp b/socketcommon.cpp index e98b837..897bc58 100644 --- a/socketcommon.cpp +++ b/socketcommon.cpp @@ -135,7 +135,7 @@ void SocketRWCommon::writeAction() { } if (amount > 0) { - printf("[fd=%d] Wrote %d bytes\n", sock, amount); + printf("[fd=%d] Wrote %d bytes out of %d\n", sock, amount, outputBuf.size()); outputBuf.trimFromStart(amount); } else if (amount == 0) printf("Sent 0!\n"); diff --git a/window.cpp b/window.cpp new file mode 100644 index 0000000..5fc094a --- /dev/null +++ b/window.cpp @@ -0,0 +1,79 @@ +#include "core.h" + +Window::Window(NetCore *_core) { + core = _core; +} + +void Window::syncStateForClient(Buffer &output) { + output.writeU32(getType()); + output.writeU32(id); + output.writeStr(getTitle()); + + output.writeU32(messages.size()); + + std::list<std::string>::iterator + i = messages.begin(), + e = messages.end(); + + for (; i != e; ++i) { + output.writeStr(i->c_str()); + } +} + +void Window::pushMessage(const char *str) { + messages.push_back(str); + + bool createdPacket = false; + Buffer packet; + + for (int i = 0; i < core->clientCount; i++) { + if (core->clients[i]->isAuthed()) { + if (!createdPacket) { + packet.writeU32(id); + packet.writeStr(str); + createdPacket = true; + } + + core->clients[i]->sendPacket(Packet::B2C_WINDOW_MESSAGE, packet); + } + } +} + + + + +StatusWindow::StatusWindow(IRCServer *_server) : + Window(_server->bouncer) +{ + server = _server; +} + +const char *StatusWindow::getTitle() const { + return server->config.hostname; +} + +int StatusWindow::getType() const { + return 1; +} + +void StatusWindow::handleUserInput(const char *str) { + if (str[0] == '/') { + // moof + if (strcmp(str, "/connect") == 0) { + server->connect(); + } else if (strcmp(str, "/disconnect") == 0) { + server->close(); + } else if (strncmp(str, "/password ", 10) == 0) { + pushMessage("Password set."); + + // This is ugly, ugh + strncpy( + server->config.password, + &str[10], + sizeof(server->config.password)); + server->config.password[sizeof(server->config.password) - 1] = 0; + } + } else { + server->sendLine(str); + } +} |