From 22b35add6baa191b6c82347686e2599d848f2cb7 Mon Sep 17 00:00:00 2001 From: Treeki Date: Thu, 23 Jan 2014 23:45:19 +0100 Subject: move bouncer files into the bouncer directory --- bouncer/buffer.h | 197 ++++++++++++++++++++ bouncer/build.sh | 9 + bouncer/build_static.sh | 9 + bouncer/client.cpp | 331 +++++++++++++++++++++++++++++++++ bouncer/core.h | 373 +++++++++++++++++++++++++++++++++++++ bouncer/dns.cpp | 159 ++++++++++++++++ bouncer/dns.h | 15 ++ bouncer/ircserver.cpp | 393 +++++++++++++++++++++++++++++++++++++++ bouncer/main.cpp | 61 ++++++ bouncer/mobileclient.cpp | 77 ++++++++ bouncer/netcore.cpp | 330 +++++++++++++++++++++++++++++++++ bouncer/server.cpp | 160 ++++++++++++++++ bouncer/socketcommon.cpp | 174 ++++++++++++++++++ bouncer/window.cpp | 468 +++++++++++++++++++++++++++++++++++++++++++++++ buffer.h | 197 -------------------- build.sh | 9 - build_static.sh | 9 - client.cpp | 331 --------------------------------- core.h | 373 ------------------------------------- dns.cpp | 159 ---------------- dns.h | 15 -- ircserver.cpp | 393 --------------------------------------- main.cpp | 61 ------ mobileclient.cpp | 77 -------- netcore.cpp | 330 --------------------------------- server.cpp | 160 ---------------- socketcommon.cpp | 174 ------------------ window.cpp | 468 ----------------------------------------------- 28 files changed, 2756 insertions(+), 2756 deletions(-) create mode 100644 bouncer/buffer.h create mode 100755 bouncer/build.sh create mode 100755 bouncer/build_static.sh create mode 100644 bouncer/client.cpp create mode 100644 bouncer/core.h create mode 100644 bouncer/dns.cpp create mode 100644 bouncer/dns.h create mode 100644 bouncer/ircserver.cpp create mode 100644 bouncer/main.cpp create mode 100644 bouncer/mobileclient.cpp create mode 100644 bouncer/netcore.cpp create mode 100644 bouncer/server.cpp create mode 100644 bouncer/socketcommon.cpp create mode 100644 bouncer/window.cpp delete mode 100644 buffer.h delete mode 100755 build.sh delete mode 100755 build_static.sh delete mode 100644 client.cpp delete mode 100644 core.h delete mode 100644 dns.cpp delete mode 100644 dns.h delete mode 100644 ircserver.cpp delete mode 100644 main.cpp delete mode 100644 mobileclient.cpp delete mode 100644 netcore.cpp delete mode 100644 server.cpp delete mode 100644 socketcommon.cpp delete mode 100644 window.cpp 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 +#include + +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::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::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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "buffer.h" + +#ifdef USE_GNUTLS +#include +#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 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 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 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 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 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 +#include +#include +#include + +#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 +#include +#include + +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::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::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::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::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."); + } +} diff --git a/buffer.h b/buffer.h deleted file mode 100644 index bfba94a..0000000 --- a/buffer.h +++ /dev/null @@ -1,197 +0,0 @@ -#ifndef BUFFER_H -#define BUFFER_H - -#include -#include - -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/build.sh b/build.sh deleted file mode 100755 index 935cae6..0000000 --- a/build.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/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/build_static.sh b/build_static.sh deleted file mode 100755 index 6ed9183..0000000 --- a/build_static.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/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/client.cpp b/client.cpp deleted file mode 100644 index 4fa0a12..0000000 --- a/client.cpp +++ /dev/null @@ -1,331 +0,0 @@ -#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::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::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/core.h b/core.h deleted file mode 100644 index b5d3164..0000000 --- a/core.h +++ /dev/null @@ -1,373 +0,0 @@ -#ifndef CORE_H -#define CORE_H - -// Set in build.sh -//#define USE_GNUTLS - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "buffer.h" - -#ifdef USE_GNUTLS -#include -#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 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 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 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 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 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/dns.cpp b/dns.cpp deleted file mode 100644 index 056bb09..0000000 --- a/dns.cpp +++ /dev/null @@ -1,159 +0,0 @@ -#include "dns.h" -#include -#include -#include -#include - -#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/dns.h b/dns.h deleted file mode 100644 index 78ec75f..0000000 --- a/dns.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef DNS_H -#define DNS_H - -#include -#include -#include - -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/ircserver.cpp b/ircserver.cpp deleted file mode 100644 index 31cbd6d..0000000 --- a/ircserver.cpp +++ /dev/null @@ -1,393 +0,0 @@ -#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::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/main.cpp b/main.cpp deleted file mode 100644 index 5330310..0000000 --- a/main.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#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/mobileclient.cpp b/mobileclient.cpp deleted file mode 100644 index fb8b3c1..0000000 --- a/mobileclient.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#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::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/netcore.cpp b/netcore.cpp deleted file mode 100644 index dee6ef7..0000000 --- a/netcore.cpp +++ /dev/null @@ -1,330 +0,0 @@ -#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::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/server.cpp b/server.cpp deleted file mode 100644 index 16c754b..0000000 --- a/server.cpp +++ /dev/null @@ -1,160 +0,0 @@ -#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/socketcommon.cpp b/socketcommon.cpp deleted file mode 100644 index 7bc55b6..0000000 --- a/socketcommon.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#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/window.cpp b/window.cpp deleted file mode 100644 index c971046..0000000 --- a/window.cpp +++ /dev/null @@ -1,468 +0,0 @@ -#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::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."); - } -} -- cgit v1.2.3