summaryrefslogtreecommitdiff
path: root/bouncer
diff options
context:
space:
mode:
Diffstat (limited to 'bouncer')
-rw-r--r--bouncer/buffer.h197
-rwxr-xr-xbouncer/build.sh9
-rwxr-xr-xbouncer/build_static.sh9
-rw-r--r--bouncer/client.cpp331
-rw-r--r--bouncer/core.h373
-rw-r--r--bouncer/dns.cpp159
-rw-r--r--bouncer/dns.h15
-rw-r--r--bouncer/ircserver.cpp393
-rw-r--r--bouncer/main.cpp61
-rw-r--r--bouncer/mobileclient.cpp77
-rw-r--r--bouncer/netcore.cpp330
-rw-r--r--bouncer/server.cpp160
-rw-r--r--bouncer/socketcommon.cpp174
-rw-r--r--bouncer/window.cpp468
14 files changed, 2756 insertions, 0 deletions
diff --git a/bouncer/buffer.h b/bouncer/buffer.h
new file mode 100644
index 0000000..bfba94a
--- /dev/null
+++ b/bouncer/buffer.h
@@ -0,0 +1,197 @@
+#ifndef BUFFER_H
+#define BUFFER_H
+
+#include <string.h>
+#include <stdint.h>
+
+class Buffer {
+private:
+ char *m_data;
+ bool m_freeBuffer;
+ int m_size;
+ int m_capacity;
+ int m_readPointer;
+ char m_preAllocBuffer[0x200];
+
+public:
+ Buffer() {
+ m_data = m_preAllocBuffer;
+ m_freeBuffer = false;
+ m_size = 0;
+ m_capacity = sizeof(m_preAllocBuffer);
+ m_readPointer = 0;
+ }
+
+ ~Buffer() {
+ if ((m_data != NULL) && m_freeBuffer) {
+ delete[] m_data;
+ m_data = NULL;
+ }
+ }
+
+ void useExistingBuffer(char *data, int size) {
+ if (m_freeBuffer)
+ delete[] m_data;
+
+ m_data = data;
+ m_freeBuffer = false;
+ m_size = size;
+ m_capacity = size;
+ m_readPointer = 0;
+ }
+
+ char *data() const { return m_data; }
+ int size() const { return m_size; }
+ int capacity() const { return m_capacity; }
+
+ void setCapacity(int capacity) {
+ if (capacity == m_capacity)
+ return;
+
+ // Trim the size down if it's too big to fit
+ if (m_size > capacity)
+ m_size = capacity;
+
+ char *newBuf = new char[capacity];
+
+ if (m_data != NULL) {
+ memcpy(newBuf, m_data, m_size);
+ if (m_freeBuffer)
+ delete[] m_data;
+ }
+
+ m_data = newBuf;
+ m_capacity = capacity;
+ m_freeBuffer = true;
+ }
+
+ void clear() {
+ m_size = 0;
+ }
+ void append(const char *data, int size) {
+ if (size <= 0)
+ return;
+
+ int requiredSize = m_size + size;
+ if (requiredSize > m_capacity)
+ setCapacity(requiredSize + 0x100);
+
+ memcpy(&m_data[m_size], data, size);
+ m_size += size;
+ }
+ void append(const Buffer &buf) {
+ append(buf.data(), buf.size());
+ }
+ void resize(int size) {
+ if (size > m_capacity)
+ setCapacity(size + 0x100);
+ m_size = size;
+ }
+
+ void trimFromStart(int amount) {
+ if (amount <= 0)
+ return;
+ if (amount >= m_size) {
+ clear();
+ return;
+ }
+
+ memmove(m_data, &m_data[amount], m_size - amount);
+ m_size -= amount;
+ }
+
+
+ void writeU32(uint32_t v) { append((const char *)&v, 4); }
+ void writeU16(uint16_t v) { append((const char *)&v, 2); }
+ void writeU8(uint8_t v) { append((const char *)&v, 1); }
+ void writeS32(int32_t v) { append((const char *)&v, 4); }
+ void writeS16(int16_t v) { append((const char *)&v, 2); }
+ void writeS8(int8_t v) { append((const char *)&v, 1); }
+
+ void writeStr(const char *data, int size = -1) {
+ if (size == -1)
+ size = strlen(data);
+ writeU32(size);
+ append(data, size);
+ }
+
+ void readSeek(int pos) {
+ m_readPointer = pos;
+ }
+ int readTell() const {
+ return m_readPointer;
+ }
+ bool readRemains(int size) const {
+ if ((size > 0) && ((m_readPointer + size) <= m_size))
+ return true;
+ return false;
+ }
+ void read(char *output, int size) {
+ if ((m_readPointer + size) > m_size) {
+ // Not enough space to read the whole thing...!
+ int copy = m_size - m_readPointer;
+ if (copy > 0)
+ memcpy(output, &m_data[m_readPointer], copy);
+ memset(&output[copy], 0, size - copy);
+ m_readPointer = m_size;
+ } else {
+ memcpy(output, &m_data[m_readPointer], size);
+ m_readPointer += size;
+ }
+ }
+ uint32_t readU32() { uint32_t v; read((char *)&v, 4); return v; }
+ uint16_t readU16() { uint16_t v; read((char *)&v, 2); return v; }
+ uint8_t readU8() { uint8_t v; read((char *)&v, 1); return v; }
+ int32_t readS32() { int32_t v; read((char *)&v, 4); return v; }
+ int16_t readS16() { int16_t v; read((char *)&v, 2); return v; }
+ int8_t readS8() { int8_t v; read((char *)&v, 1); return v; }
+
+ void readStr(char *output, int bufferSize) {
+ uint32_t size = readU32();
+ if (!readRemains(size)) {
+ strcpy(output, "");
+ return;
+ }
+
+ // How much can we safely get?
+ int readAmount;
+ if (size < (bufferSize - 1))
+ readAmount = size;
+ else
+ readAmount = bufferSize - 1;
+
+ // Put this into the buffer
+ read(output, readAmount);
+ output[readAmount] = 0;
+
+ // In case the buffer was too small, skip over the extra source data
+ m_readPointer += (size - readAmount);
+ }
+
+
+ void dump() {
+ for (int base = 0; base < m_size; base += 0x10) {
+ printf("%08x | ", base);
+
+ int pos;
+ for (pos = base; (pos < m_size) && (pos < (base + 0x10)); pos++)
+ printf("%02x ", (uint8_t)m_data[pos]);
+
+ if (pos < (base + 0x10))
+ for (; pos < (base + 0x10); pos++)
+ printf(" ");
+
+ printf("| ");
+
+ for (pos = base; (pos < m_size) && (pos < (base + 0x10)); pos++)
+ if (m_data[pos] >= 32)
+ printf("%c", m_data[pos]);
+ else
+ printf(".");
+
+ printf("\n");
+ }
+ }
+};
+
+#endif /* BUFFER_H */
diff --git a/bouncer/build.sh b/bouncer/build.sh
new file mode 100755
index 0000000..935cae6
--- /dev/null
+++ b/bouncer/build.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+mkdir -p binary
+
+NETCODE="socketcommon.cpp client.cpp mobileclient.cpp server.cpp ircserver.cpp netcore.cpp"
+SOURCES="$NETCODE main.cpp window.cpp dns.cpp"
+FLAGS="-std=c++11 -DUSE_GNUTLS -lgnutls -pthread -g"
+
+g++ -o binary/nb4 $FLAGS $SOURCES
+
diff --git a/bouncer/build_static.sh b/bouncer/build_static.sh
new file mode 100755
index 0000000..6ed9183
--- /dev/null
+++ b/bouncer/build_static.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+mkdir -p binary
+
+NETCODE="socketcommon.cpp client.cpp mobileclient.cpp server.cpp ircserver.cpp netcore.cpp"
+SOURCES="$NETCODE main.cpp window.cpp dns.cpp"
+FLAGS="-static -static-libgcc -static-libstdc++ -std=c++11 -pthread"
+
+g++ -o binary/nb4_static $FLAGS $SOURCES
+
diff --git a/bouncer/client.cpp b/bouncer/client.cpp
new file mode 100644
index 0000000..4fa0a12
--- /dev/null
+++ b/bouncer/client.cpp
@@ -0,0 +1,331 @@
+#include "core.h"
+
+static bool isNullSessionKey(uint8_t *key) {
+ for (int i = 0; i < SESSION_KEY_SIZE; i++)
+ if (key[i] != 0)
+ return false;
+
+ return true;
+}
+
+
+
+Client::Client(NetCore *_netCore) : SocketRWCommon(_netCore) {
+ authState = AS_LOGIN_WAIT;
+ memset(sessionKey, 0, sizeof(sessionKey));
+ readBufPosition = 0;
+
+ nextPacketID = 1;
+ lastReceivedPacketID = 0;
+}
+Client::~Client() {
+ std::list<Packet *>::iterator
+ i = packetCache.begin(),
+ e = packetCache.end();
+
+ for (; i != e; ++i)
+ delete *i;
+}
+
+
+void Client::startService(int _sock, bool withTls) {
+ close();
+
+ sock = _sock;
+
+ if (!setSocketNonBlocking(sock)) {
+ perror("[Client::startService] Could not set non-blocking");
+ close();
+ return;
+ }
+
+#ifdef USE_GNUTLS
+ if (withTls) {
+ int initRet = gnutls_init(&tls, GNUTLS_SERVER);
+ if (initRet != GNUTLS_E_SUCCESS) {
+ printf("[Client::startService] gnutls_init borked\n");
+ gnutls_perror(initRet);
+ close();
+ return;
+ }
+
+ // TODO: error check this
+ int ret;
+ const char *errPos;
+
+ ret = gnutls_priority_set_direct(tls, "PERFORMANCE:%SERVER_PRECEDENCE", &errPos);
+ if (ret != GNUTLS_E_SUCCESS) {
+ printf("gnutls_priority_set_direct failure: %s\n", gnutls_strerror(ret));
+ close();
+ return;
+ }
+
+ ret = gnutls_credentials_set(tls, GNUTLS_CRD_CERTIFICATE, g_clientCreds);
+ if (ret != GNUTLS_E_SUCCESS) {
+ printf("gnutls_credentials_set failure: %s\n", gnutls_strerror(ret));
+ close();
+ return;
+ }
+
+ gnutls_certificate_server_set_request(tls, GNUTLS_CERT_IGNORE);
+
+ gnutls_transport_set_int(tls, sock);
+
+ tlsActive = true;
+
+ state = CS_TLS_HANDSHAKE;
+
+ printf("[fd=%d] preparing for TLS handshake\n", sock);
+ } else
+#endif
+ {
+ state = CS_CONNECTED;
+ }
+}
+
+void Client::close() {
+ SocketRWCommon::close();
+
+ if (authState == AS_AUTHED)
+ deadTime = time(NULL) + SESSION_KEEPALIVE;
+ else
+ deadTime = time(NULL) - 1; // kill instantly
+}
+
+
+void Client::generateSessionKey() {
+ time_t now = time(NULL);
+
+ while (true) {
+ for (int i = 0; i < SESSION_KEY_SIZE; i++) {
+ if (i < sizeof(time_t))
+ sessionKey[i] = ((uint8_t*)&now)[i];
+ else
+ sessionKey[i] = rand() & 255;
+ }
+
+ // Is any other client already using this key?
+ // It's ridiculously unlikely, but... probably best
+ // to check just in case!
+ bool foundMatch = false;
+
+ for (int i = 0; i < netCore->clientCount; i++) {
+ if (netCore->clients[i] != this) {
+ if (!memcmp(netCore->clients[i]->sessionKey, sessionKey, SESSION_KEY_SIZE))
+ foundMatch = true;
+ }
+ }
+
+ // If there's none, we can safely leave!
+ if (!foundMatch)
+ break;
+ }
+}
+
+void Client::clearCachedPackets(int maxID) {
+ packetCache.remove_if([maxID](Packet *&pkt) {
+ return (pkt->id <= maxID);
+ });
+}
+
+
+void Client::handlePacket(Packet::Type type, char *data, int size) {
+ Buffer pkt;
+ pkt.useExistingBuffer(data, size);
+
+ printf("[fd=%d] Packet : type %d, size %d\n", sock, type, size);
+
+ if (authState == AS_LOGIN_WAIT) {
+ if (type == Packet::C2B_OOB_LOGIN) {
+ int error = 0;
+
+ uint32_t protocolVersion = pkt.readU32();
+ if (protocolVersion != PROTOCOL_VERSION)
+ error = 1;
+
+ uint32_t lastReceivedByClient = pkt.readU32();
+
+ if (!pkt.readRemains(SESSION_KEY_SIZE))
+ error = 2;
+
+ // Authentication goes here at some point, too
+
+
+ if (error != 0) {
+ // Send an error...
+ Buffer pkt;
+ pkt.writeU32(error);
+ sendPacket(Packet::B2C_OOB_LOGIN_FAILED, pkt, /*allowUnauthed=*/true);
+
+ // Would close() now but this means the login failed packet never gets sent
+ // need to figure out a fix for this. TODO FIXME etc etc.
+
+ } else {
+ // or log us in!
+ uint8_t reqKey[SESSION_KEY_SIZE];
+ pkt.read((char *)reqKey, SESSION_KEY_SIZE);
+
+ printf("[fd=%d] Client authenticating\n", sock);
+
+ if (!isNullSessionKey(reqKey)) {
+ printf("[fd=%d] Trying to resume session...", sock);
+ printf("(last they received = %d)\n", lastReceivedByClient);
+
+ Client *other = netCore->findClientWithSessionKey(reqKey);
+ printf("[fd=%d] Got client %p\n", sock, other);
+
+ if (other && other->authState == AS_AUTHED) {
+ printf("Valid: last packet we sent = %d\n", other->nextPacketID - 1);
+ // Yep, we can go!
+ other->resumeSession(this, lastReceivedByClient);
+ return;
+ }
+ }
+
+ // If we got here, it means we couldn't resume the session.
+ // Start over.
+ printf("[fd=%d] Creating new session\n", sock);
+
+ generateSessionKey();
+ authState = AS_AUTHED;
+
+ Buffer pkt;
+ pkt.append((char *)sessionKey, SESSION_KEY_SIZE);
+ sendPacket(Packet::B2C_OOB_LOGIN_SUCCESS, pkt);
+
+ sessionStartEvent();
+ }
+
+ } else {
+ printf("[fd=%d] Unrecognised packet in AS_LOGIN_WAIT authstate: type %d, size %d\n",
+ sock, type, size);
+ }
+ } else if (authState == AS_AUTHED) {
+ packetReceivedEvent(type, pkt);
+ }
+}
+
+void Client::processReadBuffer() {
+ // Try to process as many packets as we have in inputBuf
+
+ // Basic header is 8 bytes
+ // Extended (non-OOB) header is 16 bytes
+ inputBuf.readSeek(0);
+ readBufPosition = 0;
+
+ while (inputBuf.readRemains(8)) {
+ // We have 8 bytes, so we can try to read a basic header
+ Packet::Type type = (Packet::Type)inputBuf.readU16();
+ int reserved = inputBuf.readU16();
+ uint32_t packetSize = inputBuf.readU32();
+
+ // Do we now have the whole packet in memory...?
+ int extHeaderSize = (type & Packet::T_OUT_OF_BAND_FLAG) ? 0 : 8;
+
+ if (!inputBuf.readRemains(packetSize + extHeaderSize))
+ break;
+
+
+ if (!(type & Packet::T_OUT_OF_BAND_FLAG)) {
+ // Handle packet system things for non-OOB packets
+ uint32_t packetID = inputBuf.readU32();
+ uint32_t lastReceivedByClient = inputBuf.readU32();
+
+ lastReceivedPacketID = packetID;
+ clearCachedPackets(lastReceivedByClient);
+ }
+
+ // Yep, we can process it!
+
+ // Save the position of the next packet
+ readBufPosition = inputBuf.readTell() + packetSize;
+ handlePacket(type, &inputBuf.data()[inputBuf.readTell()], packetSize);
+
+ inputBuf.readSeek(readBufPosition);
+ }
+
+ // If we managed to handle anything, lop it off the buffer
+ inputBuf.trimFromStart(readBufPosition);
+ readBufPosition = 0;
+}
+
+
+void Client::resumeSession(Client *other, int lastReceivedByClient) {
+ close();
+
+ inputBuf.clear();
+ inputBuf.append(
+ &other->inputBuf.data()[other->readBufPosition],
+ other->inputBuf.size() - other->readBufPosition);
+
+ // Not sure if we need to copy the outputbuf but it can't hurt
+ outputBuf.clear();
+ outputBuf.append(other->outputBuf.data(), other->outputBuf.size());
+
+ sock = other->sock;
+ state = other->state;
+#ifdef USE_GNUTLS
+ tls = other->tls;
+ tlsActive = other->tlsActive;
+#endif
+
+ other->sock = -1;
+ other->state = CS_DISCONNECTED;
+#ifdef USE_GNUTLS
+ other->tls = 0;
+ other->tlsActive = false;
+#endif
+
+ other->close();
+
+ // Now send them everything we've got!
+ Buffer pkt;
+ pkt.writeU32(lastReceivedPacketID);
+ sendPacket(Packet::B2C_OOB_SESSION_RESUMED, pkt);
+
+ clearCachedPackets(lastReceivedByClient);
+
+ std::list<Packet*>::iterator
+ i = packetCache.begin(),
+ e = packetCache.end();
+
+ for (; i != e; ++i)
+ sendPacketOverWire(*i);
+}
+
+void Client::sendPacket(Packet::Type type, const Buffer &data, bool allowUnauthed) {
+ Packet *packet = new Packet;
+ packet->type = type;
+ packet->data.append(data);
+
+ if (type & Packet::T_OUT_OF_BAND_FLAG) {
+ packet->id = 0;
+ } else {
+ packet->id = nextPacketID;
+ nextPacketID++;
+ }
+
+ if (state == CS_CONNECTED)
+ if (authState == AS_AUTHED || allowUnauthed)
+ sendPacketOverWire(packet);
+
+ if (type & Packet::T_OUT_OF_BAND_FLAG)
+ delete packet;
+ else
+ packetCache.push_back(packet);
+}
+
+void Client::sendPacketOverWire(const Packet *packet) {
+ Buffer header;
+ header.writeU16(packet->type);
+ header.writeU16(0);
+ header.writeU32(packet->data.size());
+
+ if (!(packet->type & Packet::T_OUT_OF_BAND_FLAG)) {
+ header.writeU32(packet->id);
+ header.writeU32(lastReceivedPacketID);
+ }
+
+ outputBuf.append(header);
+ outputBuf.append(packet->data);
+}
diff --git a/bouncer/core.h b/bouncer/core.h
new file mode 100644
index 0000000..b5d3164
--- /dev/null
+++ b/bouncer/core.h
@@ -0,0 +1,373 @@
+#ifndef CORE_H
+#define CORE_H
+
+// Set in build.sh
+//#define USE_GNUTLS
+
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <netinet/in.h>
+#include <list>
+#include <map>
+#include <string>
+
+#include "buffer.h"
+
+#ifdef USE_GNUTLS
+#include <gnutls/gnutls.h>
+#endif
+
+#define CLIENT_LIMIT 100
+#define SERVER_LIMIT 20
+
+#define SESSION_KEEPALIVE 30
+
+#define SESSION_KEY_SIZE 16
+
+#define PROTOCOL_VERSION 1
+
+#define SERVE_VIA_TLS false
+
+class NetCore;
+class Bouncer;
+class IRCServer;
+
+// Need to move this somewhere more appropriate
+struct UserRef {
+ std::string nick, ident, hostmask;
+ bool isSelf, isValid;
+
+ UserRef() { isSelf = false; isValid = false; }
+};
+
+
+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 Channel : public Window {
+public:
+ Channel(IRCServer *_server, const char *_name);
+
+ IRCServer *server;
+
+ bool inChannel;
+
+ std::string name, topic;
+ std::map<std::string, uint32_t> users;
+
+ virtual const char *getTitle() const;
+ virtual int getType() const;
+ virtual void handleUserInput(const char *str);
+ virtual void syncStateForClient(Buffer &output);
+
+ void handleNameReply(const char *str);
+ void handleJoin(const UserRef &user);
+ void handlePart(const UserRef &user, const char *message);
+ void handleQuit(const UserRef &user, const char *message);
+ void handleNick(const UserRef &user, const char *newNick);
+ void handleMode(const UserRef &user, const char *str);
+ void handlePrivmsg(const UserRef &user, const char *str);
+
+ char getEffectivePrefixChar(const char *nick) const;
+
+ void disconnected();
+};
+
+
+
+class SocketRWCommon {
+public:
+ static bool setSocketNonBlocking(int fd); // Move me!
+
+ friend class NetCore;
+
+protected:
+ NetCore *netCore;
+
+ Buffer inputBuf, outputBuf;
+
+ enum ConnState {
+ CS_DISCONNECTED = 0,
+ CS_WAITING_DNS = 1, // server only
+ CS_WAITING_CONNECT = 2, // server only
+ CS_TLS_HANDSHAKE = 3,
+ CS_CONNECTED = 4
+ };
+ ConnState state;
+
+ int sock;
+#ifdef USE_GNUTLS
+ gnutls_session_t tls;
+ bool tlsActive;
+#endif
+
+public:
+ SocketRWCommon(NetCore *_netCore);
+ virtual ~SocketRWCommon();
+
+ virtual void close();
+
+private:
+#ifdef USE_GNUTLS
+ bool tryTLSHandshake();
+ bool hasTlsPendingData() const;
+#endif
+
+ void readAction();
+ void writeAction();
+
+ virtual void processReadBuffer() = 0;
+};
+
+
+struct Packet {
+ enum Type {
+ T_OUT_OF_BAND_FLAG = 0x8000,
+
+ C2B_COMMAND = 1,
+ B2C_STATUS = 1,
+
+ B2C_WINDOW_ADD = 0x100,
+ B2C_WINDOW_REMOVE = 0x101,
+ B2C_WINDOW_MESSAGE = 0x102,
+ B2C_WINDOW_RENAME = 0x103,
+
+ C2B_WINDOW_INPUT = 0x102,
+
+ B2C_CHANNEL_USER_ADD = 0x120,
+ B2C_CHANNEL_USER_REMOVE = 0x121,
+ B2C_CHANNEL_USER_RENAME = 0x122,
+ B2C_CHANNEL_USER_MODES = 0x123,
+
+ C2B_OOB_LOGIN = 0x8001,
+
+ B2C_OOB_LOGIN_SUCCESS = 0x8001,
+ B2C_OOB_LOGIN_FAILED = 0x8002,
+ B2C_OOB_SESSION_RESUMED = 0x8003,
+ };
+
+ Type type;
+ int id;
+ Buffer data;
+};
+
+class Client : private SocketRWCommon {
+ friend class NetCore;
+private:
+ enum AuthState {
+ AS_LOGIN_WAIT = 0,
+ AS_AUTHED = 1
+ };
+
+ AuthState authState;
+ uint8_t sessionKey[SESSION_KEY_SIZE];
+ time_t deadTime;
+
+ std::list<Packet *> packetCache;
+ int nextPacketID, lastReceivedPacketID;
+
+public:
+ Client(NetCore *_netCore);
+ ~Client();
+
+ bool isAuthed() const { return (authState == AS_AUTHED); }
+
+ void close();
+
+ void sendPacket(Packet::Type type, const Buffer &data, bool allowUnauthed = false);
+
+private:
+ void startService(int _sock, bool withTls);
+
+ int readBufPosition;
+ void processReadBuffer();
+
+ void generateSessionKey();
+ void resumeSession(Client *other, int lastReceivedByClient);
+
+ void handlePacket(Packet::Type type, char *data, int size);
+ void sendPacketOverWire(const Packet *packet);
+ void clearCachedPackets(int maxID);
+
+ // Events!
+ virtual void sessionStartEvent() = 0;
+ virtual void sessionEndEvent() = 0;
+ virtual void packetReceivedEvent(Packet::Type type, Buffer &pkt) = 0;
+};
+
+class MobileClient : public Client {
+public:
+ Bouncer *bouncer;
+
+ MobileClient(Bouncer *_bouncer);
+
+private:
+ virtual void sessionStartEvent();
+ virtual void sessionEndEvent();
+ virtual void packetReceivedEvent(Packet::Type type, Buffer &pkt);
+
+ void handleDebugCommand(char *line, int size);
+};
+
+class Server : private SocketRWCommon {
+ friend class NetCore;
+
+ int port;
+ bool useTls;
+
+ int dnsQueryId;
+
+public:
+ Server(NetCore *_netCore);
+ virtual ~Server();
+
+protected:
+ void connect(const char *hostname, int _port, bool _useTls);
+
+public:
+ void sendLine(const char *line); // protect me!
+ void close();
+
+private:
+ void tryConnectPhase();
+ void connectionSuccessful();
+ void processReadBuffer();
+
+ virtual void connectedEvent() = 0;
+ virtual void disconnectedEvent() = 0;
+ virtual void lineReceivedEvent(char *line, int size) = 0;
+
+ virtual void attachedToCore() { }
+};
+
+struct IRCNetworkConfig {
+ char hostname[512];
+ char nickname[128];
+ char username[128];
+ char realname[128];
+ char password[128];
+ int port;
+ bool useTls;
+};
+
+class IRCServer : public Server {
+public:
+ Bouncer *bouncer;
+
+ StatusWindow status;
+ std::map<std::string, Channel *> channels;
+
+ IRCNetworkConfig config;
+
+ char currentNick[128];
+ char serverPrefix[32], serverPrefixMode[32];
+ std::string serverChannelModes[4];
+
+ uint32_t getUserFlag(char search, const char *array) const;
+ uint32_t getUserFlagByPrefix(char prefix) const;
+ uint32_t getUserFlagByMode(char mode) const;
+ int getChannelModeType(char mode) const;
+
+ IRCServer(Bouncer *_bouncer);
+ ~IRCServer();
+
+ void connect();
+
+ // Events!
+private:
+ virtual void connectedEvent();
+ virtual void disconnectedEvent();
+ virtual void lineReceivedEvent(char *line, int size);
+
+ virtual void attachedToCore();
+
+
+ void resetIRCState();
+ void processISupport(const char *str);
+
+ Channel *findChannel(const char *name, bool createIfNeeded);
+};
+
+
+class NetCore {
+public:
+ NetCore();
+
+ Client *clients[CLIENT_LIMIT];
+ Server *servers[SERVER_LIMIT];
+ int clientCount;
+ int serverCount;
+
+ void sendToClients(Packet::Type type, const Buffer &data);
+
+ std::list<Window *> windows;
+ int nextWindowID;
+
+ int registerWindow(Window *window);
+ void deregisterWindow(Window *window);
+ Window *findWindow(int id) const;
+
+ bool quitFlag;
+
+ int execute();
+
+ Client *findClientWithSessionKey(uint8_t *key) const;
+private:
+ virtual Client *constructClient() = 0;
+
+public:
+ int registerServer(Server *server); // THIS FUNCTION WILL BE PROTECTED LATER
+protected:
+ void deregisterServer(int id);
+ int findServerID(Server *server) const;
+};
+
+class Bouncer : public NetCore {
+private:
+ virtual Client *constructClient();
+};
+
+
+
+
+// This is ugly as crap, TODO FIXME etc etc
+#ifdef USE_GNUTLS
+extern gnutls_certificate_credentials_t g_serverCreds, g_clientCreds;
+#endif
+
+#endif /* CORE_H */
diff --git a/bouncer/dns.cpp b/bouncer/dns.cpp
new file mode 100644
index 0000000..056bb09
--- /dev/null
+++ b/bouncer/dns.cpp
@@ -0,0 +1,159 @@
+#include "dns.h"
+#include <pthread.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#define DNS_QUERY_COUNT 20
+#define DNS_QUERY_NAME_SIZE 256
+
+enum DNSQueryState {
+ DQS_FREE = 0,
+ DQS_WAITING = 1,
+ DQS_COMPLETE = 2,
+ DQS_ERROR = 3
+};
+
+struct DNSQuery {
+ char name[DNS_QUERY_NAME_SIZE];
+ in_addr result;
+ DNSQueryState status;
+ int version;
+};
+
+static DNSQuery dnsQueue[DNS_QUERY_COUNT];
+static pthread_t dnsThread;
+static pthread_mutex_t dnsQueueMutex;
+static pthread_cond_t dnsQueueCond;
+
+static void *dnsThreadProc(void *);
+
+
+void DNS::start() {
+ pthread_mutex_init(&dnsQueueMutex, NULL);
+ pthread_cond_init(&dnsQueueCond, NULL);
+
+ pthread_create(&dnsThread, NULL, &dnsThreadProc, NULL);
+
+ for (int i = 0; i < DNS_QUERY_COUNT; i++) {
+ dnsQueue[i].status = DQS_FREE;
+ dnsQueue[i].version = 0;
+ }
+}
+
+int DNS::makeQuery(const char *name) {
+ int id = -1;
+
+ pthread_mutex_lock(&dnsQueueMutex);
+
+ for (int i = 0; i < DNS_QUERY_COUNT; i++) {
+ if (dnsQueue[i].status == DQS_FREE) {
+ id = i;
+ break;
+ }
+ }
+
+ if (id != -1) {
+ strncpy(dnsQueue[id].name, name, sizeof(dnsQueue[id].name));
+ dnsQueue[id].name[sizeof(dnsQueue[id].name) - 1] = 0;
+ dnsQueue[id].status = DQS_WAITING;
+ dnsQueue[id].version++;
+ printf("[DNS::%d] New query: %s\n", id, dnsQueue[id].name);
+ }
+
+ pthread_mutex_unlock(&dnsQueueMutex);
+ pthread_cond_signal(&dnsQueueCond);
+
+ return id;
+}
+
+void DNS::closeQuery(int id) {
+ if (id < 0 || id >= DNS_QUERY_COUNT)
+ return;
+
+ pthread_mutex_lock(&dnsQueueMutex);
+ printf("[DNS::%d] Closing query\n", id);
+ dnsQueue[id].status = DQS_FREE;
+ pthread_mutex_unlock(&dnsQueueMutex);
+}
+
+bool DNS::checkQuery(int id, in_addr *pResult, bool *pIsError) {
+ if (id < 0 || id >= DNS_QUERY_COUNT)
+ return false;
+
+ pthread_mutex_lock(&dnsQueueMutex);
+
+ bool finalResult = false;
+ if (dnsQueue[id].status == DQS_COMPLETE) {
+ finalResult = true;
+ *pIsError = false;
+ memcpy(pResult, &dnsQueue[id].result, sizeof(dnsQueue[id].result));
+ } else if (dnsQueue[id].status == DQS_ERROR) {
+ finalResult = true;
+ *pIsError = true;
+ }
+
+ pthread_mutex_unlock(&dnsQueueMutex);
+
+ return finalResult;
+}
+
+
+void *dnsThreadProc(void *) {
+ pthread_mutex_lock(&dnsQueueMutex);
+
+ for (;;) {
+ for (int i = 0; i < DNS_QUERY_COUNT; i++) {
+ if (dnsQueue[i].status == DQS_WAITING) {
+ char nameCopy[DNS_QUERY_NAME_SIZE];
+ memcpy(nameCopy, dnsQueue[i].name, DNS_QUERY_NAME_SIZE);
+
+ int versionCopy = dnsQueue[i].version;
+
+ printf("[DNS::%d] Trying %s...\n", i, nameCopy);
+
+ pthread_mutex_unlock(&dnsQueueMutex);
+
+ addrinfo hints, *res;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED;
+
+ int s = getaddrinfo(nameCopy, NULL, &hints, &res);
+
+ pthread_mutex_lock(&dnsQueueMutex);
+
+ // Before we write to the request, check that it hasn't been
+ // closed (and possibly replaced...!) by another thread
+
+ if (dnsQueue[i].status == DQS_WAITING && dnsQueue[i].version == versionCopy) {
+ if (s == 0) {
+ // Only try the first one for now...
+ // Is this safe? Not sure.
+ dnsQueue[i].status = DQS_COMPLETE;
+ memcpy(&dnsQueue[i].result, &((sockaddr_in*)res->ai_addr)->sin_addr, sizeof(dnsQueue[i].result));
+
+ printf("[DNS::%d] Resolved %s to %x\n", i, dnsQueue[i].name, dnsQueue[i].result.s_addr);
+ } else {
+ dnsQueue[i].status = DQS_ERROR;
+ printf("[DNS::%d] Error condition: %d\n", i, s);
+ }
+ } else {
+ printf("[DNS::%d] Request was cancelled before getaddrinfo completed\n", i);
+ }
+
+ if (s == 0)
+ freeaddrinfo(res);
+ }
+ }
+
+ pthread_cond_wait(&dnsQueueCond, &dnsQueueMutex);
+ }
+
+ pthread_mutex_unlock(&dnsQueueMutex);
+ return NULL;
+}
+
diff --git a/bouncer/dns.h b/bouncer/dns.h
new file mode 100644
index 0000000..78ec75f
--- /dev/null
+++ b/bouncer/dns.h
@@ -0,0 +1,15 @@
+#ifndef DNS_H
+#define DNS_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+namespace DNS {
+ void start();
+ int makeQuery(const char *name);
+ void closeQuery(int id);
+ bool checkQuery(int id, in_addr *pResult, bool *pIsError);
+}
+
+#endif /* DNS_H */
diff --git a/bouncer/ircserver.cpp b/bouncer/ircserver.cpp
new file mode 100644
index 0000000..31cbd6d
--- /dev/null
+++ b/bouncer/ircserver.cpp
@@ -0,0 +1,393 @@
+#include "core.h"
+
+IRCServer::IRCServer(Bouncer *_bouncer) :
+ Server(_bouncer),
+ bouncer(_bouncer),
+ status(this)
+{
+}
+
+IRCServer::~IRCServer() {
+ bouncer->deregisterWindow(&status);
+
+ for (auto &i : channels) {
+ bouncer->deregisterWindow(i.second);
+ delete i.second;
+ }
+}
+
+void IRCServer::attachedToCore() {
+ bouncer->registerWindow(&status);
+}
+
+void IRCServer::connect() {
+ status.pushMessage("Connecting...");
+ Server::connect(config.hostname, config.port, config.useTls);
+}
+
+
+void IRCServer::resetIRCState() {
+ strcpy(currentNick, "");
+
+ strcpy(serverPrefix, "@+");
+ strcpy(serverPrefixMode, "ov");
+}
+
+
+Channel *IRCServer::findChannel(const char *name, bool createIfNeeded) {
+ std::map<std::string, Channel *>::iterator
+ check = channels.find(name);
+
+ if (check == channels.end()) {
+ if (createIfNeeded) {
+ Channel *c = new Channel(this, name);
+ channels[name] = c;
+ return c;
+ } else {
+ return 0;
+ }
+ } else {
+ return check->second;
+ }
+}
+
+
+
+void IRCServer::connectedEvent() {
+ resetIRCState();
+
+ printf("[IRCServer:%p] connectedEvent\n", this);
+ status.pushMessage("Connected, identifying to IRC...");
+
+ char buf[2048];
+
+ if (strlen(config.password) > 0) {
+ sprintf(buf, "PASS %s", config.password);
+ sendLine(buf);
+ }
+
+ sprintf(buf, "USER %s 0 * :%s\r\nNICK %s",
+ config.username, config.realname, config.nickname);
+ sendLine(buf);
+}
+void IRCServer::disconnectedEvent() {
+ printf("[IRCServer:%p] disconnectedEvent\n", this);
+ status.pushMessage("Disconnected.");
+
+ for (auto &i : channels)
+ i.second->disconnected();
+}
+void IRCServer::lineReceivedEvent(char *line, int size) {
+ printf("[%d] { %s }\n", size, line);
+
+ status.pushMessage(line);
+
+
+ // Process this line...!
+ UserRef user;
+
+ // Is there a prefix?
+ if (line[0] == ':') {
+ char nickBuf[512], identBuf[512], hostBuf[512];
+ int nickPos = 0, identPos = 0, hostPos = 0;
+ int phase = 0;
+
+ ++line; // skip colon
+
+ while ((*line != ' ') && (*line != 0)) {
+ if (phase == 0) {
+ // Nick
+ if (*line == '!')
+ phase = 1;
+ else if (*line == '@')
+ phase = 2;
+ else {
+ if (nickPos < 511)
+ nickBuf[nickPos++] = *line;
+ }
+ } else if (phase == 1) {
+ // Ident
+ if (*line == '@')
+ phase = 2;
+ else {
+ if (identPos < 511)
+ identBuf[identPos++] = *line;
+ }
+ } else if (phase == 2) {
+ if (hostPos < 511)
+ hostBuf[hostPos++] = *line;
+ }
+
+ ++line;
+ }
+
+ if (*line == 0) {
+ // Invalid line. Can't parse this.
+ return;
+ }
+
+ ++line; // skip the space
+
+ nickBuf[nickPos] = 0;
+ identBuf[identPos] = 0;
+ hostBuf[hostPos] = 0;
+
+ user.nick = nickBuf;
+ user.ident = identBuf;
+ user.hostmask = hostBuf;
+
+ user.isValid = true;
+ user.isSelf = (strcmp(nickBuf, currentNick) == 0);
+ }
+
+ // Get the command
+ char cmdBuf[512];
+ int cmdPos = 0;
+
+ while ((*line != ' ') && (*line != 0)) {
+ if (cmdPos < 511)
+ cmdBuf[cmdPos++] = *line;
+ ++line;
+ }
+ cmdBuf[cmdPos] = 0;
+
+ if (*line == 0) {
+ // Invalid line.
+ return;
+ }
+
+ ++line; // skip the space
+
+ // Skip the : if there is one
+ if (*line == ':')
+ ++line;
+
+ // Get the first param, or "target" in many cases
+ char *allParams = line;
+ char *paramsAfterFirst = line;
+
+ char targetBuf[512];
+ int targetPos = 0;
+
+ while ((*paramsAfterFirst != ' ') && (*paramsAfterFirst != 0)) {
+ if (targetPos < 511)
+ targetBuf[targetPos++] = *paramsAfterFirst;
+ ++paramsAfterFirst;
+ }
+
+ targetBuf[targetPos] = 0;
+
+ // If we didn't reach the end of the line, skip the space
+ if (*paramsAfterFirst == ' ')
+ ++paramsAfterFirst;
+
+ // And if the params begin with :, skip it
+ if (*paramsAfterFirst == ':')
+ ++paramsAfterFirst;
+
+ // Now figure out what to do with this...!
+
+ if (strcmp(cmdBuf, "PING") == 0) {
+ char out[512];
+ snprintf(out, 512, "PONG :%s", allParams);
+ sendLine(out);
+ return;
+
+ } else if (strcmp(cmdBuf, "JOIN") == 0) {
+ Channel *c = findChannel(targetBuf, true);
+ if (c) {
+ c->handleJoin(user);
+ return;
+ }
+
+ } else if (strcmp(cmdBuf, "PART") == 0) {
+ Channel *c = findChannel(targetBuf, false);
+ if (c) {
+ c->handlePart(user, paramsAfterFirst);;
+ return;
+ }
+
+ } else if (strcmp(cmdBuf, "QUIT") == 0) {
+ for (auto &i : channels)
+ i.second->handleQuit(user, allParams);
+ return;
+
+ } else if (strcmp(cmdBuf, "NICK") == 0) {
+ if (user.isSelf) {
+ strncpy(currentNick, allParams, sizeof(currentNick));
+ currentNick[sizeof(currentNick) - 1] = 0;
+
+ char buf[1024];
+ snprintf(buf, 1024, "You are now known as %s", currentNick);
+ status.pushMessage(buf);
+ }
+
+ for (auto &i : channels)
+ i.second->handleNick(user, allParams);
+ return;
+
+ } else if (strcmp(cmdBuf, "MODE") == 0) {
+ Channel *c = findChannel(targetBuf, false);
+ if (c) {
+ c->handleMode(user, paramsAfterFirst);
+ return;
+ }
+
+ } else if (strcmp(cmdBuf, "PRIVMSG") == 0) {
+ Channel *c = findChannel(targetBuf, true);
+ if (c) {
+ c->handlePrivmsg(user, paramsAfterFirst);
+ return;
+ }
+
+ } else if (strcmp(cmdBuf, "001") == 0) {
+ status.pushMessage("[debug: currentNick change detected]");
+
+ strncpy(currentNick, targetBuf, sizeof(currentNick));
+ currentNick[sizeof(currentNick) - 1] = 0;
+ return;
+
+ } else if (strcmp(cmdBuf, "005") == 0) {
+ processISupport(paramsAfterFirst);
+ return;
+
+ } else if (strcmp(cmdBuf, "353") == 0) {
+ // RPL_NAMEREPLY:
+ // Target is always us
+ // Params: Channel privacy flag, channel, user list
+
+ char *space1 = strchr(paramsAfterFirst, ' ');
+ if (space1) {
+ char *space2 = strchr(space1 + 1, ' ');
+ if (space2) {
+ char *chanName = space1 + 1;
+ *space2 = 0;
+
+ char *userNames = space2 + 1;
+ if (*userNames == ':')
+ ++userNames;
+
+ Channel *c = findChannel(chanName, false);
+
+ if (c) {
+ c->handleNameReply(userNames);
+ return;
+ }
+ }
+ }
+ }
+
+ status.pushMessage("!! Unhandled !!");
+}
+
+
+void IRCServer::processISupport(const char *line) {
+ while (*line != 0) {
+ char keyBuf[512], valueBuf[512];
+ int keyPos = 0, valuePos = 0;
+ int phase = 0;
+
+ // This means we've reached the end
+ if (*line == ':')
+ return;
+
+ while ((*line != 0) && (*line != ' ')) {
+ if (phase == 0) {
+ if (*line == '=')
+ phase = 1;
+ else if (keyPos < 511)
+ keyBuf[keyPos++] = *line;
+ } else {
+ if (valuePos < 511)
+ valueBuf[valuePos++] = *line;
+ }
+
+ ++line;
+ }
+
+ if (*line == ' ')
+ ++line;
+
+ keyBuf[keyPos] = 0;
+ valueBuf[valuePos] = 0;
+
+
+ // Now process the thing
+
+ if (strcmp(keyBuf, "PREFIX") == 0) {
+ int prefixCount = (valuePos - 2) / 2;
+
+ if (valueBuf[0] == '(' && valueBuf[1+prefixCount] == ')') {
+ if (prefixCount < 32) {
+ strncpy(serverPrefixMode, &valueBuf[1], prefixCount);
+ strncpy(serverPrefix, &valueBuf[2+prefixCount], prefixCount);
+
+ serverPrefixMode[prefixCount] = 0;
+ serverPrefix[prefixCount] = 0;
+ }
+ }
+ } else if (strcmp(keyBuf, "CHANMODES") == 0) {
+ char *proc = &valueBuf[0];
+
+ for (int index = 0; index < 4; index++) {
+ if (*proc == 0)
+ break;
+
+ char *start = proc;
+ char *end = proc;
+
+ while ((*end != ',') && (*end != 0))
+ ++end;
+
+ // If this is a zero, we can't read any more
+ bool endsHere = (*end == 0);
+ *end = 0;
+
+ serverChannelModes[index] = start;
+ char moof[1000];
+ sprintf(moof, "set chanmodes %d to [%s]", index, serverChannelModes[index].c_str());
+ status.pushMessage(moof);
+
+ if (endsHere)
+ break;
+ else
+ proc = end + 1;
+ }
+ }
+ }
+}
+
+
+uint32_t IRCServer::getUserFlag(char search, const char *array) const {
+ uint32_t flag = 1;
+
+ // Is this character a valid prefix?
+ while (*array != 0) {
+ if (*array == search)
+ return flag;
+
+ flag <<= 1;
+ ++array;
+ }
+}
+
+uint32_t IRCServer::getUserFlagByPrefix(char prefix) const {
+ return getUserFlag(prefix, serverPrefix);
+}
+uint32_t IRCServer::getUserFlagByMode(char mode) const {
+ return getUserFlag(mode, serverPrefixMode);
+}
+
+int IRCServer::getChannelModeType(char mode) const {
+ for (int i = 0; i < 4; i++) {
+ const char *modes = serverChannelModes[i].c_str();
+
+ while (*modes != 0) {
+ if (*modes == mode)
+ return i + 1;
+ ++modes;
+ }
+ }
+
+ return 0;
+}
diff --git a/bouncer/main.cpp b/bouncer/main.cpp
new file mode 100644
index 0000000..5330310
--- /dev/null
+++ b/bouncer/main.cpp
@@ -0,0 +1,61 @@
+#include "core.h"
+#include "dns.h"
+
+#ifdef USE_GNUTLS
+static gnutls_dh_params_t dh_params;
+gnutls_certificate_credentials_t g_serverCreds, g_clientCreds;
+
+bool initTLS() {
+ int ret;
+ ret = gnutls_global_init();
+ if (ret != GNUTLS_E_SUCCESS) {
+ printf("gnutls_global_init failure: %s\n", gnutls_strerror(ret));
+ return false;
+ }
+
+ unsigned int bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY);
+
+ ret = gnutls_dh_params_init(&dh_params);
+ if (ret != GNUTLS_E_SUCCESS) {
+ printf("dh_params_init failure: %s\n", gnutls_strerror(ret));
+ return false;
+ }
+
+ ret = gnutls_dh_params_generate2(dh_params, bits);
+ if (ret != GNUTLS_E_SUCCESS) {
+ printf("dh_params_generate2 failure: %s\n", gnutls_strerror(ret));
+ return false;
+ }
+
+ gnutls_certificate_allocate_credentials(&g_clientCreds);
+ ret = gnutls_certificate_set_x509_key_file(g_clientCreds, "ssl_test.crt", "ssl_test.key", GNUTLS_X509_FMT_PEM);
+ if (ret != GNUTLS_E_SUCCESS) {
+ printf("set_x509_key_file failure: %s\n", gnutls_strerror(ret));
+ return false;
+ }
+ gnutls_certificate_set_dh_params(g_clientCreds, dh_params);
+
+ gnutls_certificate_allocate_credentials(&g_serverCreds);
+
+ return true;
+}
+#endif
+
+int main(int argc, char **argv) {
+#ifdef USE_GNUTLS
+ if (!initTLS())
+ return EXIT_FAILURE;
+#endif
+
+ DNS::start();
+
+ Bouncer bounce;
+
+ int errcode = bounce.execute();
+ if (errcode < 0) {
+ printf("(Bouncer::execute failed with %d)\n", errcode);
+ return EXIT_FAILURE;
+ } else {
+ return EXIT_SUCCESS;
+ }
+} \ No newline at end of file
diff --git a/bouncer/mobileclient.cpp b/bouncer/mobileclient.cpp
new file mode 100644
index 0000000..fb8b3c1
--- /dev/null
+++ b/bouncer/mobileclient.cpp
@@ -0,0 +1,77 @@
+#include "core.h"
+
+MobileClient::MobileClient(Bouncer *_bouncer) : Client(_bouncer) {
+ bouncer = _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");
+}
+void MobileClient::packetReceivedEvent(Packet::Type type, Buffer &pkt) {
+ if (type == Packet::C2B_COMMAND) {
+ char cmd[2048];
+ 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());
+ }
+}
+
+void MobileClient::handleDebugCommand(char *line, int size) {
+ // This is a terrible mess that will be replaced shortly
+ if (strncmp(line, "all ", 4) == 0) {
+ Buffer pkt;
+ pkt.writeStr(&line[4]);
+ for (int i = 0; i < bouncer->clientCount; i++)
+ bouncer->clients[i]->sendPacket(Packet::B2C_STATUS, pkt);
+
+ } else if (strcmp(line, "quit") == 0) {
+ bouncer->quitFlag = true;
+ } else if (strncmp(&line[1], "ddsrv ", 6) == 0) {
+ IRCServer *srv = new IRCServer(bouncer);
+ strcpy(srv->config.hostname, &line[7]);
+ srv->config.useTls = (line[0] == 's');
+ srv->config.port = (line[0] == 's') ? 1191 : 6667;
+ strcpy(srv->config.nickname, "Ninjifox");
+ strcpy(srv->config.username, "boop");
+ strcpy(srv->config.realname, "boop");
+ strcpy(srv->config.password, "");
+ bouncer->registerServer(srv);
+
+ Buffer pkt;
+ pkt.writeStr("Your wish is my command!");
+ for (int i = 0; i < bouncer->clientCount; i++)
+ bouncer->clients[i]->sendPacket(Packet::B2C_STATUS, pkt);
+ }
+}
+
diff --git a/bouncer/netcore.cpp b/bouncer/netcore.cpp
new file mode 100644
index 0000000..dee6ef7
--- /dev/null
+++ b/bouncer/netcore.cpp
@@ -0,0 +1,330 @@
+#include "core.h"
+
+
+NetCore::NetCore() {
+ clientCount = 0;
+ for (int i = 0; i < CLIENT_LIMIT; i++)
+ clients[i] = NULL;
+ serverCount = 0;
+ for (int i = 0; i < SERVER_LIMIT; i++)
+ servers[i] = NULL;
+
+ nextWindowID = 1;
+}
+
+Client *NetCore::findClientWithSessionKey(uint8_t *key) const {
+ for (int i = 0; i < clientCount; i++)
+ if (!memcmp(clients[i]->sessionKey, key, SESSION_KEY_SIZE))
+ return clients[i];
+
+ return 0;
+}
+
+int NetCore::registerServer(Server *server) {
+ if (serverCount >= SERVER_LIMIT)
+ return -1;
+
+ int id = serverCount++;
+ servers[id] = server;
+ server->attachedToCore();
+ return id;
+}
+void NetCore::deregisterServer(int id) {
+ Server *server = servers[id];
+ server->close();
+ delete server;
+
+ serverCount--;
+ servers[id] = servers[serverCount];
+}
+int NetCore::findServerID(Server *server) const {
+ for (int i = 0; i < SERVER_LIMIT; i++)
+ if (servers[i] == server)
+ return i;
+ return -1;
+}
+
+int NetCore::execute() {
+ // prepare the listen socket
+ int listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (listener == -1) {
+ perror("Could not create the listener socket");
+ return -1;
+ }
+
+ int v = 1;
+ if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)) == -1) {
+ perror("Could not set SO_REUSEADDR");
+ return -2;
+ }
+
+ sockaddr_in listenAddr;
+ listenAddr.sin_family = AF_INET;
+ listenAddr.sin_port = htons(5454);
+ listenAddr.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ if (bind(listener, (sockaddr *)&listenAddr, sizeof(listenAddr)) == -1) {
+ perror("Could not bind to the listener socket");
+ return -3;
+ }
+
+ if (!SocketRWCommon::setSocketNonBlocking(listener)) {
+ perror("[Listener] Could not set non-blocking");
+ return -4;
+ }
+
+ if (listen(listener, 10) == -1) {
+ perror("Could not listen()");
+ return -5;
+ }
+
+ printf("Listening!\n");
+
+
+ // do stuff!
+ while (!quitFlag) {
+ fd_set readSet, writeSet;
+ FD_ZERO(&readSet);
+ FD_ZERO(&writeSet);
+
+ int maxFD = listener;
+ FD_SET(listener, &readSet);
+
+ time_t now = time(NULL);
+
+ for (int i = 0; i < clientCount; i++) {
+#ifdef USE_GNUTLS
+ if (clients[i]->state == Client::CS_TLS_HANDSHAKE)
+ clients[i]->tryTLSHandshake();
+#endif
+
+ if (clients[i]->sock != -1) {
+ if (clients[i]->sock > maxFD)
+ maxFD = clients[i]->sock;
+
+ if (clients[i]->state == Client::CS_CONNECTED)
+ FD_SET(clients[i]->sock, &readSet);
+ if (clients[i]->outputBuf.size() > 0)
+ FD_SET(clients[i]->sock, &writeSet);
+
+ } else {
+ // Outdated session, can we kill it?
+ if (now >= clients[i]->deadTime) {
+ printf("[%d] Session expired, deleting\n", now);
+
+ // Yep.
+ Client *client = clients[i];
+ if (client->authState == Client::AS_AUTHED)
+ client->sessionEndEvent();
+ delete client;
+
+ // If this is the last socket in the list, we can just
+ // decrement clientCount and all will be fine.
+ clientCount--;
+
+ // Otherwise, we move that pointer into this slot, and
+ // we subtract one from i so that we'll process that slot
+ // on the next loop iteration.
+ if (i != clientCount) {
+ clients[i] = clients[clientCount];
+ i--;
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < serverCount; i++) {
+ if (servers[i]->state == Server::CS_WAITING_DNS)
+ servers[i]->tryConnectPhase();
+#ifdef USE_GNUTLS
+ else if (servers[i]->state == Server::CS_TLS_HANDSHAKE) {
+ if (servers[i]->tryTLSHandshake())
+ servers[i]->connectedEvent();
+ }
+#endif
+
+ if (servers[i]->sock != -1) {
+ if (servers[i]->sock > maxFD)
+ maxFD = servers[i]->sock;
+
+ if (servers[i]->state == Server::CS_CONNECTED)
+ FD_SET(servers[i]->sock, &readSet);
+ if (servers[i]->outputBuf.size() > 0 || servers[i]->state == Server::CS_WAITING_CONNECT)
+ FD_SET(servers[i]->sock, &writeSet);
+ }
+ }
+
+ timeval timeout;
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ int numFDs = select(maxFD+1, &readSet, &writeSet, NULL, &timeout);
+
+ now = time(NULL);
+ //printf("[%lu select:%d]\n", now, numFDs);
+
+
+ for (int i = 0; i < clientCount; i++) {
+ if (clients[i]->sock != -1) {
+ if (FD_ISSET(clients[i]->sock, &writeSet))
+ clients[i]->writeAction();
+
+ if (FD_ISSET(clients[i]->sock, &readSet)
+#ifdef USE_GNUTLS
+ || clients[i]->hasTlsPendingData()
+#endif
+ )
+ {
+ clients[i]->readAction();
+ }
+ }
+ }
+
+ for (int i = 0; i < serverCount; i++) {
+ if (servers[i]->sock != -1) {
+ if (FD_ISSET(servers[i]->sock, &writeSet)) {
+ Server *server = servers[i];
+
+ if (server->state == Server::CS_WAITING_CONNECT) {
+ // Welp, this means we're connected!
+ // Maybe.
+ // We might have an error condition, in which case,
+ // we're screwed.
+ bool didSucceed = false;
+ int sockErr;
+ socklen_t sockErrSize = sizeof(sockErr);
+
+ if (getsockopt(server->sock, SOL_SOCKET, SO_ERROR, &sockErr, &sockErrSize) == 0) {
+ if (sockErr == 0)
+ didSucceed = true;
+ }
+
+ if (didSucceed) {
+ // WE'RE IN fuck yeah
+ printf("[%d] Connection succeeded!\n", i);
+ server->connectionSuccessful();
+ } else {
+ // Nope. Nuke it.
+ printf("[%d] Connection failed: %d\n", i, sockErr);
+ server->close();
+ }
+
+ } else {
+ server->writeAction();
+ }
+ }
+
+
+ if (FD_ISSET(servers[i]->sock, &readSet)
+#ifdef USE_GNUTLS
+ || servers[i]->hasTlsPendingData()
+#endif
+ )
+ {
+ servers[i]->readAction();
+ }
+ }
+ }
+
+
+
+ if (FD_ISSET(listener, &readSet)) {
+ // Yay, we have a new connection
+ int sock = accept(listener, NULL, NULL);
+
+ if (clientCount >= CLIENT_LIMIT) {
+ // We can't accept it.
+ printf("Too many connections, we can't accept this one. THIS SHOULD NEVER HAPPEN.\n");
+ shutdown(sock, SHUT_RDWR);
+ close(sock);
+ } else {
+ // Create a new connection
+ printf("[%d] New connection, fd=%d\n", clientCount, sock);
+
+ Client *client = constructClient();
+
+ clients[clientCount] = client;
+ ++clientCount;
+
+ client->startService(sock, SERVE_VIA_TLS);
+ }
+ }
+ }
+
+ // Need to shut down all sockets here
+ for (int i = 0; i < serverCount; i++)
+ servers[i]->close();
+
+ for (int i = 0; i < clientCount; i++)
+ clients[i]->close();
+
+ shutdown(listener, SHUT_RDWR);
+ close(listener);
+
+ for (int i = 0; i < serverCount; i++)
+ delete servers[i];
+ for (int i = 0; i < clientCount; i++)
+ delete clients[i];
+
+ serverCount = clientCount = 0;
+
+ return 0;
+}
+
+
+
+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;
+}
+
+
+void NetCore::sendToClients(Packet::Type type, const Buffer &data) {
+ for (int i = 0; i < clientCount; i++)
+ if (clients[i]->isAuthed())
+ clients[i]->sendPacket(type, data);
+}
+
+
+
+Client *Bouncer::constructClient() {
+ return new MobileClient(this);
+}
+
+
+
+
diff --git a/bouncer/server.cpp b/bouncer/server.cpp
new file mode 100644
index 0000000..16c754b
--- /dev/null
+++ b/bouncer/server.cpp
@@ -0,0 +1,160 @@
+#include "core.h"
+#include "dns.h"
+
+Server::Server(NetCore *_netCore) : SocketRWCommon(_netCore) {
+ dnsQueryId = -1;
+}
+Server::~Server() {
+ if (dnsQueryId != -1)
+ DNS::closeQuery(dnsQueryId);
+ close();
+}
+
+
+
+void Server::processReadBuffer() {
+ // Try to process as many lines as we can
+ char *buf = inputBuf.data();
+ int bufSize = inputBuf.size();
+ int lineBegin = 0, pos = 0;
+
+ while (pos < bufSize) {
+ if (buf[pos] == '\r' || buf[pos] == '\n') {
+ if (pos > lineBegin) {
+ buf[pos] = 0;
+ lineReceivedEvent(&buf[lineBegin], pos - lineBegin);
+ }
+
+ lineBegin = pos + 1;
+ }
+
+ pos++;
+ }
+
+ // If we managed to handle anything, lop it off the buffer
+ inputBuf.trimFromStart(lineBegin);
+}
+
+void Server::sendLine(const char *line) {
+ outputBuf.append(line, strlen(line));
+ outputBuf.append("\r\n", 2);
+}
+
+
+void Server::connect(const char *hostname, int _port, bool _useTls) {
+ if (state == CS_DISCONNECTED) {
+ port = _port;
+ useTls = _useTls;
+
+ DNS::closeQuery(dnsQueryId); // just in case
+ dnsQueryId = DNS::makeQuery(hostname);
+
+ if (dnsQueryId == -1) {
+ // TODO: better error reporting
+ printf("DNS query failed!\n");
+ } else {
+ state = CS_WAITING_DNS;
+ }
+ }
+}
+
+void Server::tryConnectPhase() {
+ if (state == CS_WAITING_DNS) {
+ in_addr result;
+ bool isError;
+
+ if (DNS::checkQuery(dnsQueryId, &result, &isError)) {
+ DNS::closeQuery(dnsQueryId);
+ dnsQueryId = -1;
+
+ if (isError) {
+ printf("DNS query failed at phase 2!\n");
+ state = CS_DISCONNECTED;
+ } else {
+ // OK, if there was no error, we can go ahead and do this...
+
+ sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (sock == -1) {
+ perror("[Server] Failed to socket()");
+ close();
+ return;
+ }
+
+ if (!setSocketNonBlocking(sock)) {
+ perror("[Server] Could not set non-blocking");
+ close();
+ return;
+ }
+
+ // We have our non-blocking socket, let's try connecting!
+ sockaddr_in outAddr;
+ outAddr.sin_family = AF_INET;
+ outAddr.sin_port = htons(port);
+ outAddr.sin_addr.s_addr = result.s_addr;
+
+ if (::connect(sock, (sockaddr *)&outAddr, sizeof(outAddr)) == -1) {
+ if (errno == EINPROGRESS) {
+ state = CS_WAITING_CONNECT;
+ } else {
+ perror("[Server] Could not connect");
+ close();
+ }
+ } else {
+ // Whoa, we're connected? Neat.
+ connectionSuccessful();
+ }
+ }
+ }
+ }
+}
+
+void Server::connectionSuccessful() {
+ state = CS_CONNECTED;
+
+ inputBuf.clear();
+ outputBuf.clear();
+
+ // Do we need to do any TLS junk?
+#ifdef USE_GNUTLS
+ if (useTls) {
+ state = CS_TLS_HANDSHAKE;
+
+ int initRet = gnutls_init(&tls, GNUTLS_CLIENT);
+ if (initRet != GNUTLS_E_SUCCESS) {
+ printf("[Server::connectionSuccessful] gnutls_init borked\n");
+ gnutls_perror(initRet);
+ close();
+ return;
+ }
+
+ // TODO: error check this
+ const char *errPos;
+ gnutls_priority_set_direct(tls, "NORMAL", &errPos);
+
+ gnutls_credentials_set(tls, GNUTLS_CRD_CERTIFICATE, g_serverCreds);
+
+ gnutls_transport_set_int(tls, sock);
+
+ tlsActive = true;
+ } else
+#endif
+ {
+ connectedEvent();
+ }
+}
+
+void Server::close() {
+ int saveState = state;
+
+ SocketRWCommon::close();
+
+ if (dnsQueryId != -1) {
+ DNS::closeQuery(dnsQueryId);
+ dnsQueryId = -1;
+ }
+
+ if (saveState == CS_CONNECTED)
+ disconnectedEvent();
+}
+
+
diff --git a/bouncer/socketcommon.cpp b/bouncer/socketcommon.cpp
new file mode 100644
index 0000000..7bc55b6
--- /dev/null
+++ b/bouncer/socketcommon.cpp
@@ -0,0 +1,174 @@
+#include "core.h"
+
+/*static*/ bool SocketRWCommon::setSocketNonBlocking(int sock) {
+ int opts = fcntl(sock, F_GETFL);
+ if (opts < 0) {
+ perror("Could not get fcntl options\n");
+ return false;
+ }
+ opts |= O_NONBLOCK;
+ if (fcntl(sock, F_SETFL, opts) == -1) {
+ perror("Could not set fcntl options\n");
+ return false;
+ }
+ return true;
+}
+
+
+SocketRWCommon::SocketRWCommon(NetCore *_netCore) {
+ netCore = _netCore;
+ sock = -1;
+ state = CS_DISCONNECTED;
+#ifdef USE_GNUTLS
+ tlsActive = false;
+#endif
+}
+SocketRWCommon::~SocketRWCommon() {
+ close();
+}
+
+#ifdef USE_GNUTLS
+bool SocketRWCommon::hasTlsPendingData() const {
+ if (tlsActive)
+ return (gnutls_record_check_pending(tls) > 0);
+ else
+ return false;
+}
+
+bool SocketRWCommon::tryTLSHandshake() {
+ int hsRet = gnutls_handshake(tls);
+ if (gnutls_error_is_fatal(hsRet)) {
+ printf("[SocketRWCommon::tryTLSHandshake] gnutls_handshake borked\n");
+ gnutls_perror(hsRet);
+ close();
+ return false;
+ }
+
+ if (hsRet == GNUTLS_E_SUCCESS) {
+ // We're in !!
+ state = CS_CONNECTED;
+
+ inputBuf.clear();
+ outputBuf.clear();
+
+ printf("[SocketRWCommon connected via SSL!]\n");
+ return true;
+ }
+
+ return false;
+}
+#endif
+
+void SocketRWCommon::close() {
+ if (sock != -1) {
+#ifdef USE_GNUTLS
+ if (tlsActive)
+ gnutls_bye(tls, GNUTLS_SHUT_RDWR);
+#endif
+ shutdown(sock, SHUT_RDWR);
+ ::close(sock);
+ }
+
+ sock = -1;
+ inputBuf.clear();
+ outputBuf.clear();
+ state = CS_DISCONNECTED;
+
+#ifdef USE_GNUTLS
+ if (tlsActive) {
+ gnutls_deinit(tls);
+ tlsActive = false;
+ }
+#endif
+}
+
+void SocketRWCommon::readAction() {
+ // Ensure we have at least 0x200 bytes space free
+ // (Up this, maybe?)
+ int bufSize = inputBuf.size();
+ int requiredSize = bufSize + 0x200;
+ if (requiredSize > inputBuf.capacity())
+ inputBuf.setCapacity(requiredSize);
+
+ ssize_t amount;
+
+#ifdef USE_GNUTLS
+ if (tlsActive) {
+ amount = gnutls_record_recv(tls,
+ &inputBuf.data()[bufSize],
+ 0x200);
+ } else
+#endif
+ {
+ amount = recv(sock,
+ &inputBuf.data()[bufSize],
+ 0x200,
+ 0);
+ }
+
+
+ if (amount > 0) {
+ // Yep, we have data
+ printf("[fd=%d] Read %d bytes\n", sock, amount);
+ inputBuf.resize(bufSize + amount);
+
+ processReadBuffer();
+
+ } else if (amount == 0) {
+ printf("[fd=%d] Read 0! Socket closing.\n", sock);
+ close();
+
+ } else if (amount < 0) {
+#ifdef USE_GNUTLS
+ if (tlsActive) {
+ if (gnutls_error_is_fatal(amount)) {
+ printf("Error while reading [gnutls %d]!\n", amount);
+ close();
+ }
+ } else
+#endif
+ {
+ perror("Error while reading!");
+ close();
+ }
+ }
+}
+
+void SocketRWCommon::writeAction() {
+ // What can we get rid of...?
+ ssize_t amount;
+
+#ifdef USE_GNUTLS
+ if (tlsActive) {
+ amount = gnutls_record_send(tls,
+ outputBuf.data(),
+ outputBuf.size());
+ } else
+#endif
+ {
+ amount = send(sock,
+ outputBuf.data(),
+ outputBuf.size(),
+ 0);
+ }
+
+ if (amount > 0) {
+ 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");
+ else if (amount < 0) {
+#ifdef USE_GNUTLS
+ if (tlsActive) {
+ if (gnutls_error_is_fatal(amount)) {
+ printf("Error while sending [gnutls %d]!\n", amount);
+ close();
+ }
+ } else
+#endif
+ {
+ perror("Error while sending!");
+ close();
+ }
+ }
+}
diff --git a/bouncer/window.cpp b/bouncer/window.cpp
new file mode 100644
index 0000000..c971046
--- /dev/null
+++ b/bouncer/window.cpp
@@ -0,0 +1,468 @@
+#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);
+ }
+}
+
+
+
+
+Channel::Channel(IRCServer *_server, const char *_name) :
+ Window(_server->bouncer),
+ server(_server),
+ inChannel(false),
+ name(_name)
+{
+ server->bouncer->registerWindow(this);
+}
+
+const char *Channel::getTitle() const {
+ return name.c_str();
+}
+
+int Channel::getType() const {
+ return 2;
+}
+
+void Channel::handleUserInput(const char *str) {
+ char msgBuf[16384];
+
+ if (str[0] == '/') {
+ if (strncmp(str, "/me ", 4) == 0) {
+ // The duplication of code between here and
+ // handlePrivmsg is ugly. TODO: fixme.
+ char prefix[2];
+ prefix[0] = getEffectivePrefixChar(server->currentNick);
+ prefix[1] = 0;
+
+ snprintf(msgBuf, sizeof(msgBuf),
+ "* %s%s %s",
+ prefix,
+ server->currentNick,
+ &str[4]);
+ pushMessage(msgBuf);
+
+ snprintf(msgBuf, sizeof(msgBuf),
+ "PRIVMSG %s :\x01" "ACTION %s\x01",
+ name.c_str(),
+ &str[4]);
+ server->sendLine(msgBuf);
+ }
+ } else {
+ // Aaaand this is also pretty ugly ><;;
+ // TODO: fixme.
+ char prefix[2];
+ prefix[0] = getEffectivePrefixChar(server->currentNick);
+ prefix[1] = 0;
+
+ snprintf(msgBuf, sizeof(msgBuf),
+ "<%s%s> %s",
+ prefix,
+ server->currentNick,
+ str);
+ pushMessage(msgBuf);
+
+ snprintf(msgBuf, sizeof(msgBuf),
+ "PRIVMSG %s :%s",
+ name.c_str(),
+ str);
+ server->sendLine(msgBuf);
+ }
+}
+
+void Channel::syncStateForClient(Buffer &output) {
+ Window::syncStateForClient(output);
+
+ output.writeU32(users.size());
+
+ for (auto &i : users) {
+ output.writeStr(i.first.c_str());
+ output.writeU32(i.second);
+ }
+
+ output.writeStr(topic.c_str());
+}
+
+
+void Channel::handleNameReply(const char *str) {
+ char copy[4096];
+ strncpy(copy, str, 4096);
+ copy[4095] = 0;
+
+ char *strtok_var;
+ char *name = strtok_r(copy, " ", &strtok_var);
+
+ int nameCount = 0;
+
+ Buffer packet;
+ packet.writeU32(id);
+ packet.writeU32(0); // Dummy value..!
+
+ while (name) {
+ uint32_t modes = 0;
+
+ // Check the beginning of the name for as many valid
+ // mode prefixes as possible
+ // Servers may only send one, but I want to take into
+ // account the possibility that they might send multiple
+ // ones. Just in case.
+
+ while (*name != 0) {
+ uint32_t flag = server->getUserFlagByPrefix(*name);
+
+ if (flag == 0)
+ break;
+ else
+ modes |= flag;
+
+ ++name;
+ }
+
+ // Got it!
+ users[name] = modes;
+
+ nameCount++;
+ //packet.writeU8(getEffectivePrefixChar(name));
+ packet.writeStr(name);
+ packet.writeU32(modes);
+
+ // Get the next name
+ name = strtok_r(NULL, " ", &strtok_var);
+ }
+
+ if (nameCount > 0) {
+ uint32_t nameCountU32 = nameCount;
+ memcpy(&packet.data()[4], &nameCountU32, sizeof(uint32_t));
+
+ server->bouncer->sendToClients(
+ Packet::B2C_CHANNEL_USER_ADD, packet);
+ }
+}
+
+void Channel::handleJoin(const UserRef &user) {
+ if (user.isSelf) {
+ Buffer packet;
+ packet.writeU32(id);
+ packet.writeU32(0);
+
+ server->bouncer->sendToClients(
+ Packet::B2C_CHANNEL_USER_REMOVE, packet);
+
+
+ users.clear();
+
+ inChannel = true;
+ pushMessage("You have joined the channel!");
+ } else {
+ Buffer packet;
+ packet.writeU32(id);
+ packet.writeU32(1);
+ packet.writeStr(user.nick.c_str());
+ packet.writeU32(0);
+
+ server->bouncer->sendToClients(
+ Packet::B2C_CHANNEL_USER_ADD, packet);
+
+ users[user.nick] = 0;
+
+ char buf[1024];
+ snprintf(buf, 1024,
+ "%s (%s@%s) has joined",
+ user.nick.c_str(),
+ user.ident.c_str(),
+ user.hostmask.c_str());
+
+ pushMessage(buf);
+ }
+}
+
+void Channel::handlePart(const UserRef &user, const char *message) {
+ auto i = users.find(user.nick);
+ if (i != users.end()) {
+ users.erase(i);
+
+ Buffer packet;
+ packet.writeU32(id);
+ packet.writeU32(1);
+ packet.writeStr(user.nick.c_str());
+
+ server->bouncer->sendToClients(
+ Packet::B2C_CHANNEL_USER_REMOVE, packet);
+ }
+
+ char buf[1024];
+
+ if (user.isSelf) {
+ inChannel = false;
+
+ snprintf(buf, 1024,
+ "You have left the channel (%s)",
+ message);
+ pushMessage(buf);
+ } else {
+ snprintf(buf, 1024,
+ "%s (%s@%s) has parted (%s)",
+ user.nick.c_str(),
+ user.ident.c_str(),
+ user.hostmask.c_str(),
+ message);
+
+ pushMessage(buf);
+ }
+}
+
+void Channel::handleQuit(const UserRef &user, const char *message) {
+ if (user.isSelf)
+ inChannel = false;
+
+ auto i = users.find(user.nick);
+ if (i == users.end())
+ return;
+
+ users.erase(i);
+
+ Buffer packet;
+ packet.writeU32(id);
+ packet.writeU32(1);
+ packet.writeStr(user.nick.c_str());
+
+ server->bouncer->sendToClients(
+ Packet::B2C_CHANNEL_USER_REMOVE, packet);
+
+ char buf[1024];
+
+ snprintf(buf, 1024,
+ "%s (%s@%s) has quit (%s)",
+ user.nick.c_str(),
+ user.ident.c_str(),
+ user.hostmask.c_str(),
+ message);
+
+ pushMessage(buf);
+}
+
+void Channel::handleNick(const UserRef &user, const char *newNick) {
+ auto i = users.find(user.nick);
+ if (i == users.end())
+ return;
+
+ users[newNick] = i->second;
+ users.erase(i);
+
+ Buffer packet;
+ packet.writeU32(id);
+ packet.writeStr(user.nick.c_str());
+ packet.writeStr(newNick);
+
+ server->bouncer->sendToClients(
+ Packet::B2C_CHANNEL_USER_RENAME, packet);
+
+ char buf[1024];
+ snprintf(buf, 1024,
+ "%s is now known as %s",
+ user.nick.c_str(),
+ newNick);
+
+ pushMessage(buf);
+}
+
+void Channel::handleMode(const UserRef &user, const char *str) {
+ char copy[4096];
+ strncpy(copy, str, 4096);
+ copy[4095] = 0;
+
+ char *strtok_var;
+ char *modes = strtok_r(copy, " ", &strtok_var);
+
+ if (!modes)
+ return;
+
+ bool addFlag = true;
+
+ while (*modes != 0) {
+ char mode = *(modes++);
+
+ uint32_t flag;
+
+ if (mode == '+') {
+ addFlag = true;
+ } else if (mode == '-') {
+ addFlag = false;
+
+ } else if ((flag = server->getUserFlagByMode(mode)) != 0) {
+ bool oops = false;
+ char *target = strtok_r(NULL, " ", &strtok_var);
+
+ auto i = users.find(target);
+ if (i == users.end()) {
+ // Oops? Spit out an error...
+ oops = true;
+ } else {
+ // TODO: push mode change to clients
+ uint32_t flags = i->second;
+ if (addFlag)
+ flags |= flag;
+ else
+ flags &= ~flag;
+ users[target] = flags;
+ }
+
+ char buf[1024];
+ snprintf(buf, 1024,
+ "%s %s mode %c on %s%s",
+ user.nick.c_str(),
+ addFlag ? "set" : "cleared",
+ mode,
+ target,
+ oops ? ", but something went wrong!" : "");
+ pushMessage(buf);
+
+ } else {
+ int type = server->getChannelModeType(mode);
+ char *param = 0;
+
+ switch (type) {
+ case 1:
+ case 2:
+ // Always get a parameter
+ param = strtok_r(NULL, " ", &strtok_var);
+ break;
+ case 3:
+ // Only get a parameter if adding
+ if (addFlag)
+ param = strtok_r(NULL, " ", &strtok_var);
+ break;
+ }
+
+ char buf[1024];
+ snprintf(buf, 1024,
+ "%s %s channel mode %c%s%s",
+ user.nick.c_str(),
+ addFlag ? "set" : "cleared",
+ mode,
+ param ? " " : "",
+ param ? param : "");
+ pushMessage(buf);
+ }
+ }
+}
+
+void Channel::handlePrivmsg(const UserRef &user, const char *str) {
+ char prefix[2];
+ prefix[0] = getEffectivePrefixChar(user.nick.c_str());
+ prefix[1] = 0;
+
+ char buf[15000];
+ snprintf(buf, 15000,
+ "<%s%s> %s",
+ prefix,
+ user.nick.c_str(),
+ str);
+
+ pushMessage(buf);
+}
+
+
+char Channel::getEffectivePrefixChar(const char *nick) const {
+ auto i = users.find(nick);
+ if (i == users.end())
+ return 0;
+
+ // Maybe this bit would work best as an IRCServer method?
+
+ uint32_t modes = i->second;
+ uint32_t flag = 1;
+ char *prefixes = server->serverPrefix;
+
+ while (*prefixes != 0) {
+ if (modes & flag)
+ return *prefixes;
+
+ ++prefixes;
+ flag <<= 1;
+ }
+
+ return 0;
+}
+
+
+void Channel::disconnected() {
+ if (inChannel) {
+ inChannel = false;
+ pushMessage("You have been disconnected.");
+ }
+}