changeset 413:14b685cdbd2c

Rename files.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 24 May 2012 06:41:07 +0300
parents 3e64acb433e8
children ac4862a94cd1
files Makefile.gen libnnnet.c libnnnet.h libnnutil.c main.c network.c network.h nnchat.c util.c
diffstat 9 files changed, 3574 insertions(+), 3571 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.gen	Thu May 24 06:38:26 2012 +0300
+++ b/Makefile.gen	Thu May 24 06:41:07 2012 +0300
@@ -34,9 +34,9 @@
 $(THLIBS_A): $(addprefix $(OBJPATH),$(THLIBS_OBJ))
 	$(AR) cru $@ $+
 
-nnchat.c: VERSION
+main.c: VERSION
 
-$(NNCHAT_BIN): nnchat.c $(OBJPATH)libnnutil.o $(OBJPATH)libnnnet.o $(THLIBS_A) $(EXTRAOBJS)
+$(NNCHAT_BIN): main.c $(OBJPATH)util.o $(OBJPATH)network.o $(THLIBS_A) $(EXTRAOBJS)
 	$(CC) $(CFLAGS) -o $@ $+ $(LDFLAGS) -DNN_VERSION=\"$(NN_VERSION)\" -I$(THLIBS)
 
 #
--- a/libnnnet.c	Thu May 24 06:38:26 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,540 +0,0 @@
-/*
- * NNChat - Custom chat client for NewbieNudes.com chatrooms
- * Written by Matti 'ccr' Hämäläinen
- * (C) Copyright 2008-2012 Tecnic Software productions (TNSP)
- */
-#include "libnnnet.h"
-
-
-
-#ifdef __WIN32
-const char *hstrerror(int err)
-{
-    static char buf[64];
-    snprintf(buf, sizeof(buf), "Error #%d", err);
-    return buf;
-}
-
-int nn_get_socket_errno(void)
-{
-    return WSAGetLastError();
-}
-
-const char *nn_get_socket_errstr(int err)
-{
-    static char buf[64];
-    switch (err)
-    {
-        case WSAEADDRINUSE:      return "Address already in use";
-        case WSAECONNABORTED:    return "Software caused connection abort";
-        case WSAECONNREFUSED:    return "Connection refused";
-        case WSAECONNRESET:      return "Connection reset by peer";
-        case WSAEHOSTUNREACH:    return "No route to host";
-        case WSAENETDOWN:        return "Network is down";
-        case WSAETIMEDOUT:       return "Connection timed out";
-        case WSAHOST_NOT_FOUND:  return "Host not found";
-        case WSAVERNOTSUPPORTED: return "Wrong WinSock DLL version";
-        default:
-            snprintf(buf, sizeof(buf), "Error #%d", err);
-            return buf;
-            break;
-    }
-}
-#else
-int nn_get_socket_errno(void)
-{
-    return errno;
-}
-
-const char *nn_get_socket_errstr(int err)
-{
-    return strerror(err);
-}
-#endif
-
-
-void nn_conn_err(nn_conn_t *conn, const char *fmt, ...)
-{
-    if (conn->errfunc != NULL)
-    {
-        va_list ap;
-        va_start(ap, fmt);
-        conn->errfunc(conn, fmt, ap);
-        va_end(ap);
-    }
-}
-
-
-static void nn_conn_msg(nn_conn_t *conn, const char *fmt, ...)
-{
-    if (conn->msgfunc != NULL)
-    {
-        va_list ap;
-        va_start(ap, fmt);
-        conn->msgfunc(conn, fmt, ap);
-        va_end(ap);
-    }
-}
-
-struct hostent *nn_resolve_host(nn_conn_t *conn, const char *name)
-{
-    struct hostent *res = gethostbyname(name);
-    if (res == NULL)
-        nn_conn_err(conn, "Could not resolve hostname: %s\n", strerror(h_errno));
-    else
-        nn_conn_msg(conn, "True hostname for %s is %s\n", name, res->h_name);
-
-    return res;
-}
-
-static const char *nn_proxy_types[] =
-{
-    "none",
-    "SOCKS 4",
-    "SOCKS 4a",
-    NULL
-};
-
-
-nn_conn_t * nn_conn_new(
-    void (*errfunc)(nn_conn_t *conn, const char *fmt, va_list ap),
-    void (*msgfunc)(nn_conn_t *conn, const char *fmt, va_list ap))
-{
-    nn_conn_t *conn = th_calloc(1, sizeof(nn_conn_t));
-
-    if (conn == NULL)
-        return NULL;
-
-    conn->errfunc = errfunc;
-    conn->msgfunc = msgfunc;
-
-    return conn;
-}
-
-static BOOL nn_get_addr(struct in_addr *addr, struct hostent *hst)
-{
-    if (hst != NULL)
-    {
-        *addr = *(struct in_addr *) (hst->h_addr);
-        return TRUE;
-    }
-    else
-    {
-        addr->s_addr = 0;
-        return FALSE;
-    }
-}
-
-int nn_conn_set_proxy(nn_conn_t *conn, int type, int port, const char *host)
-{
-    if (conn == NULL)
-        return -1;
-
-    conn->proxy.type = type;
-    conn->proxy.port = port;
-    conn->proxy.host = th_strdup(host);
-
-    if (host != NULL)
-    {
-        conn->proxy.hst = nn_resolve_host(conn, host);
-        nn_get_addr(&(conn->proxy.addr), conn->proxy.hst);
-    }
-    else
-        return -2;
-
-    return 0;
-}
-
-int nn_conn_open(nn_conn_t *conn, const int port, const char *host)
-{
-    struct sockaddr_in dest;
-    static const char *userid = "James Bond";
-
-    if (conn == NULL)
-        return -1;
-
-    conn->port = port;
-    if (host != NULL)
-    {
-        conn->host = th_strdup(host);
-        conn->hst = nn_resolve_host(conn, host);
-    }
-
-    nn_get_addr(&(conn->addr), conn->hst);
-
-    /* Prepare for connection */
-    dest.sin_family = AF_INET;
-
-    if (conn->proxy.type > NN_PROXY_NONE && conn->proxy.type < NN_PROXY_LAST)
-    {
-        dest.sin_port = htons(conn->proxy.port);
-        dest.sin_addr = conn->proxy.addr;
-
-        nn_conn_msg(conn, "Connecting to %s proxy %s:%d ...\n",
-            nn_proxy_types[conn->proxy.type],
-            inet_ntoa(conn->proxy.addr), conn->proxy.port);
-    }
-    else
-    {
-        dest.sin_port = htons(conn->port);
-        dest.sin_addr = conn->addr;
-
-        nn_conn_msg(conn, "Connecting to %s:%d ...\n",
-            inet_ntoa(conn->addr), conn->port);
-    }
-
-    if ((conn->socket = socket(PF_INET, SOCK_STREAM, 0)) == -1)
-    {
-        conn->err = nn_get_socket_errno();
-        nn_conn_err(conn, "Could not open socket: %s\n", nn_get_socket_errstr(conn->err));
-        goto error;
-    }
-
-    nn_conn_msg(conn, "Using socket %d.\n", conn->socket);
-
-    if (connect(conn->socket, (struct sockaddr *) &dest, sizeof(dest)) == -1)
-    {
-        conn->err = nn_get_socket_errno();
-        nn_conn_err(conn, "Could not connect: %s\n", nn_get_socket_errstr(conn->err));
-        goto error;
-    }
-
-    FD_ZERO(&(conn->sockfds));
-    FD_SET(conn->socket, &(conn->sockfds));
-
-    /* Proxy-specific setup */
-    if (conn->proxy.type == NN_PROXY_SOCKS4 || conn->proxy.type == NN_PROXY_SOCKS4A)
-    {
-        struct nn_socks_t *socksh;
-        size_t bufsiz = sizeof(struct nn_socks_t) + strlen(userid) + 1;
-        char *ptr, *buf;
-        int tries, status = -1;
-
-        if (conn->proxy.type == NN_PROXY_SOCKS4A)
-            bufsiz += strlen(conn->host) + 1;
-
-        ptr = buf = th_malloc(bufsiz);
-        if (buf == NULL)
-        {
-            conn->err = -1;
-            nn_conn_err(conn, "Could not allocate memory for SOCKS negotiation buffer, %d bytes.\n", bufsiz);
-            goto error;
-        }
-
-        /* Create SOCKS 4/4A request */
-        nn_conn_msg(conn, "Initializing proxy negotiation.\n");
-        socksh = (struct nn_socks_t *) buf;
-        socksh->version = 4;
-        socksh->command = SOCKS_CMD_CONNECT;
-        socksh->port = htons(port);
-        if (conn->proxy.type == NN_PROXY_SOCKS4A)
-            socksh->addr = htonl(0x00000032);
-        else
-            socksh->addr = conn->addr.s_addr;
-        ptr += sizeof(struct nn_socks_t);
-
-        strcpy(ptr, userid);
-
-        if (conn->proxy.type == NN_PROXY_SOCKS4A)
-        {
-            ptr += strlen(userid) + 1;
-            strcpy(ptr, conn->host);
-        }
-
-        /* Send request */
-        nn_conn_reset(conn);
-        if (!nn_conn_send_buf(conn, buf, bufsiz))
-        {
-            th_free(buf);
-            nn_conn_err(conn, "Error sending SOCKS proxy request.\n");
-            goto error;
-        }
-        th_free(buf);
-
-        /* Wait for SOCKS server to reply */
-        for (status = tries = 1; tries <= 20 && status > 0; tries++)
-        {
-#ifdef __WIN32
-            Sleep(50);
-#else
-            usleep(50000);
-#endif
-            nn_conn_reset(conn);
-            status = nn_conn_pull(conn);
-        }
-
-        /* Check results */
-        if (status == 0)
-        {
-            struct nn_socks_res_t *res = (struct nn_socks_res_t *) &(conn->buf);
-            if (res->nb != 0)
-            {
-                nn_conn_err(conn, "Invalid SOCKS server reply, does not begin with NUL byte (%d).\n", res->nb);
-                goto error;
-            }
-            if (res->result != 0x5a)
-            {
-                char *s = NULL;
-                switch (res->result)
-                {
-                    case 0x5b: s = "Request rejected or failed"; break;
-                    case 0x5c: s = "Request failed because client is not running identd (or not reachable from the server)"; break;
-                    case 0x5d: s = "Request failed because client's identd could not confirm the user ID string in the request"; break;
-                    default: s = "Unknown SOCKS error response"; break;
-                }
-                nn_conn_err(conn, "SOCKS setup failed, 0x%02x: %s.\n", res->result, s);
-                goto error;
-            }
-            nn_conn_msg(conn, "SOCKS connection established!\n");
-        }
-        else if (status < 0)
-        {
-            nn_conn_err(conn, "Proxy negotiation failed at try %d with network error: %d\n", tries, status);
-            goto error;
-        }
-        else
-        {
-            nn_conn_err(conn, "Proxy negotiation timed out.\n");
-            goto error;
-        }
-    }
-
-    nn_conn_reset(conn);
-    conn->status = NN_CONN_OPEN;
-    return 0;
-
-error:
-    conn->status = NN_CONN_CLOSED;
-    return -2;
-}
-
-
-void nn_conn_close(nn_conn_t *conn)
-{
-    if (conn == NULL)
-        return;
-
-    if (conn->socket >= 0)
-    {
-#ifdef __WIN32
-        closesocket(conn->socket);
-#else
-        close(conn->socket);
-#endif
-        conn->socket = -1;
-    }
-
-    th_free(conn->host);
-    th_free(conn->proxy.host);
-
-    conn->status = NN_CONN_CLOSED;
-
-    th_free(conn);
-}
-
-
-BOOL nn_conn_send_buf(nn_conn_t *conn, const char *buf, const size_t len)
-{
-    size_t bufLeft = len;
-    const char *bufPtr = buf;
-
-    while (bufLeft > 0)
-    {
-        ssize_t bufSent;
-        bufSent = send(conn->socket, bufPtr, bufLeft, 0);
-        if (bufSent < 0)
-        {
-            conn->err = nn_get_socket_errno();
-            nn_conn_err(conn, "nn_conn_send_buf() failed: %s", nn_get_socket_errstr(conn->err));
-            return FALSE;
-        }
-        bufLeft -= bufSent;
-        bufPtr += bufSent;
-    }
-
-    return TRUE;
-}
-
-void nn_conn_reset(nn_conn_t *conn)
-{
-    if (conn != NULL)
-    {
-        conn->ptr = conn->in_ptr = conn->buf;
-        conn->got_bytes = conn->total_bytes = 0;
-    }
-}
-
-
-int nn_conn_pull(nn_conn_t *conn)
-{
-    int result;
-    struct timeval socktv;
-    fd_set tmpfds;
-
-    if (conn == NULL)
-        return -10;
-
-    /* Prod the input buffer */
-    if (conn->in_ptr > conn->buf && conn->in_ptr - conn->ptr > 0)
-    {
-        size_t delta = conn->in_ptr - conn->ptr;
-        memmove(conn->buf, conn->in_ptr, delta);
-        conn->ptr = conn->buf;
-        conn->in_ptr -= delta;
-        conn->total_bytes -= delta;
-    }
-
-    /* Check for incoming data */
-    socktv.tv_sec = 0;
-    socktv.tv_usec = NN_DELAY_USEC;
-    tmpfds = conn->sockfds;
-
-    if ((result = select(conn->socket + 1, &tmpfds, NULL, NULL, &socktv)) == -1)
-    {
-        conn->err = nn_get_socket_errno();
-        if (conn->err != EINTR)
-        {
-            nn_conn_err(conn, "Error occured in select(%d, sockfds): %d, %s\n",
-                socket, conn->err, nn_get_socket_errstr(conn->err));
-            return -1;
-        }
-    }
-    else if (FD_ISSET(conn->socket, &tmpfds))
-    {
-        conn->got_bytes = recv(conn->socket, conn->in_ptr, NN_CONNBUF_SIZE - conn->total_bytes, 0);
-        if (conn->got_bytes < 0)
-        {
-            conn->err = nn_get_socket_errno();
-            nn_conn_err(conn, "Error in recv: %d, %s\n", conn->err, nn_get_socket_errstr(conn->err));
-            return -2;
-        }
-        else if (conn->got_bytes == 0)
-        {
-            nn_conn_err(conn, "Server closed connection.\n");
-            conn->status = NN_CONN_CLOSED;
-            return -3;
-        }
-        else
-        {
-            conn->total_bytes += conn->got_bytes;
-            conn->in_ptr += conn->got_bytes;
-            return 0;
-        }
-    }
-
-    return 1;
-}
-
-
-BOOL nn_conn_check(nn_conn_t *conn)
-{
-    if (conn == NULL)
-        return FALSE;
-
-    return conn->err == 0 && conn->status == NN_CONN_OPEN;
-}
-
-
-BOOL nn_network_init(void)
-{
-#ifdef __WIN32
-    /* Initialize WinSock, if needed */
-    WSADATA wsaData;
-    int err = WSAStartup(0x0101, &wsaData);
-    if (err != 0)
-    {
-        THERR("Could not initialize WinSock library (err=%d).\n", err);
-        return FALSE;
-    }
-#endif
-    return TRUE;
-}
-
-
-void nn_network_close(void)
-{
-#ifdef __WIN32
-    WSACleanup();
-#endif
-}
-
-
-BOOL nn_conn_buf_check(nn_conn_t *conn, size_t n)
-{
-    return conn && conn->ptr && conn->in_ptr && (conn->ptr + n <= conn->in_ptr);
-}
-
-
-BOOL nn_conn_buf_skip(nn_conn_t *conn, size_t n)
-{
-    if (nn_conn_buf_check(conn, n))
-    {
-        conn->ptr += n;
-        return TRUE;
-    }
-    else
-        return FALSE;
-}
-
-
-int nn_conn_buf_strncmp(nn_conn_t *conn, const char *str, const size_t n)
-{
-    int ret;
-    if (!nn_conn_buf_check(conn, n))
-        return -1;
-
-    if ((ret = strncmp(conn->ptr, str, n)) == 0)
-    {
-        conn->ptr += n;
-        return 0;
-    }
-    else
-        return ret;
-}
-
-
-int nn_conn_buf_strcmp(nn_conn_t *conn, const char *str)
-{
-    return nn_conn_buf_strncmp(conn, str, strlen(str));
-}
-
-
-char *nn_conn_buf_strstr(nn_conn_t *conn, const char *str)
-{
-    char *pos;
-    size_t n = strlen(str);
-
-    if (nn_conn_buf_check(conn, n) && ((pos = strstr(conn->ptr, str)) != NULL))
-    {
-        conn->ptr = pos + n;
-        return pos;
-    }
-    else
-        return NULL;
-}
-
-
-BOOL nn_conn_send_msg(nn_conn_t *conn, const char *user, const char *fmt, ...)
-{
-    char *tmp, *msg;
-    va_list ap;
-
-    va_start(ap, fmt);
-    tmp = th_strdup_vprintf(fmt, ap);
-    va_end(ap);
-
-    if (tmp == NULL)
-        return FALSE;
-
-    msg = th_strdup_printf("<USER>%s</USER><MESSAGE>%s</MESSAGE>", user, tmp);
-    th_free(tmp);
-
-    if (msg != NULL)
-    {
-        BOOL ret = nn_conn_send_buf(conn, msg, strlen(msg) + 1);
-        th_free(msg);
-        return ret;
-    }
-    else
-        return FALSE;
-}
--- a/libnnnet.h	Thu May 24 06:38:26 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,132 +0,0 @@
-/*
- * NNChat - Custom chat client for NewbieNudes.com chatrooms
- * Written by Matti 'ccr' Hämäläinen
- * (C) Copyright 2008-2012 Tecnic Software productions (TNSP)
- */
-#ifndef LIBNNCHAT_H
-#define LIBNNCHAT_H
-
-#include <stdio.h>
-#include <unistd.h>
-#include "th_types.h"
-#include "th_string.h"
-
-#ifdef __WIN32
-#include <windows.h>
-#include <winsock.h>
-typedef uint16_t in_port_t;
-typedef uint32_t in_addr_t;
-#else
-#include <sys/select.h>
-#include <sys/socket.h>
-#ifdef HAVE_NETINET_IN_H
-#include <netinet/in.h>
-#endif
-#include <arpa/inet.h>
-#include <netdb.h>
-#endif
-#include <sys/types.h>
-#include <time.h>
-#include <errno.h>
-
-
-#define NN_CONNBUF_SIZE   (64 * 1024)
-#define NN_DELAY_USEC     (15 * 1000)
-
-
-enum
-{
-    NN_CONN_UNINIT = 0,
-    NN_CONN_PROXY_NEG,
-    NN_CONN_OPEN,
-    NN_CONN_CLOSED
-};
-
-enum
-{
-    NN_PROXY_NONE = 0,
-    NN_PROXY_SOCKS4,
-    NN_PROXY_SOCKS4A,
-
-    NN_PROXY_LAST
-};
-
-enum
-{
-    SOCKS_CMD_CONNECT = 1,
-    SOCKS_CMD_BIND = 2
-};
-
-struct nn_socks_t
-{
-    uint8_t version;
-    uint8_t command;
-    in_port_t port;
-    in_addr_t addr;
-} __attribute__((__packed__));
-
-struct nn_socks_res_t
-{
-    uint8_t nb;
-    uint8_t result;
-    in_port_t port;
-    in_addr_t addr;
-} __attribute__((__packed__));
-
-typedef struct _nn_conn_t
-{
-    struct
-    {
-        char *host;
-        struct hostent *hst;
-        int type;
-        int port;
-        struct in_addr addr;
-    } proxy;
-
-    char *host;
-    struct hostent *hst;
-    int port;
-
-    int socket;
-    struct in_addr addr;
-    fd_set sockfds;
-
-    void (*errfunc)(struct _nn_conn_t *conn, const char *fmt, va_list ap);
-    void (*msgfunc)(struct _nn_conn_t *conn, const char *fmt, va_list ap);
-
-    int err;
-    int status;
-
-    char buf[NN_CONNBUF_SIZE + 16];
-    char *ptr, *in_ptr;
-    ssize_t got_bytes, total_bytes;
-} nn_conn_t;
-
-
-const char *nn_get_errstr(int err);
-BOOL        nn_network_init();
-void        nn_network_close(void);
-
-struct hostent *nn_resolve_host(nn_conn_t *conn, const char *name);
-nn_conn_t * nn_conn_new(
-    void (*errfunc)(nn_conn_t *conn, const char *fmt, va_list ap),
-    void (*msgfunc)(nn_conn_t *conn, const char *fmt, va_list ap));
-
-int         nn_conn_set_proxy(nn_conn_t *conn, int type, int port, const char *host);
-int         nn_conn_open(nn_conn_t *conn, const int port, const char *host);
-void        nn_conn_close(nn_conn_t *);
-void        nn_conn_reset(nn_conn_t *);
-int         nn_conn_pull(nn_conn_t *);
-BOOL        nn_conn_send_buf(nn_conn_t *, const char *buf, const size_t len);
-BOOL        nn_conn_send_msg(nn_conn_t *, const char *user, const char *fmt, ...);
-BOOL        nn_conn_check(nn_conn_t *);
-
-
-BOOL        nn_conn_buf_check(nn_conn_t *conn, size_t n);
-BOOL        nn_conn_buf_skip(nn_conn_t *conn, size_t n);
-int         nn_conn_buf_strncmp(nn_conn_t *conn, const char *str, const size_t n);
-int         nn_conn_buf_strcmp(nn_conn_t *conn, const char *str);
-char *      nn_conn_buf_strstr(nn_conn_t *conn, const char *str);
-
-#endif
--- a/libnnutil.c	Thu May 24 06:38:26 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,739 +0,0 @@
-/*
- * NNChat - Custom chat client for NewbieNudes.com chatrooms
- * Written by Matti 'ccr' Hämäläinen
- * (C) Copyright 2008-2012 Tecnic Software productions (TNSP)
- */
-#include "libnnutil.h"
-
-
-#define PUSHCHAR(x) th_vputch(&result, &resSize, &resPos, x)
-#define PUSHSTR(x) th_vputs(&result, &resSize, &resPos, x)
-
-char *nn_encode_str1(const char *str)
-{
-    const char *s = str;
-    char *result;
-    size_t resSize, resPos = 0;
-
-    if (str == NULL) return NULL;
-
-    resSize = strlen(str) + NN_ALLOC_SIZE;
-    if ((result = th_malloc(resSize)) == NULL)
-        return NULL;
-
-    while (*s)
-    {
-        switch (*s)
-        {
-        case 32:
-            PUSHCHAR('+');
-            break;
-
-        default:
-            if (th_isalnum(*s))
-                PUSHCHAR(*s);
-            else
-            {
-                char tmpStr[4];
-                snprintf(tmpStr, sizeof(tmpStr), "%2X", (unsigned char) *s);
-                PUSHCHAR('%');
-                PUSHSTR(tmpStr);
-            }
-            break;
-        }
-        s++;
-    }
-    PUSHCHAR(0);
-
-    return result;
-}
-
-
-static int nn_get_hexdigit(const int c, const int shift)
-{
-    int i;
-
-    if (c >= 'A' && c <= 'F')
-        i = c - 'A' + 10;
-    else if (c >= 'a' && c <= 'f')
-        i = c - 'a' + 10;
-    else if (c >= '0' && c <= '9')
-        i = c - '0';
-    else
-        return -1;
-
-    return i << shift;
-}
-
-
-char *nn_decode_str1(const char *str)
-{
-    const char *s = str;
-    char *result;
-    size_t resSize, resPos = 0;
-    int c;
-
-    if (str == NULL) return NULL;
-
-    resSize = strlen(str) + NN_ALLOC_SIZE;
-    if ((result = th_malloc(resSize)) == NULL)
-        return NULL;
-
-    while (*s)
-    {
-        switch (*s)
-        {
-        case '+':
-            PUSHCHAR(' ');
-            s++;
-            break;
-
-        case '½':
-            /* Escape these .. */
-            PUSHCHAR('½');
-            PUSHCHAR('½');
-            s++;
-            break;
-
-        case '\r':
-            PUSHCHAR(' ');
-            s++;
-            break;
-
-        case '%':
-            s++;
-            if (*s == '%')
-                PUSHCHAR('%');
-            else if ((c = nn_get_hexdigit(*s, 4)) >= 0)
-            {
-                int i = nn_get_hexdigit(*(++s), 0);
-                if (i >= 0)
-                {
-                    PUSHCHAR(c | i);
-                }
-                else
-                {
-                    PUSHCHAR('§');
-                    PUSHCHAR(*s);
-                }
-            }
-            else
-            {
-                PUSHCHAR('§');
-                PUSHCHAR(*s);
-            }
-            s++;
-            break;
-
-        default:
-            PUSHCHAR(*s);
-            s++;
-        }
-    }
-    PUSHCHAR(0);
-
-    return result;
-}
-
-
-char *nn_strip_tags(const char *str)
-{
-    const char *s = str;
-    char *result;
-    size_t resSize, resPos = 0;
-
-    if (str == NULL) return NULL;
-
-    resSize = strlen(str) + NN_ALLOC_SIZE;
-    if ((result = th_malloc(resSize)) == NULL)
-        return NULL;
-
-    while (*s)
-    {
-        if (*s == '<')
-        {
-            while (*s && *s != '>') s++;
-            if (*s == '>') s++;
-        }
-        else
-            PUSHCHAR(*s++);
-    }
-    PUSHCHAR(0);
-
-    return result;
-}
-
-
-typedef struct
-{
-    char c;
-    char *ent;
-} html_entity_t;
-
-
-static const html_entity_t HTMLEntities[] =
-{
-    { '<', "&lt;" },
-    { '>', "&gt;" },
-};
-
-static const int numHTMLEntities = sizeof(HTMLEntities) / sizeof(HTMLEntities[0]);
-
-
-char *nn_encode_str2(const char *str)
-{
-    const char *s = str;
-    char *result;
-    size_t resSize, resPos = 0;
-
-    if (str == NULL) return NULL;
-
-    resSize = strlen(str) + NN_ALLOC_SIZE;
-    if ((result = th_malloc(resSize)) == NULL)
-        return NULL;
-
-    while (*s)
-    {
-        int i;
-        BOOL found = FALSE;
-        for (i = 0; i < numHTMLEntities; i++)
-            if (HTMLEntities[i].c == *s)
-            {
-                PUSHSTR(HTMLEntities[i].ent);
-                found = TRUE;
-                break;
-            }
-        if (!found) PUSHCHAR(*s);
-
-        s++;
-    }
-    PUSHCHAR(0);
-
-    return result;
-}
-
-
-char *nn_decode_str2(const char *str)
-{
-    const char *s = str;
-    char *result;
-    size_t resSize, resPos = 0;
-
-    if (str == NULL) return NULL;
-
-    resSize = strlen(str);
-    if ((result = th_malloc(resSize)) == NULL)
-        return NULL;
-
-    while (*s)
-    {
-        if (*s == '&')
-        {
-            int i;
-            BOOL found = FALSE;
-            for (i = 0; i < numHTMLEntities; i++)
-            {
-                const html_entity_t *ent = &HTMLEntities[i];
-                int len = strlen(ent->ent);
-                if (!strncmp(s, ent->ent, len))
-                {
-                    PUSHCHAR(ent->c);
-                    s += len;
-                    found = TRUE;
-                    break;
-                }
-            }
-            if (!found) PUSHCHAR(*s++);
-        }
-        else
-            PUSHCHAR(*s++);
-    }
-    PUSHCHAR(0);
-
-    return result;
-}
-
-
-char *nn_dbldecode_str(const char *str)
-{
-    char *res, *tmp;
-
-    if ((tmp = nn_decode_str1(str)) == NULL)
-        return NULL;
-
-    res = nn_decode_str2(tmp);
-    th_free(tmp);
-
-    return res;
-}
-
-
-char *nn_dblencode_str(const char *str)
-{
-    char *res, *tmp;
-
-    if ((tmp = nn_encode_str2(str)) == NULL)
-        return NULL;
-
-    res = nn_encode_str1(tmp);
-    th_free(tmp);
-
-    return res;
-}
-
-
-int nn_editbuf_write(nn_editbuf_t *buf, ssize_t pos, int ch)
-{
-    if (buf->len+1 >= buf->size) return -3;
-
-    if (pos < 0)
-        return -1;
-    else if (pos >= buf->len)
-        buf->data[buf->len++] = ch;
-    else
-        buf->data[pos] = ch;
-
-    return 0;
-}
-
-
-int nn_editbuf_insert(nn_editbuf_t *buf, ssize_t pos, int ch)
-{
-    if (buf->len+1 >= buf->size) return -3;
-
-    if (pos < 0)
-        return -1;
-    else if (pos >= buf->len)
-    {
-        buf->data[buf->len] = ch;
-    }
-    else
-    {
-        memmove(&(buf->data[pos+1]), &(buf->data[pos]), buf->len - pos + 1);
-        buf->data[pos] = ch;
-    }
-    buf->len++;
-    return 0;
-}
-
-
-int nn_editbuf_delete(nn_editbuf_t *buf, ssize_t pos)
-{
-    if (pos < 0)
-        return -1;
-    else if (pos < buf->len)
-    {
-        memmove(&(buf->data[pos]), &(buf->data[pos+1]), buf->len - pos);
-        buf->len--;
-        return 0;
-    }
-    else
-        return -2;
-}
-
-
-void nn_editbuf_clear(nn_editbuf_t *buf)
-{
-    buf->len = 0;
-    buf->pos = 0;
-}
-
-
-nn_editbuf_t * nn_editbuf_new(ssize_t n)
-{
-    nn_editbuf_t *res = th_calloc(1, sizeof(nn_editbuf_t));
-
-    res->data = (char *) th_malloc(n);
-    res->size = n;
-
-    return res;
-}
-
-
-void nn_editbuf_free(nn_editbuf_t *buf)
-{
-    if (buf != NULL)
-    {
-        th_free(buf->data);
-        th_free(buf);
-    }
-}
-
-
-nn_editbuf_t * nn_editbuf_copy(nn_editbuf_t *src)
-{
-    nn_editbuf_t *res;
-
-    assert(src != NULL);
-
-    if (src == NULL) return NULL;
-
-    if ((res = nn_editbuf_new(src->size)) == NULL)
-        return NULL;
-
-    memcpy(res->data, src->data, src->size);
-    res->pos = res->len = src->len;
-
-    return res;
-}
-
-
-char * nn_editbuf_get_string(nn_editbuf_t *buf, ssize_t start, ssize_t end)
-{
-    char *str;
-    ssize_t siz;
-
-    if (buf == NULL)
-        return NULL;
-
-    if (start < 0 || end > buf->len || start >= buf->len)
-        return NULL;
-
-    if (end < 0)
-    {
-        siz = buf->len - start + 1;
-    }
-    else if (start <= end)
-    {
-        siz = end - start + 1;
-    }
-    else
-        return NULL;
-
-    if ((str = th_malloc(siz + 1)) == NULL)
-        return NULL;
-
-    memcpy(str, buf->data + start, siz);
-    str[siz] = 0;
-
-    return str;
-}
-
-
-void nn_editbuf_setpos(nn_editbuf_t *buf, ssize_t pos)
-{
-    assert(buf != NULL);
-
-    if (pos < 0)
-        buf->pos = 0;
-    else if (pos >= buf->len)
-        buf->pos = buf->len;
-    else
-        buf->pos = pos;
-}
-
-
-static uint8_t nn_hash_user(const char *name)
-{
-/*
-    int n = 0;
-    const uint8_t *c = (uint8_t *)name;
-    uint8_t hash = 0xff;
-    
-    while (*c && n < 4)
-    {
-        hash = (hash << 1) ^ tolower(*c);
-        c++; n++;
-    }
-    
-    return (hash & 0xff);
-*/
-    return tolower(name[0]);
-}
-
-
-static void nn_user_insert(nn_user_t **list, nn_user_t *node)
-{
-    node->next = *list;
-    *list = node;
-}
-
-
-nn_user_t *nn_userhash_foreach(const nn_userhash_t *list, int (*func)(const nn_user_t *))
-{
-    int i;
-
-    if (list == NULL) return NULL;
-
-    for (i = 0; i < NN_NUM_BUCKETS; i++)
-        if (list->buckets[i] != NULL)
-        {
-            nn_user_t *curr = list->buckets[i];
-            while (curr != NULL)
-            {
-                if (func(curr) != 0)
-                    return curr;
-                curr = curr->next;
-            }
-        }
-
-    return NULL;
-}
-
-
-nn_user_t *nn_user_find(const nn_userhash_t *list, const char *name)
-{
-    uint8_t hash;
-
-    if (list == NULL) return NULL;
-
-    hash = nn_hash_user(name);
-    if (list->buckets[hash] != NULL)
-    {
-        nn_user_t *curr = list->buckets[hash];
-        while (curr != NULL)
-        {
-            if (th_strcasecmp(curr->name, name) == 0)
-                return curr;
-            curr = curr->next;
-        }
-    }
-
-    return NULL;
-}
-
-
-static nn_user_t *nn_user_match_do(nn_user_t *list, const char *pattern, size_t len)
-{
-    nn_user_t *curr = list;
-
-    while (curr != NULL)
-    {
-        if (len <= strlen(curr->name) && th_strncasecmp(curr->name, pattern, len) == 0)
-            return curr;
-        curr = curr->next;
-    }
-    return NULL;
-}
-
-
-nn_user_t *nn_user_match(const nn_userhash_t *list, const char *pattern, const char *current, BOOL again)
-{
-    uint8_t hash;
-
-    if (list == NULL || pattern == NULL) return NULL;
-
-    hash = nn_hash_user(pattern);
-    if (list->buckets[hash] != NULL)
-    {
-        nn_user_t *curr = list->buckets[hash];
-        size_t len = strlen(pattern);
-
-        if (current != NULL)
-        {
-            nn_user_t *found = NULL;
-            while (curr != NULL)
-            {
-                if (th_strcasecmp(curr->name, current) == 0)
-                {
-                    if (again)
-                        return curr;
-                    found = curr->next;
-                    break;
-                }
-                curr = curr->next;
-            }
-
-            if (found != NULL && (found = nn_user_match_do(found, pattern, len)) != NULL)
-                return found;
-        }
-
-        if ((curr = nn_user_match_do(list->buckets[hash], pattern, len)) != NULL)
-            return curr;
-    }
-
-    return NULL;
-}
-
-
-nn_userhash_t *nn_userhash_new(void)
-{
-    return th_calloc(1, sizeof(nn_userhash_t));
-}
-
-
-int nn_userhash_insert(nn_userhash_t *list, const char *name)
-{
-    uint8_t hash;
-    nn_user_t *user;
-
-    /* Check arguments */
-    if (list == NULL || name == NULL)
-        return -1;
-
-    /* Check if username is already there */
-    if (nn_user_find(list, name) != NULL)
-        return 1;
-
-    /* No, we'll add it */
-    if ((user = th_calloc(1, sizeof(nn_user_t))) == NULL)
-        return -3;
-
-    user->name = th_strdup(name);
-    if (user->name == NULL)
-        return -4;
-
-    hash = nn_hash_user(name);
-    nn_user_insert(&(list->buckets[hash]), user);
-
-    return 0;
-}
-
-
-int nn_userhash_delete(nn_userhash_t *list, const char *name)
-{
-    uint8_t hash;
-
-    /* Check arguments */
-    if (list == NULL || name == NULL)
-        return -1;
-
-    /* Check if username is already there */
-    hash = nn_hash_user(name);
-    if (list->buckets[hash] != NULL)
-    {
-        nn_user_t *curr, *prev;
-        curr = list->buckets[hash];
-        prev = NULL;
-        while (curr != NULL)
-        {
-            if (th_strcasecmp(curr->name, name) == 0)
-            {
-                if (prev)
-                    prev->next = curr->next;
-                else
-                    list->buckets[hash] = curr->next;
-
-                nn_user_free(curr);
-
-                return 0;
-            }
-            else
-            {
-                prev = curr;
-                curr = curr->next;
-            }
-        }
-    }
-
-    return 1;
-}
-
-
-nn_user_t *nn_user_copy(const nn_user_t *src)
-{
-    nn_user_t *user;
-
-    if (src == NULL) return NULL;
-
-    if ((user = th_calloc(1, sizeof(nn_user_t))) == NULL)
-        return NULL;
-
-    /* Copy relevant data */
-    user->name = th_strdup(src->name);
-    user->lastspoke = src->lastspoke;
-    user->joined = src->joined;
-
-    return user;
-}
-
-
-void nn_user_free(nn_user_t *user)
-{
-    th_free(user->name);
-    th_free(user);
-}
-
-
-void nn_user_free_list(nn_user_t *list)
-{
-    nn_user_t *next, *curr = list;
-
-    while (curr != NULL)
-    {
-        next = curr->next;
-        nn_user_free(curr);
-        curr = next;
-    }
-}
-
-void nn_userhash_free(nn_userhash_t *hash)
-{
-    int i;
-    if (hash == NULL)
-        return;
-
-    for (i = 0; i < NN_NUM_BUCKETS; i++)
-    {
-        nn_user_free_list(hash->buckets[i]);
-        hash->buckets[i] = NULL;
-    }
-
-    th_free(hash);
-}
-
-
-char *nn_username_encode(char *str)
-{
-    unsigned char *c = (unsigned char *) str;
-    if (str == NULL) return NULL;
-    for (; *c ; c++)
-        if (*c == ' ') *c = 255;
-    return str;
-}
-
-
-char *nn_username_decode(char *str)
-{
-    unsigned char *c = (unsigned char *) str;
-    if (str == NULL) return NULL;
-    for (; *c ; c++)
-        if (*c == 255) *c = ' ';
-    return str;
-}
-
-
-nn_window_t *nn_window_new(const char *id)
-{
-    nn_window_t *res = th_calloc(1, sizeof(nn_window_t));
-
-    if (res == NULL) return NULL;
-
-    res->data = th_ringbuf_new(NN_BACKBUF_LEN, th_free);
-    if (res->data == NULL)
-    {
-        th_free(res);
-        return NULL;
-    }
-
-    res->id = th_strdup(id);
-
-    return res;
-}
-
-
-void nn_window_free(nn_window_t *win)
-{
-    if (win != NULL)
-    {
-        th_ringbuf_free(win->data);
-        th_free(win->id);
-        th_free(win);
-    }
-}
-
-
-nn_strtuple_t *nn_strtuple_new(size_t len, char *str)
-{
-    nn_strtuple_t *tuple = th_calloc(1, sizeof(nn_strtuple_t));
-    tuple->len = len;
-    tuple->str = str;
-    return tuple;
-}
-
-
-void nn_strtuple_free(nn_strtuple_t *tuple)
-{
-    th_free(tuple->str);
-    th_free(tuple);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.c	Thu May 24 06:41:07 2012 +0300
@@ -0,0 +1,2158 @@
+/*
+ * NNChat - Custom chat client for NewbieNudes.com chatrooms
+ * Written by Matti 'ccr' Hämäläinen
+ * (C) Copyright 2008-2011 Tecnic Software productions (TNSP)
+ */
+#include "util.h"
+#include "network.h"
+#include "th_args.h"
+#include "th_config.h"
+#ifdef __WIN32
+/* Undefine because both windows.h and curses.h #define it */
+#undef MOUSE_MOVED
+#include <shlwapi.h>
+#else
+#include <sys/wait.h>
+#endif
+#ifdef HAVE_NCURSES_H
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+#ifdef __WIN32
+#define SET_CONFIG_FILE    "nnchat.txt"
+#define SET_DIR_SEPARATOR  "\\"
+#define SET_DELAY          (0)
+#else
+#define SET_CONFIG_FILE    ".nnchat"
+#define SET_DIR_SEPARATOR  "/"
+#define SET_DELAY          (5)
+#endif
+
+#define SET_NICK_SEPARATOR ':'
+
+#define SET_MAX_HISTORY (16)        /* Command history length */
+#define SET_KEEPALIVE   (15*60)     /* Ping/keepalive period in seconds */
+#define SET_MAX_WINDOWS (32)
+
+
+/* Options
+ */
+int     optPort = 8005,
+        optProxyPort = 1080,
+        optProxyType = NN_PROXY_NONE;
+int     optUserColor = 0x000000;
+char    *optServer = "chat.newbienudes.com",
+        *optProxyServer = NULL,
+        *optUserName = NULL,
+        *optUserNameCmd = NULL,
+        *optUserNameEnc = NULL,
+        *optPassword = NULL,
+        *optPasswordCmd = NULL,
+        *optLogFilename = NULL,
+        *optSite = "NN",
+        *optNickSepStr = NULL;
+char    optNickSep;
+BOOL    optDaemon = FALSE;
+FILE    *optLogFile = NULL;
+BOOL    setIgnoreMode = FALSE;
+BOOL    optDebug = FALSE;
+BOOL    optLogEnable = FALSE;
+
+nn_window_t *chatWindows[SET_MAX_WINDOWS],
+        *currWin = NULL;
+WINDOW  *mainWin = NULL,
+        *statusWin = NULL,
+        *editWin = NULL;
+
+qlist_t *setIgnoreList = NULL,
+        *setIdleMessages = NULL;
+nn_userhash_t *nnUsers = NULL;
+char    *setConfigFile = NULL,
+        *setBrowser = NULL;
+cfgitem_t *cfg = NULL;
+
+
+/* Logging mode flags
+ */
+enum
+{
+    LOG_FILE   = 1,
+    LOG_WINDOW = 2,
+    LOG_STAMP  = 4
+};
+
+
+/* Arguments
+ */
+optarg_t optList[] =
+{
+    { 0, '?', "help",       "Show this help", OPT_NONE },
+    { 1, 'v', "verbose",    "Be more verbose", OPT_NONE },
+    { 2, 'p', "port",       "Connect to port", OPT_ARGREQ },
+    { 3, 's', "server",     "Server to connect to", OPT_ARGREQ },
+    { 4, 'C', "color",      "Initial color in RGB hex 000000", OPT_ARGREQ },
+    { 5, 'l', "logfile",    "Log filename", OPT_ARGREQ },
+    { 6, 'D', "daemon",     "A pseudo-daemon mode for logging", OPT_NONE },
+    { 7, 'f', "force-site", "Force site (default: NN)", OPT_ARGREQ },
+    { 8, 'd', "debug",      "Enable various debug features", OPT_NONE },
+
+    {10, '4', "socks4",     "SOCKS4 proxy server", OPT_ARGREQ },
+    {11, 'A', "socks4a",    "SOCKS4A proxy server", OPT_ARGREQ },
+    {12, 'P', "proxy-port", "Proxy port (default: 1080)", OPT_ARGREQ },
+};
+
+const int optListN = (sizeof(optList) / sizeof(optList[0]));
+
+
+void argShowHelp(void)
+{
+    th_print_banner(stdout, th_prog_name,
+                    "[options] <username> <password>");
+
+    th_args_help(stdout, optList, optListN);
+}
+
+
+BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
+{
+    switch (optN)
+    {
+    case 0:
+        argShowHelp();
+        exit(0);
+        break;
+
+    case 1:
+        th_verbosityLevel++;
+        break;
+
+    case 2:
+        optPort = atoi(optArg);
+        break;
+
+    case 3:
+        optServer = optArg;
+        break;
+
+    case 4:
+        if ((optUserColor = th_get_hex_triplet(optArg)) < 0)
+        {
+            THERR("Invalid color argument '%s', should be a RGB hex triplet '000000'.\n",
+                optArg);
+            return FALSE;
+        }
+        THMSG(1, "Using color #%06x\n", optUserColor);
+        break;
+
+    case 5:
+        optLogFilename = optArg;
+        optLogEnable = TRUE;
+        break;
+
+    case 7:
+        optSite = optArg;
+        break;
+
+    case 6:
+        optDaemon = TRUE;
+        THMSG(1, "Running in pseudo-daemon mode.\n");
+        break;
+
+    case 8:
+        optDebug = TRUE;
+        THMSG(1, "Debug mode enabled.\n");
+        break;
+
+
+    case 10:
+        optProxyServer = optArg;
+        optProxyType = NN_PROXY_SOCKS4;
+        break;
+
+    case 11:
+        optProxyServer = optArg;
+        optProxyType = NN_PROXY_SOCKS4A;
+        break;
+
+    case 12:
+        optPort = atoi(optArg);
+        break;
+
+
+    default:
+        THERR("Unknown option '%s'.\n", currArg);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+BOOL argHandleFile(char *currArg)
+{
+    if (!optUserNameCmd)
+        optUserNameCmd = currArg;
+    else if (!optPasswordCmd)
+        optPasswordCmd = currArg;
+    else
+    {
+        THERR("Username '%s' already specified on commandline!\n", optUserNameCmd);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+BOOL str_get_timestamp(char *str, size_t len, const char *fmt)
+{
+    time_t stamp = time(NULL);
+    struct tm *stamp_tm;
+    if ((stamp_tm = localtime(&stamp)) != NULL)
+    {
+        strftime(str, len, fmt, stamp_tm);
+        return TRUE;
+    }
+    else
+    {
+        str[0] = 0;
+        return FALSE;
+    }
+}
+
+
+char * str_trim_left(char *buf)
+{
+    while (*buf != 0 && th_isspace(*buf)) buf++;
+    return buf;
+}
+
+int compareUsername(const void *s1, const void *s2)
+{
+    return th_strcasecmp((char *) s1, (char *) s2);
+}
+
+nn_window_t *findWindow(const char *id)
+{
+    int i;
+
+    for (i = 0; i < SET_MAX_WINDOWS; i++)
+        if (chatWindows[i] != NULL &&
+            chatWindows[i]->id != NULL &&
+            th_strcasecmp(id, chatWindows[i]->id) == 0)
+            return chatWindows[i];
+
+    return NULL;
+}
+
+
+BOOL openWindow(const char *name, BOOL curwin)
+{
+    int i;
+    nn_window_t *res;
+    if (name == NULL)
+        return FALSE;
+
+    if ((res = nn_window_new(name)) == NULL)
+        return FALSE;
+
+    for (i = 1; i < SET_MAX_WINDOWS; i++)
+        if (chatWindows[i] == NULL)
+        {
+            res->num = i;
+            chatWindows[i] = res;
+            if (curwin)
+                currWin = res;
+            return TRUE;
+        }
+
+    return FALSE;
+}
+
+
+void closeWindow(nn_window_t *win)
+{
+    int i;
+    if (win == NULL) return;
+
+    for (i = 1; i < SET_MAX_WINDOWS; i++)
+        if (chatWindows[i] == win)
+        {
+            chatWindows[i] = NULL;
+            nn_window_free(win);
+            return;
+        }
+}
+
+
+void updateStatus(void)
+{
+    char tmpStr[128];
+    int i;
+
+    if (statusWin == NULL) return;
+
+    str_get_timestamp(tmpStr, sizeof(tmpStr), "%H:%M:%S");
+
+    wbkgdset(statusWin, COLOR_PAIR(10));
+    werase(statusWin);
+
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(11));
+    mvwaddstr(statusWin, 0, 1, tmpStr);
+
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
+    waddstr(statusWin, " | ");
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(16));
+    waddstr(statusWin, optUserName);
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
+
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
+    waddstr(statusWin, " | ");
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(11));
+    snprintf(tmpStr, sizeof(tmpStr), "#%06x", optUserColor);
+    waddstr(statusWin, tmpStr);
+
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
+    waddstr(statusWin, " | WIN: ");
+    snprintf(tmpStr, sizeof(tmpStr), "%d: %s / %d",
+        currWin->num + 1,
+        currWin->id != NULL ? currWin->id : "MAIN",
+        currWin->pos);
+    waddstr(statusWin, tmpStr);
+
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
+    waddstr(statusWin, " | ");
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(11));
+
+    for (i = 0; i < SET_MAX_WINDOWS; i++)
+        if (chatWindows[i] != NULL && chatWindows[i]->dirty)
+        {
+            snprintf(tmpStr, sizeof(tmpStr), "%d ", i + 1);
+            waddstr(statusWin, tmpStr);
+        }
+
+    wrefresh(statusWin);
+}
+
+
+void printEditBuf(nn_editbuf_t *buf)
+{
+    char *tmp;
+    if (editWin == NULL || buf == NULL) return;
+
+    buf->data[buf->len] = 0;
+    tmp = nn_username_decode(th_strdup(buf->data));
+
+    werase(editWin);
+
+    wattrset(editWin, A_NORMAL);
+
+    if (buf->pos < buf->len)
+    {
+        waddnstr(editWin, tmp, buf->pos);
+        wattrset(editWin, A_REVERSE);
+        waddch(editWin, tmp[buf->pos]);
+        wattrset(editWin, A_NORMAL);
+        waddnstr(editWin, tmp + buf->pos + 1, buf->len - buf->pos - 1);
+    }
+    else
+    {
+        waddnstr(editWin, tmp, buf->len);
+        wattrset(editWin, A_REVERSE);
+        waddch(editWin, ' ');
+        wattrset(editWin, A_NORMAL);
+    }
+    wrefresh(editWin);
+    th_free(tmp);
+}
+
+
+int printWin(WINDOW *win, const char *fmt)
+{
+    const char *s = fmt;
+    int col = 0;
+
+    while (*s)
+    {
+        if (*s == '½')
+        {
+            s++;
+            if (*s == '½')
+            {
+                waddch(win, ((unsigned char) *s) | col);
+                s++;
+            }
+            else
+            {
+                memcpy(&col, s, sizeof(int));
+                s += sizeof(int);
+            }
+        }
+        else
+        {
+            waddch(win, ((unsigned char) *s) | col);
+            s++;
+        }
+    }
+    return 0;
+}
+
+
+#define QPUTCH(ch) th_vputch(&(win->buf), &(win->bufsize), &(win->len), ch)
+
+int nn_window_print(nn_window_t *win, const char *fmt)
+{
+    const char *s = fmt;
+    int col = 0;
+    while (*s)
+    {
+        if (*s == '½')
+        {
+            s++;
+            if (*s == '½')
+            {
+                QPUTCH(*s);
+                QPUTCH(*s);
+                win->chlen++;
+            }
+            else
+            {
+                int val = 0;
+                while (*s >= '0' && *s <= '9')
+                {
+                    val *= 10;
+                    val += (*s - '0');
+                    s++;
+                }
+                if (*s != '½') return -1;
+
+                if (val < 9)
+                    col = A_DIM | COLOR_PAIR(val);
+                else if (val < 30)
+                    col = A_BOLD | COLOR_PAIR(val - 9);
+
+                QPUTCH('½');
+
+                if (!th_growbuf(&(win->buf), &(win->bufsize), &(win->len), sizeof(int)))
+                    return -2;
+
+                memcpy(win->buf + win->len, &col, sizeof(int));
+                win->len += sizeof(int);
+            }
+        }
+        else if (*s == '\n')
+        {
+            QPUTCH('\n');
+            QPUTCH(0);
+            th_ringbuf_add(win->data, win->buf);
+            win->buf = NULL;
+            win->chlen = 0;
+            win->dirty = TRUE;
+        }
+        else if (*s != '\r')
+        {
+            QPUTCH((unsigned char) *s == 255 ? ' ' : *s);
+            win->chlen++;
+        }
+
+        s++;
+    }
+
+    return 0;
+}
+
+
+BOOL updateMainWin(BOOL force)
+{
+    int h, offs;
+    qringbuf_t *buf;
+
+    /* Check pointers */
+    if (mainWin == NULL || currWin == NULL)
+        return FALSE;
+
+    /* Check if update is forced or if the window is dirty */
+    if (!force && !currWin->dirty)
+        return FALSE;
+
+    /* Compute how many lines from backbuffer fit on the screen */
+    buf = currWin->data;
+    h = getmaxy(mainWin);
+
+    /* Clear and redraw window */
+    werase(mainWin);
+    scrollok(mainWin, 1);
+    for (offs = buf->size - h - currWin->pos; offs >= 0 && offs < buf->size - currWin->pos && offs < buf->size; offs++)
+    {
+        if (buf->data[offs] != NULL)
+            printWin(mainWin, buf->data[offs]);
+    }
+
+    currWin->dirty = FALSE;
+    wrefresh(mainWin);
+    return TRUE;
+}
+
+
+int printFile(FILE *outFile, const char *fmt)
+{
+    const char *s = fmt;
+
+    while (*s)
+    {
+        if (*s == '½')
+        {
+            s++;
+            if (*s == '½')
+            {
+                fputc((unsigned char) *s, outFile);
+                s++;
+            }
+            else
+            {
+                while (*s && isdigit((int) *s)) s++;
+                if (*s != '½') return -1;
+                s++;
+            }
+        }
+        else
+        {
+            if ((unsigned char) *s == 255)
+                fputc(' ', outFile);
+            else
+                fputc((unsigned char) *s, outFile);
+            s++;
+        }
+    }
+
+    return 0;
+}
+
+void printMsgV(nn_window_t *win, int flags, const char *fmt, va_list ap)
+{
+    char tmpStr[128], *buf;
+
+    str_get_timestamp(tmpStr, sizeof(tmpStr), "½17½[½11½%H:%M:%S½17½]½0½ ");
+
+    buf = th_strdup_vprintf(fmt, ap);
+
+    if (optLogFile && (flags & LOG_FILE))
+    {
+        if (flags & LOG_STAMP) printFile(optLogFile, tmpStr);
+        printFile(optLogFile, buf);
+        fflush(optLogFile);
+    }
+
+    if (!optDaemon && (flags & LOG_WINDOW))
+    {
+        nn_window_t *tmp = win != NULL ? win : chatWindows[0];
+        if (flags & LOG_STAMP) nn_window_print(tmp, tmpStr);
+        nn_window_print(tmp, buf);
+    }
+
+    th_free(buf);
+}
+
+void printMsg(nn_window_t *win, const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    printMsgV(win, LOG_STAMP | LOG_WINDOW | LOG_FILE, fmt, ap);
+    va_end(ap);
+}
+
+void printMsgF(nn_window_t *win, int flags, const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    printMsgV(win, flags | LOG_STAMP, fmt, ap);
+    va_end(ap);
+}
+
+void printMsgQ(nn_window_t *win, const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    printMsgV(win, LOG_STAMP | LOG_WINDOW, fmt, ap);
+    va_end(ap);
+}
+
+
+char *errorMessages = NULL;
+
+void errorMsgV(const char *fmt, va_list ap)
+{
+    char *tmp = th_strdup_vprintf(fmt, ap);
+
+    printMsg(NULL, "%s", tmp);
+
+    if (errorMessages != NULL)
+    {
+        char *tmp2 = th_strdup_printf("%s%s", errorMessages, tmp);
+        th_free(errorMessages);
+        th_free(tmp);
+        errorMessages = tmp2;
+    }
+    else
+        errorMessages = tmp;
+}
+
+void errorMsg(const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    errorMsgV(fmt, ap);
+    va_end(ap);
+}
+
+void errorFunc(struct _nn_conn_t *conn, const char *fmt, va_list ap)
+{
+    (void) conn;
+    errorMsgV(fmt, ap);
+}
+
+void messageFunc(struct _nn_conn_t *conn, const char *fmt, va_list ap)
+{
+    (void) conn;
+    printMsgV(NULL, LOG_STAMP | LOG_WINDOW | LOG_FILE, fmt, ap);
+}
+
+
+BOOL checkIgnoreList(const char *name)
+{
+    qlist_t *node = setIgnoreList;
+    while (node != NULL)
+    {
+        if (th_strcasecmp(name, (char *) node->data) == 0)
+            return TRUE;
+        node = node->next;
+    }
+    return FALSE;
+}
+
+
+int nnproto_handle_user(nn_conn_t *conn)
+{
+    static const char *msg = "</USER><MESSAGE>";
+    char *p = conn->ptr;
+    BOOL isMine, isIgnored = FALSE;
+    char *s, *t, *userName;
+
+    /* Find start of the message */
+    s = strstr(p, msg);
+    if (!s) return 1;
+    *s = 0;
+    s += strlen(msg);
+
+    /* Find end of the message */
+    t = strstr(s, "</MESSAGE>");
+    if (!t) return 3;
+    *t = 0;
+
+    /* Decode message string */
+    s = nn_decode_str1(s);
+    if (!s) return -1;
+
+    /* Decode username */
+    userName = nn_decode_str1(p);
+    if (!userName)
+    {
+        th_free(s);
+        return -2;
+    }
+
+    /* Check if the username is on our ignore list and
+     * that it is not our OWN username!
+     */
+    isMine = strcmp(userName, optUserName) == 0;
+    isIgnored = setIgnoreMode && !isMine && checkIgnoreList(userName);
+
+    /* Is it a special control message? */
+    if (*s == '/')
+    {
+        /* Ignore room join/leave messages */
+        if (!optDebug && (strstr(s, "left the room") || strstr(s, "joined the room from")))
+            goto done;
+
+        t = nn_strip_tags(s + 1);
+        if (!strncmp(t, "BPRV ", 5))
+        {
+            char *name, *tmp, *msg, *h;
+            nn_window_t *win;
+            h = nn_decode_str2(t + 1);
+
+            if (!strncmp(t, "BPRV from ", 10))
+            {
+                name = nn_decode_str2(t + 10);
+                isMine = FALSE;
+            }
+            else
+            {
+                name = nn_decode_str2(t + 8);
+                isMine = TRUE;
+            }
+
+            for (tmp = name; *tmp && *tmp != ':'; tmp++);
+            if (tmp[0] != 0 && tmp[1] == ' ')
+                msg = tmp + 2;
+            else
+                msg = "";
+            *tmp = 0;
+
+            isIgnored = setIgnoreMode && checkIgnoreList(name);
+            win = findWindow(name);
+
+            if (win != NULL)
+            {
+                printMsgF(win, isIgnored ? 0 : LOG_WINDOW,
+                    "½5½<½%d½%s½5½>½0½ %s\n",
+                    isMine ? 14 : 15, isMine ? optUserName : name, msg);
+
+                printMsgF(NULL, LOG_FILE, "½11½%s½0½\n", h);
+            }
+            else
+            {
+                printMsgF(NULL, isIgnored ? LOG_FILE : (LOG_WINDOW | LOG_FILE),
+                    "½11½%s½0½\n", h);
+            }
+            th_free(name);
+            th_free(h);
+        }
+        else
+        {
+            /* It's an action (/me) */
+            char *h = nn_decode_str2(t);
+            printMsgF(NULL, isIgnored ? LOG_FILE : (LOG_WINDOW | LOG_FILE),
+                "½9½* %s½0½\n", h);
+            th_free(h);
+        }
+        th_free(t);
+    }
+    else
+    {
+        /* It's a normal message */
+        char *h;
+        t = nn_strip_tags(s);
+        h = nn_decode_str2(t);
+        printMsgF(NULL, isIgnored ? LOG_FILE : (LOG_WINDOW | LOG_FILE),
+            "½5½<½%d½%s½5½>½0½ %s\n", isMine ? 14 : 15, userName, h);
+        th_free(h);
+        th_free(t);
+    }
+
+done:
+    th_free(s);
+    th_free(userName);
+    return 0;
+}
+
+
+int nnproto_handle_login(nn_conn_t *conn)
+{
+    char tmpStr[256];
+    str_get_timestamp(tmpStr, sizeof(tmpStr), "%c");
+
+    if (!nn_conn_buf_strcmp(conn, "FAILURE>"))
+    {
+        printMsg(NULL, "½1½Login failure½0½ - ½3½%s½0½\n", tmpStr);
+        return -2;
+    }
+    else if (!nn_conn_buf_strcmp(conn, "SUCCESS>"))
+    {
+        printMsg(NULL, "½2½Login success½0½ - ½3½%s½0½\n", tmpStr);
+        nn_conn_send_msg(conn, optUserNameEnc, "%%2FRequestUserList");
+        return 0;
+    }
+    else
+        return 1;
+}
+
+
+int nnproto_handle_add_user(nn_conn_t *conn)
+{
+    char *p, *s, *str = conn->ptr;
+    nn_window_t *win;
+
+    s = nn_conn_buf_strstr(conn, "</ADD_USER>");
+    if (!s) return 1;
+    *s = 0;
+
+    p = nn_dbldecode_str(str);
+    if (!p) return -1;
+
+    win = findWindow(p);
+    nn_userhash_insert(nnUsers, nn_username_encode(p));
+
+    printMsg(NULL, "! ½3½%s½0½ ½2½ADDED.½0½\n", p);
+    if (win != NULL)
+        printMsg(win, "! ½3½%s½0½ ½2½joined the chat.½0½\n", p);
+
+    th_free(p);
+    return 0;
+}
+
+
+int nnproto_handle_delete_user(nn_conn_t *conn)
+{
+    char *p, *s, *str = conn->ptr;
+    nn_window_t *win;
+
+    s = nn_conn_buf_strstr(conn, "</DELETE_USER>");
+    if (!s) return 1;
+    *s = 0;
+
+    p = nn_dbldecode_str(str);
+    if (!p) return -1;
+
+    win = findWindow(p);
+    nn_userhash_delete(nnUsers, nn_username_encode(p));
+
+    printMsg(NULL, "! ½3½%s½0½ ½1½DELETED.½0½\n", p);
+    if (win != NULL)
+        printMsg(win, "! ½3½%s½0½ ½1½left the chat.½0½\n", p);
+
+    th_free(p);
+    return 0;
+}
+
+
+int nnproto_handle_num_clients(nn_conn_t *conn)
+{
+    nn_conn_buf_strstr(conn, "</NUMCLIENTS>");
+    return 0;
+}
+
+
+int nnproto_handle_boot(nn_conn_t *conn)
+{
+    (void) conn;
+    errorMsg("Booted by server.\n");
+    return -1;
+}
+
+
+typedef struct
+{
+    char *cmd;
+    ssize_t len;
+    int (*handler)(nn_conn_t *);
+} nn_protocolcmd_t;
+
+
+static nn_protocolcmd_t protoCmds[] =
+{
+    { "<USER>",         -1, nnproto_handle_user },
+    { "<LOGIN_",        -1, nnproto_handle_login },
+    { "<DELETE_USER>",  -1, nnproto_handle_delete_user },
+    { "<ADD_USER>",     -1, nnproto_handle_add_user },
+    { "<NUMCLIENTS>",   -1, nnproto_handle_num_clients },
+    { "<BOOT />",       -1, nnproto_handle_boot },
+};
+
+static const int nprotoCmds = sizeof(protoCmds) / sizeof(protoCmds[0]);
+
+
+int nn_parse_protocol(nn_conn_t *conn)
+{
+    static BOOL protoCmdsInit = FALSE;
+    int i;
+
+    if (!protoCmdsInit)
+    {
+        for (i = 0; i < nprotoCmds; i++)
+            protoCmds[i].len = strlen(protoCmds[i].cmd);
+
+        protoCmdsInit = TRUE;
+    }
+
+    for (i = 0; i < nprotoCmds; i++)
+    {
+        if (!nn_conn_buf_strncmp(conn, protoCmds[i].cmd, protoCmds[i].len))
+            return protoCmds[i].handler(conn);
+    }
+
+    if (optDebug)
+    {
+        printMsg(NULL, "Unknown protocmd: \"%s\"\n", conn->ptr);
+        return 0;
+    }
+    else
+        return 1;
+}
+
+
+int nn_handle_input(nn_conn_t *conn, char *buf, size_t bufLen)
+{
+    char *tmpStr, tmpBuf[4096];
+    BOOL result;
+
+    /* Trim right */
+    bufLen--;
+    buf[bufLen--] = 0;
+    while (bufLen > 0 && th_isspace(buf[bufLen]))
+        buf[bufLen--] = 0;
+
+    /* Decode completed usernames */
+    nn_username_decode(buf);
+
+    /* Check for special user commands */
+    if (*buf == 0)
+    {
+        return 1;
+    }
+    else if (!th_strncasecmp(buf, "/color ", 7))
+    {
+        /* Change color */
+        int tmpInt;
+        if ((tmpInt = th_get_hex_triplet(str_trim_left(buf + 7))) < 0)
+        {
+            printMsgQ(currWin, "Invalid color value '%s'\n", buf+7);
+            return 1;
+        }
+        optUserColor = tmpInt;
+        printMsgQ(currWin, "Setting color to #%06x\n", optUserColor);
+        nn_conn_send_msg(conn, optUserNameEnc, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor);
+        return 0;
+    }
+    else if (!th_strncasecmp(buf, "/ignore", 7))
+    {
+        char *name = str_trim_left(buf + 7);
+        if (strlen(name) > 0)
+        {
+            /* Add or remove someone to/from ignore */
+            qlist_t *user = th_llist_find_func(setIgnoreList, name, compareUsername);
+            if (user != NULL)
+            {
+                printMsgQ(currWin, "Removed user '%s' from ignore.\n", name);
+                th_llist_delete_node(&setIgnoreList, user);
+            }
+            else
+            {
+                printMsgQ(currWin, "Now ignoring '%s'.\n", name);
+                th_llist_append(&setIgnoreList, th_strdup(name));
+            }
+        }
+        else
+        {
+            /* Just list whomever is in ignore now */
+            qlist_t *user = setIgnoreList;
+            ssize_t nuser = th_llist_length(setIgnoreList);
+            char *result = th_strdup_printf("Users ignored (%d): ", nuser);
+            while (user != NULL)
+            {
+                if (user->data != NULL)
+                {
+                    th_pstr_printf(&result, "%s'%s'", result, (char *) user->data);
+                    if (--nuser > 0)
+                        th_pstr_printf(&result, "%s, ", result);
+                }
+                user = user->next;
+            }
+            printMsgQ(currWin, "%s\n", result);
+            th_free(result);
+        }
+        return 0;
+    }
+    else if (!th_strncasecmp(buf, "/query", 6))
+    {
+        char *name = str_trim_left(buf + 6);
+        if (strlen(name) > 0)
+        {
+            nn_user_t *user = nn_user_find(nnUsers, nn_username_encode(name));
+            if (user != NULL)
+            {
+                name = nn_username_decode(th_strdup(user->name));
+                printMsgQ(currWin, "Opening PRV query for '%s'.\n", name);
+                if (openWindow(name, TRUE))
+                    printMsgQ(currWin, "In PRV query with '%s'.\n", name);
+                th_free(name);
+            }
+        }
+        else
+        {
+            printMsgQ(currWin, "Usage: /query username\n");
+            printMsgQ(currWin, "To close a PRV query, use /close [username]\n");
+            printMsgQ(currWin, "/close without username will close the current PRV window.\n");
+        }
+        return 0;
+    }
+    else if (!th_strncasecmp(buf, "/win", 4))
+    {
+        /* Change color */
+        char *tmp = str_trim_left(buf + 4);
+        if (strlen(tmp) > 0)
+        {
+            int val = atoi(tmp);
+            if (val >= 1 && val < SET_MAX_WINDOWS)
+            {
+                if (chatWindows[val - 1] != NULL)
+                    currWin = chatWindows[val - 1];
+            }
+            else
+            {
+                printMsgQ(currWin, "Invalid window number '%s'\n", tmp);
+                return 1;
+            }
+        }
+        else
+        {
+            printMsgQ(currWin, "Window   : #%d\n", currWin->num);
+            printMsgQ(currWin, "ID       : %s\n", currWin->id);
+        }
+        return 0;
+    }
+    else if (!th_strncasecmp(buf, "/close", 6))
+    {
+        char *name = str_trim_left(buf + 6);
+        if (strlen(name) > 0)
+        {
+            nn_window_t *win = findWindow(name);
+            if (win != NULL)
+            {
+                closeWindow(win);
+                printMsgQ(currWin, "Closed PRV query to '%s'.\n", name);
+            }
+            else
+            {
+                printMsgQ(currWin, "No PRV query by name '%s'.\n", name);
+            }
+        }
+        else
+        {
+            if (currWin != chatWindows[0])
+            {
+                closeWindow(currWin);
+                currWin = chatWindows[0];
+            }
+        }
+        return 0;
+    }
+    else if (!th_strncasecmp(buf, "/save", 5))
+    {
+        /* Save configuration */
+        FILE *cfgfile = fopen(setConfigFile, "w");
+        if (cfgfile == NULL)
+        {
+            printMsgQ(currWin, "Could not create configuration to file '%s': %s\n",
+                setConfigFile, strerror(errno));
+            return 0;
+        }
+        printMsgQ(currWin, "Configuration saved in file '%s', res=%d\n",
+            setConfigFile,
+            th_cfg_write(cfgfile, setConfigFile, cfg));
+
+        fclose(cfgfile);
+        return 0;
+    }
+    else if (!th_strncasecmp(buf, "/w ", 3))
+    {
+        /* Open given username's profile via firefox in a new tab */
+        char *name = str_trim_left(buf + 3);
+
+        printMsg(currWin, "Opening profile for: '%s'\n", name);
+
+        tmpStr = nn_encode_str1(name);
+#ifdef __WIN32
+        {
+            HINSTANCE status;
+            snprintf(tmpBuf, sizeof(tmpBuf), "http://www.newbienudes.com/profile/%s/", tmpStr);
+            th_free(tmpStr);
+            status = ShellExecute(NULL, "open", tmpBuf, NULL, NULL, SW_SHOWNA);
+            if (status <= (HINSTANCE) 32)
+                printMsgQ(currWin, "Could not launch default web browser: %d\n", status);
+        }
+#else
+        {
+            int status;
+            int fds[2];
+            pid_t pid;
+            snprintf(tmpBuf, sizeof(tmpBuf), "openurl(http://www.newbienudes.com/profile/%s/,new-tab)", tmpStr);
+            th_free(tmpStr);
+
+            if (pipe(fds) == -1)
+            {
+                int ret = errno;
+                printMsgQ(currWin, "Could not open process communication pipe! (%d, %s)\n", ret, strerror(ret));
+                return 0;
+            }
+
+            if ((pid = fork()) < 0)
+            {
+                printMsgQ(currWin, "Could not create sub-process!\n");
+            }
+            else if (pid == 0)
+            {
+                dup2(fds[1], STDOUT_FILENO);
+                dup2(fds[0], STDERR_FILENO);
+                execlp(setBrowser, setBrowser, "-remote", tmpBuf, (void *)NULL);
+                _exit(errno);
+            }
+
+            wait(&status);
+        }
+#endif
+        return 0;
+    }
+    else if (!th_strncasecmp(buf, "/who", 4))
+    {
+        /* Alias /who to /listallusers */
+        snprintf(tmpBuf, sizeof(tmpBuf), "/listallusers");
+        buf = tmpBuf;
+    }
+
+    if (currWin != chatWindows[0])
+    {
+        if (currWin->id != NULL)
+        {
+            snprintf(tmpBuf, sizeof(tmpBuf), "/prv -to %s -msg %s", currWin->id, buf);
+            buf = tmpBuf;
+        }
+        else
+        {
+            printMsgQ(NULL, "No target set, exiting prv mode.\n");
+            return 1;
+        }
+    }
+
+    /* Send double-encoded */
+    tmpStr = nn_dblencode_str(nn_username_decode(buf));
+    if (tmpStr == 0) return -2;
+    result = nn_conn_send_msg(conn, optUserNameEnc, "%s", tmpStr);
+    th_free(tmpStr);
+
+    return result ? 0 : -1;
+}
+
+
+void closeWindows(void)
+{
+    if (mainWin) delwin(mainWin);
+    if (statusWin) delwin(statusWin);
+    if (editWin) delwin(editWin);
+}
+
+
+BOOL initializeWindows(void)
+{
+    int w, h;
+
+    getmaxyx(stdscr, h, w);
+
+    closeWindows();
+
+    mainWin = subwin(stdscr, h - 4, w, 0, 0);
+    statusWin = subwin(stdscr, 1, w, h - 4, 0);
+    editWin = subwin(stdscr, 3, w, h - 3, 0);
+
+    if (mainWin == NULL || statusWin == NULL || editWin == NULL)
+        return FALSE;
+
+    return TRUE;
+}
+
+
+void updateWindows(void)
+{
+    if (mainWin) redrawwin(mainWin);
+    if (statusWin) redrawwin(statusWin);
+    if (editWin) redrawwin(editWin);
+}
+
+
+BOOL performTabCompletion(nn_editbuf_t *buf)
+{
+    static char *previous = NULL, *pattern = NULL;
+    BOOL again = FALSE, hasSeparator = FALSE, newPattern = FALSE, hasSpace = FALSE;
+    char *str = buf->data;
+    int mode = 0;
+    ssize_t endPos, startPos = buf->pos;
+
+    /* previous word */
+    if (startPos >= 2 && str[startPos - 1] == ' ' && str[startPos - 2] != ' ')
+    {
+        startPos -= 2;
+        endPos = startPos;
+        while (startPos > 0 && str[startPos - 1] != ' ') startPos--;
+        mode = 1;
+    }
+    else
+    /* middle of a word, new pattern */
+    if (startPos < buf->len && str[startPos] != ' ')
+    {
+        endPos = startPos;
+        while (startPos > 0 && str[startPos - 1] != ' ') startPos--;
+        while (endPos < buf->len - 1 && str[endPos + 1] != ' ') endPos++;
+        newPattern = TRUE;
+        mode = 2;
+    }
+    else
+    /* previous word, new pattern */
+    if (startPos >= 1 && str[startPos - 1] != ' ')
+    {
+        startPos -= 1;
+        endPos = startPos;
+        while (startPos > 0 && str[startPos - 1] != ' ') startPos--;
+        newPattern = TRUE;
+        mode = 3;
+    }
+    else
+    {
+        if (optDebug)
+            printMsg(currWin, "no mode\n");
+        return FALSE;
+    }
+
+    if (str[endPos] == optNickSep)
+    {
+        endPos--;
+        if (startPos > 0)
+        {
+            if (optDebug)
+                printMsg(currWin, "str[endPos] == optNickSep && startPos > 0 (%d)\n", startPos);
+            return FALSE;
+        }
+        hasSeparator = TRUE;
+    }
+
+    if (buf->pos > 0 && str[buf->pos - 1] == ' ')
+        hasSpace = TRUE;
+    if (buf->pos <= buf->len && str[buf->pos] == ' ')
+        hasSpace = TRUE;
+
+    if (newPattern)
+    {
+        /* Get pattern, check if it matches previous pattern and set 'again' flag */
+        char *npattern = nn_editbuf_get_string(buf, startPos, endPos);
+        if (pattern && npattern && th_strcasecmp(npattern, pattern) == 0)
+            again = TRUE;
+
+        th_free(pattern);
+        pattern = npattern;
+
+        if (!again)
+        {
+            th_free(previous);
+            previous = NULL;
+        }
+    }
+
+    if (optDebug)
+    {
+        printMsg(currWin, "sPos=%d, ePos=%d <-> bPos=%d, bufLen=%d : pat='%s' (again=%s, hassep=%s, hasspc=%s, newpat=%s, mode=%d)\n",
+                 startPos, endPos, buf->pos, buf->len, pattern,
+                 again ? "yes" : "no",
+                 hasSeparator ? "yes" : "no",
+                 hasSpace ? "yes" : "no",
+                 newPattern ? "yes" : "no", mode);
+    }
+
+    if (pattern)
+    {
+        nn_user_t *user = nn_user_match(nnUsers, pattern, previous, again);
+
+        if (user)
+        {
+            int i;
+            char *c = user->name;
+            if (optDebug)
+                printMsg(currWin, "match='%s' / prev='%s'\n", user->name, previous);
+
+            for (i = startPos; i <= endPos; i++)
+                nn_editbuf_delete(buf, startPos);
+
+            for (i = startPos; *c; i++, c++)
+                nn_editbuf_insert(buf, i, *c);
+
+            if (!hasSeparator && startPos == 0)
+            {
+                nn_editbuf_insert(buf, i++, optNickSep);
+                startPos++;
+            }
+            if (hasSeparator)
+                startPos++;
+            if (!hasSpace)
+                nn_editbuf_insert(buf, i++, ' ');
+
+            nn_editbuf_setpos(buf, startPos + 1 + strlen(user->name));
+
+            th_free(previous);
+            previous = th_strdup(user->name);
+
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+#define VPUTCH(CH)  th_vputch(&bufData, &bufSize, &bufLen, CH)
+#define VPUTS(STR)  th_vputs(&bufData, &bufSize, &bufLen, STR)
+
+char *logParseFilename(const char *fmt, int id)
+{
+    size_t bufSize = strlen(fmt) + TH_BUFGROW, bufLen = 0;
+    char *bufData = th_malloc(bufSize);
+    char tmpBuf[32];
+    const char *s = fmt;
+
+    while (*s)
+    {
+        if (*s == '%')
+        {
+            s++;
+            switch (*s)
+            {
+            case 'i':
+                snprintf(tmpBuf, sizeof(tmpBuf), "%05d", id);
+                VPUTS(tmpBuf);
+                break;
+
+            case 'd':
+                snprintf(tmpBuf, sizeof(tmpBuf), "%d", id);
+                VPUTS(tmpBuf);
+                break;
+
+            case '%':
+                VPUTCH('%');
+                break;
+            }
+            s++;
+        }
+        else
+        {
+            VPUTCH(*s);
+            s++;
+        }
+    }
+
+    VPUTCH(0);
+    return bufData;
+}
+
+
+BOOL logFileOpen(void)
+{
+    char *filename;
+
+    if (optLogFilename == NULL || !optLogEnable)
+        return FALSE;
+
+    filename = logParseFilename(optLogFilename, optPort);
+
+    if ((optLogFile = fopen(filename, "a")) == NULL)
+    {
+        errorMsg("Could not open logfile '%s' for appending!\n", filename);
+        th_free(filename);
+        return FALSE;
+    }
+
+    th_free(filename);
+
+    return TRUE;
+}
+
+
+void logFileClose(void)
+{
+    if (optLogFile)
+    {
+        fclose(optLogFile);
+        optLogFile = NULL;
+    }
+}
+
+
+char *promptRequester(WINDOW *win, const char *info, BOOL allowEmpty)
+{
+    char tmpBuf[512], *ptr;
+    ssize_t pos;
+    int curVis = curs_set(1);
+
+    echo();
+    waddstr(win, info);
+    wgetnstr(win, tmpBuf, sizeof(tmpBuf) - 1);
+    noecho();
+    if (curVis != ERR)
+        curs_set(curVis);
+
+    for (pos = strlen(tmpBuf) - 1; pos > 0 && th_isspace(tmpBuf[pos]); pos--)
+        tmpBuf[pos] = 0;
+
+    ptr = str_trim_left(tmpBuf);
+
+    if (allowEmpty || strlen(ptr) > 0)
+        return th_strdup(ptr);
+    else
+        return NULL;
+}
+
+
+void printHelp(void)
+{
+    printMsgQ(currWin, "\n"
+    "NNChat Help\n"
+    "===========\n"
+    "\n"
+    "F1             This help.\n"
+    "F2             \n"
+    );
+}
+
+
+int main(int argc, char *argv[])
+{
+    nn_conn_t *conn = NULL;
+    int curVis = ERR, updateCount = 0;
+    BOOL argsOK, isError = FALSE,
+        exitProg = FALSE,
+        colorSet = FALSE,
+        cursesInit = FALSE,
+        networkInit = FALSE,
+        insertMode = TRUE,
+        firstUpdate = TRUE;
+    time_t prevTime;
+    char *tmpStr;
+    nn_editbuf_t *editBuf = nn_editbuf_new(NN_TMPBUF_SIZE);
+    nn_editbuf_t *histBuf[SET_MAX_HISTORY+2];
+    int histPos = 0, histMax = 0;
+
+    cfgitem_t *tmpcfg;
+    char *homeDir = NULL;
+
+    memset(histBuf, 0, sizeof(histBuf));
+
+    /* Initialize */
+    th_init("NNChat", "Newbie Nudes chat client", NN_VERSION,
+        "Written and designed by Anonymous Finnish Guy (C) 2008-2012",
+        "This software is freeware, use and distribute as you wish.");
+    th_verbosityLevel = 0;
+
+    /* Read configuration file */
+    tmpcfg = NULL;
+    th_cfg_add_comment(&tmpcfg, "General settings");
+    th_cfg_add_string(&tmpcfg, "username", &optUserName, NULL);
+    th_cfg_add_string(&tmpcfg, "password", &optPassword, NULL);
+
+    th_cfg_add_comment(&tmpcfg, "Default color as a hex-triplet");
+    th_cfg_add_hexvalue(&tmpcfg, "color", &optUserColor, optUserColor);
+
+    th_cfg_add_comment(&tmpcfg, "Default setting of ignore mode");
+    th_cfg_add_bool(&tmpcfg, "ignore", &setIgnoreMode, setIgnoreMode);
+    th_cfg_add_comment(&tmpcfg, "People to be ignored when ignore mode is enabled");
+    th_cfg_add_string_list(&tmpcfg, "ignore_list", &setIgnoreList);
+
+    th_cfg_add_comment(&tmpcfg, "Random messages for idle timeout protection. If none are set, plain '.' is used.");
+    th_cfg_add_string_list(&tmpcfg, "idle_messages", &setIdleMessages);
+
+    th_cfg_add_comment(&tmpcfg, "Character used as nickname auto-completion separator (default is ':')");
+    th_cfg_add_string(&tmpcfg, "nick_separator", &optNickSepStr, NULL);
+
+    th_cfg_add_section(&cfg, "general", tmpcfg);
+
+
+    tmpcfg = NULL;
+    th_cfg_add_comment(&tmpcfg, "Chat server hostname or IP address");
+    th_cfg_add_string(&tmpcfg, "host", &optServer, optServer);
+    th_cfg_add_comment(&tmpcfg, "Default port to connect to (8005 = main room, 8003 = passion pit)");
+    th_cfg_add_int(&tmpcfg, "port", &optPort, optPort);
+    th_cfg_add_section(&cfg, "server", tmpcfg);
+
+    tmpcfg = NULL;
+    th_cfg_add_comment(&tmpcfg, "Proxy server type (0 = none, 1 = SOCKS 4, 2 = SOCKS 4a)");
+    th_cfg_add_int(&tmpcfg, "type", &optProxyType, optProxyType);
+    th_cfg_add_comment(&tmpcfg, "Proxy server host name");
+    th_cfg_add_string(&tmpcfg, "host", &optProxyServer, optProxyServer);
+    th_cfg_add_comment(&tmpcfg, "Proxy port, 1080 is the standard SOCKS port");
+    th_cfg_add_int(&tmpcfg, "port", &optProxyPort, optProxyPort);
+    th_cfg_add_section(&cfg, "proxy", tmpcfg);
+
+    tmpcfg = NULL;
+    th_cfg_add_comment(&tmpcfg, "Enable logging");
+    th_cfg_add_bool(&tmpcfg, "enable", &optLogEnable, optLogEnable);
+    th_cfg_add_comment(&tmpcfg, "Log filename format");
+    th_cfg_add_string(&tmpcfg, "filename", &optLogFilename, optLogFilename);
+    th_cfg_add_section(&cfg, "logging", tmpcfg);
+
+#ifdef __WIN32
+    {
+        char tmpPath[MAX_PATH];
+        if (SHGetFolderPath(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, tmpPath) == S_OK)
+            homeDir = th_strdup(tmpPath);
+
+        CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
+    }
+#else
+    homeDir = th_strdup(getenv("HOME"));
+#endif
+
+    if (homeDir != NULL)
+    {
+        FILE *cfgfile;
+        setConfigFile = th_strdup_printf("%s" SET_DIR_SEPARATOR "%s", homeDir, SET_CONFIG_FILE);
+
+        THMSG(0, "Reading configuration from '%s'.\n", setConfigFile);
+
+        if ((cfgfile = fopen(setConfigFile, "r")) != NULL)
+        {
+            th_cfg_read(cfgfile, setConfigFile, cfg);
+            fclose(cfgfile);
+        }
+    }
+
+    if (optNickSepStr)
+        optNickSep = optNickSepStr[0];
+    else
+        optNickSep = SET_NICK_SEPARATOR;
+
+
+    setBrowser = getenv("BROWSER");
+    if (setBrowser == NULL)
+        setBrowser = "firefox";
+
+    /* Parse command line arguments */
+    argsOK = th_args_process(argc, argv, optList, optListN,
+                             argHandleOpt, argHandleFile, FALSE);
+
+    if (optUserNameCmd != NULL)
+    {
+        optUserName = optUserNameCmd;
+        optPassword = optPasswordCmd;
+    }
+
+    if (!argsOK)
+        return -2;
+
+    /* Allocate userhash */
+    if ((nnUsers = nn_userhash_new()) == NULL)
+    {
+        THERR("Could not allocate userhash. Fatal error.\n");
+        return -105;
+    }
+
+    /* If no idle messages are set, add default */
+    if (setIdleMessages == NULL)
+    {
+        th_llist_append(&setIdleMessages, th_strdup("."));
+    }
+
+    /* Open logfile */
+    logFileOpen();
+
+    /* Initialize network */
+    if (!nn_network_init())
+    {
+        THERR("Could not initialize network subsystem.\n");
+        goto err_exit;
+    }
+    else
+        networkInit = TRUE;
+
+    /* Initialize NCurses */
+    if (!optDaemon)
+    {
+        if (LINES < 0 || LINES > 1000) LINES = 24;
+        if (COLS < 0 || COLS > 1000) COLS = 80;
+        initscr();
+        raw();
+        keypad(stdscr, TRUE);
+        noecho();
+        meta(stdscr, TRUE);
+        timeout(SET_DELAY);
+        curVis = curs_set(0);
+
+        if (has_colors())
+        {
+            start_color();
+
+            init_pair( 1, COLOR_RED,     COLOR_BLACK);
+            init_pair( 2, COLOR_GREEN,   COLOR_BLACK);
+            init_pair( 3, COLOR_YELLOW,  COLOR_BLACK);
+            init_pair( 4, COLOR_BLUE,    COLOR_BLACK);
+            init_pair( 5, COLOR_MAGENTA, COLOR_BLACK);
+            init_pair( 6, COLOR_CYAN,    COLOR_BLACK);
+            init_pair( 7, COLOR_WHITE,   COLOR_BLACK);
+            init_pair( 8, COLOR_BLACK,   COLOR_BLACK);
+
+            init_pair(10, COLOR_BLACK,   COLOR_RED);
+            init_pair(11, COLOR_WHITE,   COLOR_RED);
+            init_pair(12, COLOR_GREEN,   COLOR_RED);
+            init_pair(13, COLOR_YELLOW,  COLOR_RED);
+            init_pair(14, COLOR_BLUE,    COLOR_RED);
+            init_pair(15, COLOR_MAGENTA, COLOR_RED);
+            init_pair(16, COLOR_CYAN,    COLOR_RED);
+        }
+
+        cursesInit = TRUE;
+
+        if (!initializeWindows())
+            goto err_exit;
+
+#ifdef PDCURSES
+        PDC_set_title("NNChat v" NN_VERSION);
+#endif
+
+        memset(chatWindows, 0, sizeof(chatWindows));
+        chatWindows[0] = nn_window_new(NULL);
+        currWin = chatWindows[0];
+        updateStatus();
+    }
+
+    /* Check if we have username and password */
+    if (cursesInit && (optUserName == NULL || optPassword == NULL))
+    {
+        printWin(editWin, "You can avoid this prompt by issuing '/save' after logging in.\n");
+        optUserName = promptRequester(editWin, "NN username: ", FALSE);
+        optPassword = promptRequester(editWin, "NN password: ", TRUE);
+    }
+
+    if (optUserName == NULL || optPassword == NULL)
+    {
+        errorMsg("Username and/or password not specified.\n");
+        goto err_exit;
+    }
+
+    /* Create a connection */
+    conn = nn_conn_new(errorFunc, messageFunc);
+    if (conn == NULL)
+    {
+        errorMsg("Could not create connection structure.\n");
+        goto err_exit;
+    }
+
+    /* Are we using a proxy? */
+    if (optProxyType != NN_PROXY_NONE && optProxyServer != NULL)
+    {
+        if (nn_conn_set_proxy(conn, optProxyType, optProxyPort, optProxyServer) != 0)
+        {
+            errorMsg("Error setting proxy information.\n");
+            goto err_exit;
+        }
+    }
+
+    /* Okay ... */
+    printMsg(currWin, "Trying to resolve host '%s' ...\n", optServer);
+    conn->host = th_strdup(optServer);
+    conn->hst = nn_resolve_host(conn, optServer);
+    if (conn->hst == NULL)
+    {
+        errorMsg("Could not resolve hostname: %s.\n", strerror(h_errno));
+        goto err_exit;
+    }
+
+#ifdef FINAL_BUILD
+    /* To emulate the official client, we first make a request for
+     * policy file, even though we don't use it for anything...
+     */
+    if (nn_conn_open(conn, 843, NULL) != 0)
+    {
+        errorMsg("Policy file request connection setup failed!\n");
+        goto err_exit;
+    }
+
+    tmpStr = "<policy-file-request/>";
+    if (nn_conn_send_buf(conn, tmpStr, strlen(tmpStr) + 1) == FALSE)
+    {
+        errorMsg("Failed to send policy file request.\n");
+        goto err_exit;
+    }
+    else
+    {
+        int cres = nn_conn_pull(conn);
+        if (cres == 0)
+        {
+            printMsg(currWin, "Probe got: %s\n", conn->buf);
+        }
+        else
+        {
+            printMsg(currWin, "Could not get policy probe.\n");
+        }
+    }
+    nn_conn_close(conn);
+#endif
+
+    /* Okay, now do the proper connection ... */
+    if (nn_conn_open(conn, optPort, NULL) != 0)
+    {
+        errorMsg("Main connection setup failed!\n");
+        goto err_exit;
+    }
+
+    /* Send login command */
+    optUserNameEnc = nn_dblencode_str(optUserName);
+    tmpStr = nn_dblencode_str(optSite);
+    nn_conn_send_msg(conn, optUserNameEnc, "%%2Flogin%%20%%2Dsite%%20%s%%20%%2Dpassword%%20%s", tmpStr, optPassword);
+    th_free(tmpStr);
+
+    /* Initialize random numbers */
+    prevTime = time(NULL);
+    srandom((int) prevTime);
+
+    if (cursesInit)
+    {
+        /* Initialize rest of interactive UI code */
+        nn_editbuf_clear(editBuf);
+
+        /* First update of screen */
+        printEditBuf(editBuf);
+        updateStatus();
+
+        printMsg(NULL, "%s v%s - %s\n", th_prog_name, th_prog_version, th_prog_fullname);
+        printMsg(NULL, "%s\n", th_prog_author);
+        printMsg(NULL, "%s\n", th_prog_license);
+    }
+
+    /* Enter mainloop */
+    nn_conn_reset(conn);
+    while (!isError && !exitProg)
+    {
+        nn_conn_reset(conn);
+        do {
+            int cres = nn_conn_pull(conn);
+            if (cres == 0 && *(conn->in_ptr - 1) == 0)
+            {
+                int result = nn_parse_protocol(conn);
+                if (result > 0)
+                {
+                    /* Couldn't handle the message for some reason */
+                    printMsg(currWin, "Could not handle: %s\n", conn->ptr);
+                }
+                else if (result < 0)
+                {
+                    /* Fatal error, quit */
+                    errorMsg("Fatal error with message: %s\n", conn->ptr);
+                    isError = TRUE;
+                }
+            }
+            else if (cres < 0)
+                isError = TRUE;
+            else
+                break;
+        }
+        while (conn->total_bytes > 0 && !isError);
+
+        if (!nn_conn_check(conn))
+            isError = TRUE;
+
+        /* Handle user input */
+        if (cursesInit)
+        {
+            int c, cnt = 0;
+            BOOL update = FALSE, updateMain = FALSE;
+
+            /* Handle several buffered keypresses at once */
+            do
+            {
+                c = wgetch(stdscr);
+                
+                /* Handle various problematic cases where terminal 
+                 * keycodes do not get properly translated by curses
+                 */
+                if (c == 0x1b)
+                {
+                    /* ^[O */
+                    c = wgetch(stdscr);
+                    if (c == 'O')
+                    {
+                        c = wgetch(stdscr);
+                        switch (c)
+                        {
+                        case 'd':
+                            c = 0x204;
+                            break;
+                        case 'c':
+                            c = 0x206;
+                            break;
+                        default:
+                            if (optDebug)
+                                printMsg(currWin, "Unhandled ESC-O key sequence 0x%02x\n", c);
+                            break;
+                        }
+                    }
+                    /* ^[[ */
+                    else if (c == '[')
+                    {
+                        c = wgetch(stdscr);
+                        switch (c)
+                        {
+                        case 0x31:
+                            c = wgetch(stdscr);
+                            if (c >= 0x31 && c <= 0x39)
+                                c = KEY_F(c - 0x30);
+                            else
+                                c = ERR;
+                            break;
+
+                        case 0x32:
+                            c = KEY_IC;
+                            break;
+                        case 0x33:
+                            c = KEY_DC;
+                            break;
+
+                        case 0x35:
+                            c = KEY_PPAGE;
+                            break;
+                        case 0x36:
+                            c = KEY_NPAGE;
+                            break;
+
+                        case 0x37:
+                            c = KEY_HOME;
+                            break;
+                        case 0x38:
+                            c = KEY_END;
+                            break;
+
+                        default:
+                            if (optDebug)
+                                printMsg(currWin, "Unhandled ESC-[*~ key sequence 0x%02x\n", c);
+                            c = ERR;
+                            break;
+                        }
+                        /* Get the trailing ~ */
+                        if (c != ERR)
+                            wgetch(stdscr);
+                    }
+                    if (c >= 0x31 && c <= 0x39)
+                    {
+                        /* Chat window switching via Meta/Esc-[1..9] */
+                        int win = c - 0x31;
+                        if (win < SET_MAX_WINDOWS && chatWindows[win] != NULL)
+                        {
+                            currWin = chatWindows[win];
+                            update = updateMain = TRUE;
+                        }
+                        c = ERR;
+                    }
+                    else
+                    {
+                        if (optDebug)
+                            printMsg(currWin, "Unhandled ESC key sequence 0x%02x\n", c);
+                    }
+                }
+#if defined(__WIN32) && defined(PDCURSES)
+                else if (c >= 0x198 && c <= 0x1a0)
+                {
+                    /* Chat window switching via Meta/Esc-[1..9] */
+                    int win = c - 0x198;
+                    if (win < SET_MAX_WINDOWS && chatWindows[win] != NULL)
+                    {
+                        currWin = chatWindows[win];
+                        update = updateMain = TRUE;
+                    }
+                    c = ERR;
+                }
+#endif
+
+                switch (c)
+                {
+#ifdef KEY_RESIZE
+                case KEY_RESIZE:
+                    resize_term(0, 0);
+                    erase();
+                    timeout(SET_DELAY);
+
+                    if (!initializeWindows())
+                    {
+                        errorMsg("Error resizing curses chatWindows\n");
+                        isError = TRUE;
+                    }
+                    update = updateMain = TRUE;
+                    break;
+#endif
+
+                case KEY_ENTER:
+                case '\n':
+                case '\r':
+                    /* Call the user input handler */
+                    if (editBuf->len > 0)
+                    {
+                        int result;
+
+                        if (histMax > 0)
+                        {
+                            nn_editbuf_free(histBuf[SET_MAX_HISTORY+1]);
+                            histBuf[SET_MAX_HISTORY+1] = NULL;
+                            memmove(&histBuf[2], &histBuf[1], histMax * sizeof(histBuf[0]));
+                        }
+
+                        histPos = 0;
+                        histBuf[1] = nn_editbuf_copy(editBuf);
+                        if (histMax < SET_MAX_HISTORY) histMax++;
+
+                        nn_editbuf_insert(editBuf, editBuf->len, 0);
+                        result = nn_handle_input(conn, editBuf->data, editBuf->len);
+
+                        nn_editbuf_clear(editBuf);
+
+                        if (result < 0)
+                        {
+                            errorMsg("Fatal error handling user input: %s\n", editBuf->data);
+                            isError = TRUE;
+                        }
+                        else
+                        {
+                            /* Update time value of last sent message for unidle timeouts */
+                            prevTime = time(NULL);
+                        }
+
+                        updateMain = update = TRUE;
+                    }
+                    break;
+
+                case KEY_UP: /* Backwards in input history */
+                    if (histPos == 0)
+                    {
+                        nn_editbuf_free(histBuf[0]);
+                        histBuf[0] = nn_editbuf_copy(editBuf);
+                    }
+                    if (histPos < histMax)
+                    {
+                        histPos++;
+                        nn_editbuf_free(editBuf);
+                        editBuf = nn_editbuf_copy(histBuf[histPos]);
+                        update = TRUE;
+                    }
+                    break;
+
+                case KEY_DOWN: /* Forwards in input history */
+                    if (histPos > 0)
+                    {
+                        histPos--;
+                        nn_editbuf_free(editBuf);
+                        editBuf = nn_editbuf_copy(histBuf[histPos]);
+                        update = TRUE;
+                    }
+                    break;
+
+                case 0x204: /* ctrl+left arrow = Skip words left */
+                case 0x20b:
+                    while (editBuf->pos > 0 && isspace((int) editBuf->data[editBuf->pos - 1]))
+                        editBuf->pos--;
+                    while (editBuf->pos > 0 && !isspace((int) editBuf->data[editBuf->pos - 1]))
+                        editBuf->pos--;
+                    update = TRUE;
+                    break;
+
+                case 0x206: /* ctrl+right arrow = Skip words right */
+                case 0x210:
+                    while (editBuf->pos < editBuf->len && isspace((int) editBuf->data[editBuf->pos]))
+                        editBuf->pos++;
+                    while (editBuf->pos < editBuf->len && !isspace((int) editBuf->data[editBuf->pos]))
+                        editBuf->pos++;
+                    update = TRUE;
+                    break;
+
+                case KEY_HOME:
+                    nn_editbuf_setpos(editBuf, 0);
+                    update = TRUE;
+                    break;
+                case KEY_END:
+                    nn_editbuf_setpos(editBuf, editBuf->len);
+                    update = TRUE;
+                    break;
+                case KEY_LEFT:
+                    nn_editbuf_setpos(editBuf, editBuf->pos - 1);
+                    update = TRUE;
+                    break;
+                case KEY_RIGHT:
+                    nn_editbuf_setpos(editBuf, editBuf->pos + 1);
+                    update = TRUE;
+                    break;
+
+                case KEY_BACKSPACE:
+                case 0x08:
+                case 0x7f:
+                    nn_editbuf_delete(editBuf, editBuf->pos - 1);
+                    nn_editbuf_setpos(editBuf, editBuf->pos - 1);
+                    update = TRUE;
+                    break;
+
+                case KEY_DC: /* Delete character */
+                    nn_editbuf_delete(editBuf, editBuf->pos);
+                    update = TRUE;
+                    break;
+
+
+                case KEY_IC: /* Ins = Toggle insert / overwrite mode */
+                    insertMode = !insertMode;
+                    update = TRUE;
+                    break;
+
+                case KEY_F(1): /* F1 = Print help */
+                    printHelp();
+                    updateMain = TRUE;
+                    break;
+
+                case KEY_F(2): /* F2 = Clear editbuffer */
+                    nn_editbuf_clear(editBuf);
+                    update = TRUE;
+                    break;
+
+                case KEY_F(5): /* F5 = Ignore mode */
+                    setIgnoreMode = !setIgnoreMode;
+                    printMsgQ(currWin, "Ignore mode = %s\n", setIgnoreMode ? "ON" : "OFF");
+                    break;
+
+#if 0
+                case KEY_F(8): /* F8 = Debug */
+                    optDebug = !optDebug;
+                    update = TRUE;
+                    break;
+#endif
+
+                case 0x03: /* ^C = quit */
+                case KEY_F(9): /* F9 = Quit */
+                    printMsg(currWin, "Quitting per user request (%d/0x%x).\n", c, c);
+                    exitProg = TRUE;
+                    break;
+
+                case 0x09: /* Tab = complete username */
+                    performTabCompletion(editBuf);
+                    update = TRUE;
+                    break;
+
+                case 0x0c: /* Ctrl + L */
+                    updateWindows();
+                    update = updateMain = TRUE;
+                    break;
+
+                case KEY_NPAGE:
+                case KEY_PPAGE:
+                    /* Page Up / Page Down */
+                    if (currWin != NULL)
+                    {
+                        int oldPos = currWin->pos;
+
+                        currWin->pos += (c == KEY_NPAGE) ? -10 : +10;
+
+                        if (currWin->pos < 0)
+                            currWin->pos = 0;
+                        else if (currWin->pos >= currWin->data->n - 10)
+                            currWin->pos = currWin->data->n - 10;
+
+                        if (oldPos != currWin->pos)
+                            updateMain = TRUE;
+                    }
+                    break;
+
+                case ERR:
+                    /* Ignore */
+                    break;
+
+                default:
+                    if (isprint(c) || c == 0xe4 || c == 0xf6 || c == 0xc4 || c == 0xd6)
+                    {
+                        if (insertMode)
+                            nn_editbuf_insert(editBuf, editBuf->pos, c);
+                        else
+                            nn_editbuf_write(editBuf, editBuf->pos, c);
+                        nn_editbuf_setpos(editBuf, editBuf->pos + 1);
+                        update = TRUE;
+                    }
+                    else
+                    {
+                        if (optDebug)
+                            printMsg(currWin, "Unhandled key: 0x%02x\n", c);
+                    }
+                    break;
+                }
+            }
+            while (c != ERR && !exitProg && ++cnt < 10);
+
+            updateMainWin(updateMain);
+
+            if (update || firstUpdate)
+            {
+                /* Update edit line */
+                updateStatus();
+                printEditBuf(editBuf);
+                firstUpdate = FALSE; /* a nasty hack ... */
+            }
+
+        } /* cursesInit */
+
+        if (++updateCount > 10)
+        {
+            time_t tmpTime = time(NULL);
+            if (tmpTime - prevTime > SET_KEEPALIVE)
+            {
+                int n = random() % th_llist_length(setIdleMessages);
+                qlist_t *node = th_llist_get_nth(setIdleMessages, n);
+                nn_conn_send_msg(conn, optUserNameEnc, node->data);
+                prevTime = tmpTime;
+            }
+
+            if (!colorSet)
+            {
+                colorSet = TRUE;
+                nn_conn_send_msg(conn, optUserNameEnc, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor);
+            }
+
+            updateStatus();
+            printEditBuf(editBuf);
+            updateCount = 0;
+        }
+
+    }
+
+    /* Shutdown */
+err_exit:
+    th_cfg_free(cfg);
+    th_free(homeDir);
+    th_llist_free_func(setIdleMessages, th_free);
+    nn_userhash_free(nnUsers);
+    nn_editbuf_free(editBuf);
+
+    {
+        int i;
+        for (i = 0; i <= SET_MAX_HISTORY; i++)
+            nn_editbuf_free(histBuf[i]);
+
+        for (i = 0; i < SET_MAX_WINDOWS; i++)
+            nn_window_free(chatWindows[i]);
+    }
+
+#ifdef __WIN32
+    if (errorMessages)
+    {
+        char *tmp;
+        wclear(editWin);
+        tmp = promptRequester(editWin, "Press enter to quit.\n", FALSE);
+        th_free(tmp);
+    }
+#endif
+
+    if (cursesInit)
+    {
+        if (curVis != ERR)
+            curs_set(curVis);
+        closeWindows();
+        endwin();
+        THMSG(1, "NCurses deinitialized.\n");
+    }
+
+#ifndef __WIN32
+    if (errorMessages)
+        THERR("%s", errorMessages);
+#endif
+
+    th_free(optUserNameEnc);
+
+    nn_conn_close(conn);
+
+    if (networkInit)
+        nn_network_close();
+
+    THMSG(1, "Connection terminated.\n");
+
+    logFileClose();
+
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/network.c	Thu May 24 06:41:07 2012 +0300
@@ -0,0 +1,543 @@
+/*
+ * NNChat - Custom chat client for NewbieNudes.com chatrooms
+ * Written by Matti 'ccr' Hämäläinen
+ * (C) Copyright 2008-2012 Tecnic Software productions (TNSP)
+ */
+#include "network.h"
+
+
+
+#ifdef __WIN32
+const char *hstrerror(int err)
+{
+    static char buf[64];
+    snprintf(buf, sizeof(buf), "Error #%d", err);
+    return buf;
+}
+
+
+int nn_get_socket_errno(void)
+{
+    return WSAGetLastError();
+}
+
+
+const char *nn_get_socket_errstr(int err)
+{
+    static char buf[64];
+    switch (err)
+    {
+        case WSAEADDRINUSE:      return "Address already in use";
+        case WSAECONNABORTED:    return "Software caused connection abort";
+        case WSAECONNREFUSED:    return "Connection refused";
+        case WSAECONNRESET:      return "Connection reset by peer";
+        case WSAEHOSTUNREACH:    return "No route to host";
+        case WSAENETDOWN:        return "Network is down";
+        case WSAETIMEDOUT:       return "Connection timed out";
+        case WSAHOST_NOT_FOUND:  return "Host not found";
+        case WSAVERNOTSUPPORTED: return "Wrong WinSock DLL version";
+        default:
+            snprintf(buf, sizeof(buf), "Error #%d", err);
+            return buf;
+            break;
+    }
+}
+#else
+int nn_get_socket_errno(void)
+{
+    return errno;
+}
+
+
+const char *nn_get_socket_errstr(int err)
+{
+    return strerror(err);
+}
+#endif
+
+
+void nn_conn_err(nn_conn_t *conn, const char *fmt, ...)
+{
+    if (conn->errfunc != NULL)
+    {
+        va_list ap;
+        va_start(ap, fmt);
+        conn->errfunc(conn, fmt, ap);
+        va_end(ap);
+    }
+}
+
+
+static void nn_conn_msg(nn_conn_t *conn, const char *fmt, ...)
+{
+    if (conn->msgfunc != NULL)
+    {
+        va_list ap;
+        va_start(ap, fmt);
+        conn->msgfunc(conn, fmt, ap);
+        va_end(ap);
+    }
+}
+
+struct hostent *nn_resolve_host(nn_conn_t *conn, const char *name)
+{
+    struct hostent *res = gethostbyname(name);
+    if (res == NULL)
+        nn_conn_err(conn, "Could not resolve hostname: %s\n", strerror(h_errno));
+    else
+        nn_conn_msg(conn, "True hostname for %s is %s\n", name, res->h_name);
+
+    return res;
+}
+
+static const char *nn_proxy_types[] =
+{
+    "none",
+    "SOCKS 4",
+    "SOCKS 4a",
+    NULL
+};
+
+
+nn_conn_t * nn_conn_new(
+    void (*errfunc)(nn_conn_t *conn, const char *fmt, va_list ap),
+    void (*msgfunc)(nn_conn_t *conn, const char *fmt, va_list ap))
+{
+    nn_conn_t *conn = th_calloc(1, sizeof(nn_conn_t));
+
+    if (conn == NULL)
+        return NULL;
+
+    conn->errfunc = errfunc;
+    conn->msgfunc = msgfunc;
+
+    return conn;
+}
+
+static BOOL nn_get_addr(struct in_addr *addr, struct hostent *hst)
+{
+    if (hst != NULL)
+    {
+        *addr = *(struct in_addr *) (hst->h_addr);
+        return TRUE;
+    }
+    else
+    {
+        addr->s_addr = 0;
+        return FALSE;
+    }
+}
+
+int nn_conn_set_proxy(nn_conn_t *conn, int type, int port, const char *host)
+{
+    if (conn == NULL)
+        return -1;
+
+    conn->proxy.type = type;
+    conn->proxy.port = port;
+    conn->proxy.host = th_strdup(host);
+
+    if (host != NULL)
+    {
+        conn->proxy.hst = nn_resolve_host(conn, host);
+        nn_get_addr(&(conn->proxy.addr), conn->proxy.hst);
+    }
+    else
+        return -2;
+
+    return 0;
+}
+
+int nn_conn_open(nn_conn_t *conn, const int port, const char *host)
+{
+    struct sockaddr_in dest;
+    static const char *userid = "James Bond";
+
+    if (conn == NULL)
+        return -1;
+
+    conn->port = port;
+    if (host != NULL)
+    {
+        conn->host = th_strdup(host);
+        conn->hst = nn_resolve_host(conn, host);
+    }
+
+    nn_get_addr(&(conn->addr), conn->hst);
+
+    /* Prepare for connection */
+    dest.sin_family = AF_INET;
+
+    if (conn->proxy.type > NN_PROXY_NONE && conn->proxy.type < NN_PROXY_LAST)
+    {
+        dest.sin_port = htons(conn->proxy.port);
+        dest.sin_addr = conn->proxy.addr;
+
+        nn_conn_msg(conn, "Connecting to %s proxy %s:%d ...\n",
+            nn_proxy_types[conn->proxy.type],
+            inet_ntoa(conn->proxy.addr), conn->proxy.port);
+    }
+    else
+    {
+        dest.sin_port = htons(conn->port);
+        dest.sin_addr = conn->addr;
+
+        nn_conn_msg(conn, "Connecting to %s:%d ...\n",
+            inet_ntoa(conn->addr), conn->port);
+    }
+
+    if ((conn->socket = socket(PF_INET, SOCK_STREAM, 0)) == -1)
+    {
+        conn->err = nn_get_socket_errno();
+        nn_conn_err(conn, "Could not open socket: %s\n", nn_get_socket_errstr(conn->err));
+        goto error;
+    }
+
+    nn_conn_msg(conn, "Using socket %d.\n", conn->socket);
+
+    if (connect(conn->socket, (struct sockaddr *) &dest, sizeof(dest)) == -1)
+    {
+        conn->err = nn_get_socket_errno();
+        nn_conn_err(conn, "Could not connect: %s\n", nn_get_socket_errstr(conn->err));
+        goto error;
+    }
+
+    FD_ZERO(&(conn->sockfds));
+    FD_SET(conn->socket, &(conn->sockfds));
+
+    /* Proxy-specific setup */
+    if (conn->proxy.type == NN_PROXY_SOCKS4 || conn->proxy.type == NN_PROXY_SOCKS4A)
+    {
+        struct nn_socks_t *socksh;
+        size_t bufsiz = sizeof(struct nn_socks_t) + strlen(userid) + 1;
+        char *ptr, *buf;
+        int tries, status = -1;
+
+        if (conn->proxy.type == NN_PROXY_SOCKS4A)
+            bufsiz += strlen(conn->host) + 1;
+
+        ptr = buf = th_malloc(bufsiz);
+        if (buf == NULL)
+        {
+            conn->err = -1;
+            nn_conn_err(conn, "Could not allocate memory for SOCKS negotiation buffer, %d bytes.\n", bufsiz);
+            goto error;
+        }
+
+        /* Create SOCKS 4/4A request */
+        nn_conn_msg(conn, "Initializing proxy negotiation.\n");
+        socksh = (struct nn_socks_t *) buf;
+        socksh->version = 4;
+        socksh->command = SOCKS_CMD_CONNECT;
+        socksh->port = htons(port);
+        if (conn->proxy.type == NN_PROXY_SOCKS4A)
+            socksh->addr = htonl(0x00000032);
+        else
+            socksh->addr = conn->addr.s_addr;
+        ptr += sizeof(struct nn_socks_t);
+
+        strcpy(ptr, userid);
+
+        if (conn->proxy.type == NN_PROXY_SOCKS4A)
+        {
+            ptr += strlen(userid) + 1;
+            strcpy(ptr, conn->host);
+        }
+
+        /* Send request */
+        nn_conn_reset(conn);
+        if (!nn_conn_send_buf(conn, buf, bufsiz))
+        {
+            th_free(buf);
+            nn_conn_err(conn, "Error sending SOCKS proxy request.\n");
+            goto error;
+        }
+        th_free(buf);
+
+        /* Wait for SOCKS server to reply */
+        for (status = tries = 1; tries <= 20 && status > 0; tries++)
+        {
+#ifdef __WIN32
+            Sleep(50);
+#else
+            usleep(50000);
+#endif
+            nn_conn_reset(conn);
+            status = nn_conn_pull(conn);
+        }
+
+        /* Check results */
+        if (status == 0)
+        {
+            struct nn_socks_res_t *res = (struct nn_socks_res_t *) &(conn->buf);
+            if (res->nb != 0)
+            {
+                nn_conn_err(conn, "Invalid SOCKS server reply, does not begin with NUL byte (%d).\n", res->nb);
+                goto error;
+            }
+            if (res->result != 0x5a)
+            {
+                char *s = NULL;
+                switch (res->result)
+                {
+                    case 0x5b: s = "Request rejected or failed"; break;
+                    case 0x5c: s = "Request failed because client is not running identd (or not reachable from the server)"; break;
+                    case 0x5d: s = "Request failed because client's identd could not confirm the user ID string in the request"; break;
+                    default: s = "Unknown SOCKS error response"; break;
+                }
+                nn_conn_err(conn, "SOCKS setup failed, 0x%02x: %s.\n", res->result, s);
+                goto error;
+            }
+            nn_conn_msg(conn, "SOCKS connection established!\n");
+        }
+        else if (status < 0)
+        {
+            nn_conn_err(conn, "Proxy negotiation failed at try %d with network error: %d\n", tries, status);
+            goto error;
+        }
+        else
+        {
+            nn_conn_err(conn, "Proxy negotiation timed out.\n");
+            goto error;
+        }
+    }
+
+    nn_conn_reset(conn);
+    conn->status = NN_CONN_OPEN;
+    return 0;
+
+error:
+    conn->status = NN_CONN_CLOSED;
+    return -2;
+}
+
+
+void nn_conn_close(nn_conn_t *conn)
+{
+    if (conn == NULL)
+        return;
+
+    if (conn->socket >= 0)
+    {
+#ifdef __WIN32
+        closesocket(conn->socket);
+#else
+        close(conn->socket);
+#endif
+        conn->socket = -1;
+    }
+
+    th_free(conn->host);
+    th_free(conn->proxy.host);
+
+    conn->status = NN_CONN_CLOSED;
+
+    th_free(conn);
+}
+
+
+BOOL nn_conn_send_buf(nn_conn_t *conn, const char *buf, const size_t len)
+{
+    size_t bufLeft = len;
+    const char *bufPtr = buf;
+
+    while (bufLeft > 0)
+    {
+        ssize_t bufSent;
+        bufSent = send(conn->socket, bufPtr, bufLeft, 0);
+        if (bufSent < 0)
+        {
+            conn->err = nn_get_socket_errno();
+            nn_conn_err(conn, "nn_conn_send_buf() failed: %s", nn_get_socket_errstr(conn->err));
+            return FALSE;
+        }
+        bufLeft -= bufSent;
+        bufPtr += bufSent;
+    }
+
+    return TRUE;
+}
+
+void nn_conn_reset(nn_conn_t *conn)
+{
+    if (conn != NULL)
+    {
+        conn->ptr = conn->in_ptr = conn->buf;
+        conn->got_bytes = conn->total_bytes = 0;
+    }
+}
+
+
+int nn_conn_pull(nn_conn_t *conn)
+{
+    int result;
+    struct timeval socktv;
+    fd_set tmpfds;
+
+    if (conn == NULL)
+        return -10;
+
+    /* Prod the input buffer */
+    if (conn->in_ptr > conn->buf && conn->in_ptr - conn->ptr > 0)
+    {
+        size_t delta = conn->in_ptr - conn->ptr;
+        memmove(conn->buf, conn->in_ptr, delta);
+        conn->ptr = conn->buf;
+        conn->in_ptr -= delta;
+        conn->total_bytes -= delta;
+    }
+
+    /* Check for incoming data */
+    socktv.tv_sec = 0;
+    socktv.tv_usec = NN_DELAY_USEC;
+    tmpfds = conn->sockfds;
+
+    if ((result = select(conn->socket + 1, &tmpfds, NULL, NULL, &socktv)) == -1)
+    {
+        conn->err = nn_get_socket_errno();
+        if (conn->err != EINTR)
+        {
+            nn_conn_err(conn, "Error occured in select(%d, sockfds): %d, %s\n",
+                socket, conn->err, nn_get_socket_errstr(conn->err));
+            return -1;
+        }
+    }
+    else if (FD_ISSET(conn->socket, &tmpfds))
+    {
+        conn->got_bytes = recv(conn->socket, conn->in_ptr, NN_CONNBUF_SIZE - conn->total_bytes, 0);
+        if (conn->got_bytes < 0)
+        {
+            conn->err = nn_get_socket_errno();
+            nn_conn_err(conn, "Error in recv: %d, %s\n", conn->err, nn_get_socket_errstr(conn->err));
+            return -2;
+        }
+        else if (conn->got_bytes == 0)
+        {
+            nn_conn_err(conn, "Server closed connection.\n");
+            conn->status = NN_CONN_CLOSED;
+            return -3;
+        }
+        else
+        {
+            conn->total_bytes += conn->got_bytes;
+            conn->in_ptr += conn->got_bytes;
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+
+BOOL nn_conn_check(nn_conn_t *conn)
+{
+    if (conn == NULL)
+        return FALSE;
+
+    return conn->err == 0 && conn->status == NN_CONN_OPEN;
+}
+
+
+BOOL nn_network_init(void)
+{
+#ifdef __WIN32
+    /* Initialize WinSock, if needed */
+    WSADATA wsaData;
+    int err = WSAStartup(0x0101, &wsaData);
+    if (err != 0)
+    {
+        THERR("Could not initialize WinSock library (err=%d).\n", err);
+        return FALSE;
+    }
+#endif
+    return TRUE;
+}
+
+
+void nn_network_close(void)
+{
+#ifdef __WIN32
+    WSACleanup();
+#endif
+}
+
+
+BOOL nn_conn_buf_check(nn_conn_t *conn, size_t n)
+{
+    return conn && conn->ptr && conn->in_ptr && (conn->ptr + n <= conn->in_ptr);
+}
+
+
+BOOL nn_conn_buf_skip(nn_conn_t *conn, size_t n)
+{
+    if (nn_conn_buf_check(conn, n))
+    {
+        conn->ptr += n;
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int nn_conn_buf_strncmp(nn_conn_t *conn, const char *str, const size_t n)
+{
+    int ret;
+    if (!nn_conn_buf_check(conn, n))
+        return -1;
+
+    if ((ret = strncmp(conn->ptr, str, n)) == 0)
+    {
+        conn->ptr += n;
+        return 0;
+    }
+    else
+        return ret;
+}
+
+
+int nn_conn_buf_strcmp(nn_conn_t *conn, const char *str)
+{
+    return nn_conn_buf_strncmp(conn, str, strlen(str));
+}
+
+
+char *nn_conn_buf_strstr(nn_conn_t *conn, const char *str)
+{
+    char *pos;
+    size_t n = strlen(str);
+
+    if (nn_conn_buf_check(conn, n) && ((pos = strstr(conn->ptr, str)) != NULL))
+    {
+        conn->ptr = pos + n;
+        return pos;
+    }
+    else
+        return NULL;
+}
+
+
+BOOL nn_conn_send_msg(nn_conn_t *conn, const char *user, const char *fmt, ...)
+{
+    char *tmp, *msg;
+    va_list ap;
+
+    va_start(ap, fmt);
+    tmp = th_strdup_vprintf(fmt, ap);
+    va_end(ap);
+
+    if (tmp == NULL)
+        return FALSE;
+
+    msg = th_strdup_printf("<USER>%s</USER><MESSAGE>%s</MESSAGE>", user, tmp);
+    th_free(tmp);
+
+    if (msg != NULL)
+    {
+        BOOL ret = nn_conn_send_buf(conn, msg, strlen(msg) + 1);
+        th_free(msg);
+        return ret;
+    }
+    else
+        return FALSE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/network.h	Thu May 24 06:41:07 2012 +0300
@@ -0,0 +1,132 @@
+/*
+ * NNChat - Custom chat client for NewbieNudes.com chatrooms
+ * Written by Matti 'ccr' Hämäläinen
+ * (C) Copyright 2008-2012 Tecnic Software productions (TNSP)
+ */
+#ifndef LIBNNCHAT_H
+#define LIBNNCHAT_H
+
+#include <stdio.h>
+#include <unistd.h>
+#include "th_types.h"
+#include "th_string.h"
+
+#ifdef __WIN32
+#include <windows.h>
+#include <winsock.h>
+typedef uint16_t in_port_t;
+typedef uint32_t in_addr_t;
+#else
+#include <sys/select.h>
+#include <sys/socket.h>
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#include <arpa/inet.h>
+#include <netdb.h>
+#endif
+#include <sys/types.h>
+#include <time.h>
+#include <errno.h>
+
+
+#define NN_CONNBUF_SIZE   (64 * 1024)
+#define NN_DELAY_USEC     (15 * 1000)
+
+
+enum
+{
+    NN_CONN_UNINIT = 0,
+    NN_CONN_PROXY_NEG,
+    NN_CONN_OPEN,
+    NN_CONN_CLOSED
+};
+
+enum
+{
+    NN_PROXY_NONE = 0,
+    NN_PROXY_SOCKS4,
+    NN_PROXY_SOCKS4A,
+
+    NN_PROXY_LAST
+};
+
+enum
+{
+    SOCKS_CMD_CONNECT = 1,
+    SOCKS_CMD_BIND = 2
+};
+
+struct nn_socks_t
+{
+    uint8_t version;
+    uint8_t command;
+    in_port_t port;
+    in_addr_t addr;
+} __attribute__((__packed__));
+
+struct nn_socks_res_t
+{
+    uint8_t nb;
+    uint8_t result;
+    in_port_t port;
+    in_addr_t addr;
+} __attribute__((__packed__));
+
+typedef struct _nn_conn_t
+{
+    struct
+    {
+        char *host;
+        struct hostent *hst;
+        int type;
+        int port;
+        struct in_addr addr;
+    } proxy;
+
+    char *host;
+    struct hostent *hst;
+    int port;
+
+    int socket;
+    struct in_addr addr;
+    fd_set sockfds;
+
+    void (*errfunc)(struct _nn_conn_t *conn, const char *fmt, va_list ap);
+    void (*msgfunc)(struct _nn_conn_t *conn, const char *fmt, va_list ap);
+
+    int err;
+    int status;
+
+    char buf[NN_CONNBUF_SIZE + 16];
+    char *ptr, *in_ptr;
+    ssize_t got_bytes, total_bytes;
+} nn_conn_t;
+
+
+const char *nn_get_errstr(int err);
+BOOL        nn_network_init();
+void        nn_network_close(void);
+
+struct hostent *nn_resolve_host(nn_conn_t *conn, const char *name);
+nn_conn_t * nn_conn_new(
+    void (*errfunc)(nn_conn_t *conn, const char *fmt, va_list ap),
+    void (*msgfunc)(nn_conn_t *conn, const char *fmt, va_list ap));
+
+int         nn_conn_set_proxy(nn_conn_t *conn, int type, int port, const char *host);
+int         nn_conn_open(nn_conn_t *conn, const int port, const char *host);
+void        nn_conn_close(nn_conn_t *);
+void        nn_conn_reset(nn_conn_t *);
+int         nn_conn_pull(nn_conn_t *);
+BOOL        nn_conn_send_buf(nn_conn_t *, const char *buf, const size_t len);
+BOOL        nn_conn_send_msg(nn_conn_t *, const char *user, const char *fmt, ...);
+BOOL        nn_conn_check(nn_conn_t *);
+
+
+BOOL        nn_conn_buf_check(nn_conn_t *conn, size_t n);
+BOOL        nn_conn_buf_skip(nn_conn_t *conn, size_t n);
+int         nn_conn_buf_strncmp(nn_conn_t *conn, const char *str, const size_t n);
+int         nn_conn_buf_strcmp(nn_conn_t *conn, const char *str);
+char *      nn_conn_buf_strstr(nn_conn_t *conn, const char *str);
+
+#endif
--- a/nnchat.c	Thu May 24 06:38:26 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2158 +0,0 @@
-/*
- * NNChat - Custom chat client for NewbieNudes.com chatrooms
- * Written by Matti 'ccr' Hämäläinen
- * (C) Copyright 2008-2011 Tecnic Software productions (TNSP)
- */
-#include "libnnutil.h"
-#include "libnnnet.h"
-#include "th_args.h"
-#include "th_config.h"
-#ifdef __WIN32
-/* Undefine because both windows.h and curses.h #define it */
-#undef MOUSE_MOVED
-#include <shlwapi.h>
-#else
-#include <sys/wait.h>
-#endif
-#ifdef HAVE_NCURSES_H
-#include <ncurses.h>
-#else
-#include <curses.h>
-#endif
-
-#ifdef __WIN32
-#define SET_CONFIG_FILE    "nnchat.txt"
-#define SET_DIR_SEPARATOR  "\\"
-#define SET_DELAY          (0)
-#else
-#define SET_CONFIG_FILE    ".nnchat"
-#define SET_DIR_SEPARATOR  "/"
-#define SET_DELAY          (5)
-#endif
-
-#define SET_NICK_SEPARATOR ':'
-
-#define SET_MAX_HISTORY (16)        /* Command history length */
-#define SET_KEEPALIVE   (15*60)     /* Ping/keepalive period in seconds */
-#define SET_MAX_WINDOWS (32)
-
-
-/* Options
- */
-int     optPort = 8005,
-        optProxyPort = 1080,
-        optProxyType = NN_PROXY_NONE;
-int     optUserColor = 0x000000;
-char    *optServer = "chat.newbienudes.com",
-        *optProxyServer = NULL,
-        *optUserName = NULL,
-        *optUserNameCmd = NULL,
-        *optUserNameEnc = NULL,
-        *optPassword = NULL,
-        *optPasswordCmd = NULL,
-        *optLogFilename = NULL,
-        *optSite = "NN",
-        *optNickSepStr = NULL;
-char    optNickSep;
-BOOL    optDaemon = FALSE;
-FILE    *optLogFile = NULL;
-BOOL    setIgnoreMode = FALSE;
-BOOL    optDebug = FALSE;
-BOOL    optLogEnable = FALSE;
-
-nn_window_t *chatWindows[SET_MAX_WINDOWS],
-        *currWin = NULL;
-WINDOW  *mainWin = NULL,
-        *statusWin = NULL,
-        *editWin = NULL;
-
-qlist_t *setIgnoreList = NULL,
-        *setIdleMessages = NULL;
-nn_userhash_t *nnUsers = NULL;
-char    *setConfigFile = NULL,
-        *setBrowser = NULL;
-cfgitem_t *cfg = NULL;
-
-
-/* Logging mode flags
- */
-enum
-{
-    LOG_FILE   = 1,
-    LOG_WINDOW = 2,
-    LOG_STAMP  = 4
-};
-
-
-/* Arguments
- */
-optarg_t optList[] =
-{
-    { 0, '?', "help",       "Show this help", OPT_NONE },
-    { 1, 'v', "verbose",    "Be more verbose", OPT_NONE },
-    { 2, 'p', "port",       "Connect to port", OPT_ARGREQ },
-    { 3, 's', "server",     "Server to connect to", OPT_ARGREQ },
-    { 4, 'C', "color",      "Initial color in RGB hex 000000", OPT_ARGREQ },
-    { 5, 'l', "logfile",    "Log filename", OPT_ARGREQ },
-    { 6, 'D', "daemon",     "A pseudo-daemon mode for logging", OPT_NONE },
-    { 7, 'f', "force-site", "Force site (default: NN)", OPT_ARGREQ },
-    { 8, 'd', "debug",      "Enable various debug features", OPT_NONE },
-
-    {10, '4', "socks4",     "SOCKS4 proxy server", OPT_ARGREQ },
-    {11, 'A', "socks4a",    "SOCKS4A proxy server", OPT_ARGREQ },
-    {12, 'P', "proxy-port", "Proxy port (default: 1080)", OPT_ARGREQ },
-};
-
-const int optListN = (sizeof(optList) / sizeof(optList[0]));
-
-
-void argShowHelp(void)
-{
-    th_print_banner(stdout, th_prog_name,
-                    "[options] <username> <password>");
-
-    th_args_help(stdout, optList, optListN);
-}
-
-
-BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
-{
-    switch (optN)
-    {
-    case 0:
-        argShowHelp();
-        exit(0);
-        break;
-
-    case 1:
-        th_verbosityLevel++;
-        break;
-
-    case 2:
-        optPort = atoi(optArg);
-        break;
-
-    case 3:
-        optServer = optArg;
-        break;
-
-    case 4:
-        if ((optUserColor = th_get_hex_triplet(optArg)) < 0)
-        {
-            THERR("Invalid color argument '%s', should be a RGB hex triplet '000000'.\n",
-                optArg);
-            return FALSE;
-        }
-        THMSG(1, "Using color #%06x\n", optUserColor);
-        break;
-
-    case 5:
-        optLogFilename = optArg;
-        optLogEnable = TRUE;
-        break;
-
-    case 7:
-        optSite = optArg;
-        break;
-
-    case 6:
-        optDaemon = TRUE;
-        THMSG(1, "Running in pseudo-daemon mode.\n");
-        break;
-
-    case 8:
-        optDebug = TRUE;
-        THMSG(1, "Debug mode enabled.\n");
-        break;
-
-
-    case 10:
-        optProxyServer = optArg;
-        optProxyType = NN_PROXY_SOCKS4;
-        break;
-
-    case 11:
-        optProxyServer = optArg;
-        optProxyType = NN_PROXY_SOCKS4A;
-        break;
-
-    case 12:
-        optPort = atoi(optArg);
-        break;
-
-
-    default:
-        THERR("Unknown option '%s'.\n", currArg);
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-
-BOOL argHandleFile(char *currArg)
-{
-    if (!optUserNameCmd)
-        optUserNameCmd = currArg;
-    else if (!optPasswordCmd)
-        optPasswordCmd = currArg;
-    else
-    {
-        THERR("Username '%s' already specified on commandline!\n", optUserNameCmd);
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-BOOL str_get_timestamp(char *str, size_t len, const char *fmt)
-{
-    time_t stamp = time(NULL);
-    struct tm *stamp_tm;
-    if ((stamp_tm = localtime(&stamp)) != NULL)
-    {
-        strftime(str, len, fmt, stamp_tm);
-        return TRUE;
-    }
-    else
-    {
-        str[0] = 0;
-        return FALSE;
-    }
-}
-
-
-char * str_trim_left(char *buf)
-{
-    while (*buf != 0 && th_isspace(*buf)) buf++;
-    return buf;
-}
-
-int compareUsername(const void *s1, const void *s2)
-{
-    return th_strcasecmp((char *) s1, (char *) s2);
-}
-
-nn_window_t *findWindow(const char *id)
-{
-    int i;
-
-    for (i = 0; i < SET_MAX_WINDOWS; i++)
-        if (chatWindows[i] != NULL &&
-            chatWindows[i]->id != NULL &&
-            th_strcasecmp(id, chatWindows[i]->id) == 0)
-            return chatWindows[i];
-
-    return NULL;
-}
-
-
-BOOL openWindow(const char *name, BOOL curwin)
-{
-    int i;
-    nn_window_t *res;
-    if (name == NULL)
-        return FALSE;
-
-    if ((res = nn_window_new(name)) == NULL)
-        return FALSE;
-
-    for (i = 1; i < SET_MAX_WINDOWS; i++)
-        if (chatWindows[i] == NULL)
-        {
-            res->num = i;
-            chatWindows[i] = res;
-            if (curwin)
-                currWin = res;
-            return TRUE;
-        }
-
-    return FALSE;
-}
-
-
-void closeWindow(nn_window_t *win)
-{
-    int i;
-    if (win == NULL) return;
-
-    for (i = 1; i < SET_MAX_WINDOWS; i++)
-        if (chatWindows[i] == win)
-        {
-            chatWindows[i] = NULL;
-            nn_window_free(win);
-            return;
-        }
-}
-
-
-void updateStatus(void)
-{
-    char tmpStr[128];
-    int i;
-
-    if (statusWin == NULL) return;
-
-    str_get_timestamp(tmpStr, sizeof(tmpStr), "%H:%M:%S");
-
-    wbkgdset(statusWin, COLOR_PAIR(10));
-    werase(statusWin);
-
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(11));
-    mvwaddstr(statusWin, 0, 1, tmpStr);
-
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
-    waddstr(statusWin, " | ");
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(16));
-    waddstr(statusWin, optUserName);
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
-
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
-    waddstr(statusWin, " | ");
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(11));
-    snprintf(tmpStr, sizeof(tmpStr), "#%06x", optUserColor);
-    waddstr(statusWin, tmpStr);
-
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
-    waddstr(statusWin, " | WIN: ");
-    snprintf(tmpStr, sizeof(tmpStr), "%d: %s / %d",
-        currWin->num + 1,
-        currWin->id != NULL ? currWin->id : "MAIN",
-        currWin->pos);
-    waddstr(statusWin, tmpStr);
-
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
-    waddstr(statusWin, " | ");
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(11));
-
-    for (i = 0; i < SET_MAX_WINDOWS; i++)
-        if (chatWindows[i] != NULL && chatWindows[i]->dirty)
-        {
-            snprintf(tmpStr, sizeof(tmpStr), "%d ", i + 1);
-            waddstr(statusWin, tmpStr);
-        }
-
-    wrefresh(statusWin);
-}
-
-
-void printEditBuf(nn_editbuf_t *buf)
-{
-    char *tmp;
-    if (editWin == NULL || buf == NULL) return;
-
-    buf->data[buf->len] = 0;
-    tmp = nn_username_decode(th_strdup(buf->data));
-
-    werase(editWin);
-
-    wattrset(editWin, A_NORMAL);
-
-    if (buf->pos < buf->len)
-    {
-        waddnstr(editWin, tmp, buf->pos);
-        wattrset(editWin, A_REVERSE);
-        waddch(editWin, tmp[buf->pos]);
-        wattrset(editWin, A_NORMAL);
-        waddnstr(editWin, tmp + buf->pos + 1, buf->len - buf->pos - 1);
-    }
-    else
-    {
-        waddnstr(editWin, tmp, buf->len);
-        wattrset(editWin, A_REVERSE);
-        waddch(editWin, ' ');
-        wattrset(editWin, A_NORMAL);
-    }
-    wrefresh(editWin);
-    th_free(tmp);
-}
-
-
-int printWin(WINDOW *win, const char *fmt)
-{
-    const char *s = fmt;
-    int col = 0;
-
-    while (*s)
-    {
-        if (*s == '½')
-        {
-            s++;
-            if (*s == '½')
-            {
-                waddch(win, ((unsigned char) *s) | col);
-                s++;
-            }
-            else
-            {
-                memcpy(&col, s, sizeof(int));
-                s += sizeof(int);
-            }
-        }
-        else
-        {
-            waddch(win, ((unsigned char) *s) | col);
-            s++;
-        }
-    }
-    return 0;
-}
-
-
-#define QPUTCH(ch) th_vputch(&(win->buf), &(win->bufsize), &(win->len), ch)
-
-int nn_window_print(nn_window_t *win, const char *fmt)
-{
-    const char *s = fmt;
-    int col = 0;
-    while (*s)
-    {
-        if (*s == '½')
-        {
-            s++;
-            if (*s == '½')
-            {
-                QPUTCH(*s);
-                QPUTCH(*s);
-                win->chlen++;
-            }
-            else
-            {
-                int val = 0;
-                while (*s >= '0' && *s <= '9')
-                {
-                    val *= 10;
-                    val += (*s - '0');
-                    s++;
-                }
-                if (*s != '½') return -1;
-
-                if (val < 9)
-                    col = A_DIM | COLOR_PAIR(val);
-                else if (val < 30)
-                    col = A_BOLD | COLOR_PAIR(val - 9);
-
-                QPUTCH('½');
-
-                if (!th_growbuf(&(win->buf), &(win->bufsize), &(win->len), sizeof(int)))
-                    return -2;
-
-                memcpy(win->buf + win->len, &col, sizeof(int));
-                win->len += sizeof(int);
-            }
-        }
-        else if (*s == '\n')
-        {
-            QPUTCH('\n');
-            QPUTCH(0);
-            th_ringbuf_add(win->data, win->buf);
-            win->buf = NULL;
-            win->chlen = 0;
-            win->dirty = TRUE;
-        }
-        else if (*s != '\r')
-        {
-            QPUTCH((unsigned char) *s == 255 ? ' ' : *s);
-            win->chlen++;
-        }
-
-        s++;
-    }
-
-    return 0;
-}
-
-
-BOOL updateMainWin(BOOL force)
-{
-    int h, offs;
-    qringbuf_t *buf;
-
-    /* Check pointers */
-    if (mainWin == NULL || currWin == NULL)
-        return FALSE;
-
-    /* Check if update is forced or if the window is dirty */
-    if (!force && !currWin->dirty)
-        return FALSE;
-
-    /* Compute how many lines from backbuffer fit on the screen */
-    buf = currWin->data;
-    h = getmaxy(mainWin);
-
-    /* Clear and redraw window */
-    werase(mainWin);
-    scrollok(mainWin, 1);
-    for (offs = buf->size - h - currWin->pos; offs >= 0 && offs < buf->size - currWin->pos && offs < buf->size; offs++)
-    {
-        if (buf->data[offs] != NULL)
-            printWin(mainWin, buf->data[offs]);
-    }
-
-    currWin->dirty = FALSE;
-    wrefresh(mainWin);
-    return TRUE;
-}
-
-
-int printFile(FILE *outFile, const char *fmt)
-{
-    const char *s = fmt;
-
-    while (*s)
-    {
-        if (*s == '½')
-        {
-            s++;
-            if (*s == '½')
-            {
-                fputc((unsigned char) *s, outFile);
-                s++;
-            }
-            else
-            {
-                while (*s && isdigit((int) *s)) s++;
-                if (*s != '½') return -1;
-                s++;
-            }
-        }
-        else
-        {
-            if ((unsigned char) *s == 255)
-                fputc(' ', outFile);
-            else
-                fputc((unsigned char) *s, outFile);
-            s++;
-        }
-    }
-
-    return 0;
-}
-
-void printMsgV(nn_window_t *win, int flags, const char *fmt, va_list ap)
-{
-    char tmpStr[128], *buf;
-
-    str_get_timestamp(tmpStr, sizeof(tmpStr), "½17½[½11½%H:%M:%S½17½]½0½ ");
-
-    buf = th_strdup_vprintf(fmt, ap);
-
-    if (optLogFile && (flags & LOG_FILE))
-    {
-        if (flags & LOG_STAMP) printFile(optLogFile, tmpStr);
-        printFile(optLogFile, buf);
-        fflush(optLogFile);
-    }
-
-    if (!optDaemon && (flags & LOG_WINDOW))
-    {
-        nn_window_t *tmp = win != NULL ? win : chatWindows[0];
-        if (flags & LOG_STAMP) nn_window_print(tmp, tmpStr);
-        nn_window_print(tmp, buf);
-    }
-
-    th_free(buf);
-}
-
-void printMsg(nn_window_t *win, const char *fmt, ...)
-{
-    va_list ap;
-
-    va_start(ap, fmt);
-    printMsgV(win, LOG_STAMP | LOG_WINDOW | LOG_FILE, fmt, ap);
-    va_end(ap);
-}
-
-void printMsgF(nn_window_t *win, int flags, const char *fmt, ...)
-{
-    va_list ap;
-
-    va_start(ap, fmt);
-    printMsgV(win, flags | LOG_STAMP, fmt, ap);
-    va_end(ap);
-}
-
-void printMsgQ(nn_window_t *win, const char *fmt, ...)
-{
-    va_list ap;
-
-    va_start(ap, fmt);
-    printMsgV(win, LOG_STAMP | LOG_WINDOW, fmt, ap);
-    va_end(ap);
-}
-
-
-char *errorMessages = NULL;
-
-void errorMsgV(const char *fmt, va_list ap)
-{
-    char *tmp = th_strdup_vprintf(fmt, ap);
-
-    printMsg(NULL, "%s", tmp);
-
-    if (errorMessages != NULL)
-    {
-        char *tmp2 = th_strdup_printf("%s%s", errorMessages, tmp);
-        th_free(errorMessages);
-        th_free(tmp);
-        errorMessages = tmp2;
-    }
-    else
-        errorMessages = tmp;
-}
-
-void errorMsg(const char *fmt, ...)
-{
-    va_list ap;
-
-    va_start(ap, fmt);
-    errorMsgV(fmt, ap);
-    va_end(ap);
-}
-
-void errorFunc(struct _nn_conn_t *conn, const char *fmt, va_list ap)
-{
-    (void) conn;
-    errorMsgV(fmt, ap);
-}
-
-void messageFunc(struct _nn_conn_t *conn, const char *fmt, va_list ap)
-{
-    (void) conn;
-    printMsgV(NULL, LOG_STAMP | LOG_WINDOW | LOG_FILE, fmt, ap);
-}
-
-
-BOOL checkIgnoreList(const char *name)
-{
-    qlist_t *node = setIgnoreList;
-    while (node != NULL)
-    {
-        if (th_strcasecmp(name, (char *) node->data) == 0)
-            return TRUE;
-        node = node->next;
-    }
-    return FALSE;
-}
-
-
-int nnproto_handle_user(nn_conn_t *conn)
-{
-    static const char *msg = "</USER><MESSAGE>";
-    char *p = conn->ptr;
-    BOOL isMine, isIgnored = FALSE;
-    char *s, *t, *userName;
-
-    /* Find start of the message */
-    s = strstr(p, msg);
-    if (!s) return 1;
-    *s = 0;
-    s += strlen(msg);
-
-    /* Find end of the message */
-    t = strstr(s, "</MESSAGE>");
-    if (!t) return 3;
-    *t = 0;
-
-    /* Decode message string */
-    s = nn_decode_str1(s);
-    if (!s) return -1;
-
-    /* Decode username */
-    userName = nn_decode_str1(p);
-    if (!userName)
-    {
-        th_free(s);
-        return -2;
-    }
-
-    /* Check if the username is on our ignore list and
-     * that it is not our OWN username!
-     */
-    isMine = strcmp(userName, optUserName) == 0;
-    isIgnored = setIgnoreMode && !isMine && checkIgnoreList(userName);
-
-    /* Is it a special control message? */
-    if (*s == '/')
-    {
-        /* Ignore room join/leave messages */
-        if (!optDebug && (strstr(s, "left the room") || strstr(s, "joined the room from")))
-            goto done;
-
-        t = nn_strip_tags(s + 1);
-        if (!strncmp(t, "BPRV ", 5))
-        {
-            char *name, *tmp, *msg, *h;
-            nn_window_t *win;
-            h = nn_decode_str2(t + 1);
-
-            if (!strncmp(t, "BPRV from ", 10))
-            {
-                name = nn_decode_str2(t + 10);
-                isMine = FALSE;
-            }
-            else
-            {
-                name = nn_decode_str2(t + 8);
-                isMine = TRUE;
-            }
-
-            for (tmp = name; *tmp && *tmp != ':'; tmp++);
-            if (tmp[0] != 0 && tmp[1] == ' ')
-                msg = tmp + 2;
-            else
-                msg = "";
-            *tmp = 0;
-
-            isIgnored = setIgnoreMode && checkIgnoreList(name);
-            win = findWindow(name);
-
-            if (win != NULL)
-            {
-                printMsgF(win, isIgnored ? 0 : LOG_WINDOW,
-                    "½5½<½%d½%s½5½>½0½ %s\n",
-                    isMine ? 14 : 15, isMine ? optUserName : name, msg);
-
-                printMsgF(NULL, LOG_FILE, "½11½%s½0½\n", h);
-            }
-            else
-            {
-                printMsgF(NULL, isIgnored ? LOG_FILE : (LOG_WINDOW | LOG_FILE),
-                    "½11½%s½0½\n", h);
-            }
-            th_free(name);
-            th_free(h);
-        }
-        else
-        {
-            /* It's an action (/me) */
-            char *h = nn_decode_str2(t);
-            printMsgF(NULL, isIgnored ? LOG_FILE : (LOG_WINDOW | LOG_FILE),
-                "½9½* %s½0½\n", h);
-            th_free(h);
-        }
-        th_free(t);
-    }
-    else
-    {
-        /* It's a normal message */
-        char *h;
-        t = nn_strip_tags(s);
-        h = nn_decode_str2(t);
-        printMsgF(NULL, isIgnored ? LOG_FILE : (LOG_WINDOW | LOG_FILE),
-            "½5½<½%d½%s½5½>½0½ %s\n", isMine ? 14 : 15, userName, h);
-        th_free(h);
-        th_free(t);
-    }
-
-done:
-    th_free(s);
-    th_free(userName);
-    return 0;
-}
-
-
-int nnproto_handle_login(nn_conn_t *conn)
-{
-    char tmpStr[256];
-    str_get_timestamp(tmpStr, sizeof(tmpStr), "%c");
-
-    if (!nn_conn_buf_strcmp(conn, "FAILURE>"))
-    {
-        printMsg(NULL, "½1½Login failure½0½ - ½3½%s½0½\n", tmpStr);
-        return -2;
-    }
-    else if (!nn_conn_buf_strcmp(conn, "SUCCESS>"))
-    {
-        printMsg(NULL, "½2½Login success½0½ - ½3½%s½0½\n", tmpStr);
-        nn_conn_send_msg(conn, optUserNameEnc, "%%2FRequestUserList");
-        return 0;
-    }
-    else
-        return 1;
-}
-
-
-int nnproto_handle_add_user(nn_conn_t *conn)
-{
-    char *p, *s, *str = conn->ptr;
-    nn_window_t *win;
-
-    s = nn_conn_buf_strstr(conn, "</ADD_USER>");
-    if (!s) return 1;
-    *s = 0;
-
-    p = nn_dbldecode_str(str);
-    if (!p) return -1;
-
-    win = findWindow(p);
-    nn_userhash_insert(nnUsers, nn_username_encode(p));
-
-    printMsg(NULL, "! ½3½%s½0½ ½2½ADDED.½0½\n", p);
-    if (win != NULL)
-        printMsg(win, "! ½3½%s½0½ ½2½joined the chat.½0½\n", p);
-
-    th_free(p);
-    return 0;
-}
-
-
-int nnproto_handle_delete_user(nn_conn_t *conn)
-{
-    char *p, *s, *str = conn->ptr;
-    nn_window_t *win;
-
-    s = nn_conn_buf_strstr(conn, "</DELETE_USER>");
-    if (!s) return 1;
-    *s = 0;
-
-    p = nn_dbldecode_str(str);
-    if (!p) return -1;
-
-    win = findWindow(p);
-    nn_userhash_delete(nnUsers, nn_username_encode(p));
-
-    printMsg(NULL, "! ½3½%s½0½ ½1½DELETED.½0½\n", p);
-    if (win != NULL)
-        printMsg(win, "! ½3½%s½0½ ½1½left the chat.½0½\n", p);
-
-    th_free(p);
-    return 0;
-}
-
-
-int nnproto_handle_num_clients(nn_conn_t *conn)
-{
-    nn_conn_buf_strstr(conn, "</NUMCLIENTS>");
-    return 0;
-}
-
-
-int nnproto_handle_boot(nn_conn_t *conn)
-{
-    (void) conn;
-    errorMsg("Booted by server.\n");
-    return -1;
-}
-
-
-typedef struct
-{
-    char *cmd;
-    ssize_t len;
-    int (*handler)(nn_conn_t *);
-} nn_protocolcmd_t;
-
-
-static nn_protocolcmd_t protoCmds[] =
-{
-    { "<USER>",         -1, nnproto_handle_user },
-    { "<LOGIN_",        -1, nnproto_handle_login },
-    { "<DELETE_USER>",  -1, nnproto_handle_delete_user },
-    { "<ADD_USER>",     -1, nnproto_handle_add_user },
-    { "<NUMCLIENTS>",   -1, nnproto_handle_num_clients },
-    { "<BOOT />",       -1, nnproto_handle_boot },
-};
-
-static const int nprotoCmds = sizeof(protoCmds) / sizeof(protoCmds[0]);
-
-
-int nn_parse_protocol(nn_conn_t *conn)
-{
-    static BOOL protoCmdsInit = FALSE;
-    int i;
-
-    if (!protoCmdsInit)
-    {
-        for (i = 0; i < nprotoCmds; i++)
-            protoCmds[i].len = strlen(protoCmds[i].cmd);
-
-        protoCmdsInit = TRUE;
-    }
-
-    for (i = 0; i < nprotoCmds; i++)
-    {
-        if (!nn_conn_buf_strncmp(conn, protoCmds[i].cmd, protoCmds[i].len))
-            return protoCmds[i].handler(conn);
-    }
-
-    if (optDebug)
-    {
-        printMsg(NULL, "Unknown protocmd: \"%s\"\n", conn->ptr);
-        return 0;
-    }
-    else
-        return 1;
-}
-
-
-int nn_handle_input(nn_conn_t *conn, char *buf, size_t bufLen)
-{
-    char *tmpStr, tmpBuf[4096];
-    BOOL result;
-
-    /* Trim right */
-    bufLen--;
-    buf[bufLen--] = 0;
-    while (bufLen > 0 && th_isspace(buf[bufLen]))
-        buf[bufLen--] = 0;
-
-    /* Decode completed usernames */
-    nn_username_decode(buf);
-
-    /* Check for special user commands */
-    if (*buf == 0)
-    {
-        return 1;
-    }
-    else if (!th_strncasecmp(buf, "/color ", 7))
-    {
-        /* Change color */
-        int tmpInt;
-        if ((tmpInt = th_get_hex_triplet(str_trim_left(buf + 7))) < 0)
-        {
-            printMsgQ(currWin, "Invalid color value '%s'\n", buf+7);
-            return 1;
-        }
-        optUserColor = tmpInt;
-        printMsgQ(currWin, "Setting color to #%06x\n", optUserColor);
-        nn_conn_send_msg(conn, optUserNameEnc, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor);
-        return 0;
-    }
-    else if (!th_strncasecmp(buf, "/ignore", 7))
-    {
-        char *name = str_trim_left(buf + 7);
-        if (strlen(name) > 0)
-        {
-            /* Add or remove someone to/from ignore */
-            qlist_t *user = th_llist_find_func(setIgnoreList, name, compareUsername);
-            if (user != NULL)
-            {
-                printMsgQ(currWin, "Removed user '%s' from ignore.\n", name);
-                th_llist_delete_node(&setIgnoreList, user);
-            }
-            else
-            {
-                printMsgQ(currWin, "Now ignoring '%s'.\n", name);
-                th_llist_append(&setIgnoreList, th_strdup(name));
-            }
-        }
-        else
-        {
-            /* Just list whomever is in ignore now */
-            qlist_t *user = setIgnoreList;
-            ssize_t nuser = th_llist_length(setIgnoreList);
-            char *result = th_strdup_printf("Users ignored (%d): ", nuser);
-            while (user != NULL)
-            {
-                if (user->data != NULL)
-                {
-                    th_pstr_printf(&result, "%s'%s'", result, (char *) user->data);
-                    if (--nuser > 0)
-                        th_pstr_printf(&result, "%s, ", result);
-                }
-                user = user->next;
-            }
-            printMsgQ(currWin, "%s\n", result);
-            th_free(result);
-        }
-        return 0;
-    }
-    else if (!th_strncasecmp(buf, "/query", 6))
-    {
-        char *name = str_trim_left(buf + 6);
-        if (strlen(name) > 0)
-        {
-            nn_user_t *user = nn_user_find(nnUsers, nn_username_encode(name));
-            if (user != NULL)
-            {
-                name = nn_username_decode(th_strdup(user->name));
-                printMsgQ(currWin, "Opening PRV query for '%s'.\n", name);
-                if (openWindow(name, TRUE))
-                    printMsgQ(currWin, "In PRV query with '%s'.\n", name);
-                th_free(name);
-            }
-        }
-        else
-        {
-            printMsgQ(currWin, "Usage: /query username\n");
-            printMsgQ(currWin, "To close a PRV query, use /close [username]\n");
-            printMsgQ(currWin, "/close without username will close the current PRV window.\n");
-        }
-        return 0;
-    }
-    else if (!th_strncasecmp(buf, "/win", 4))
-    {
-        /* Change color */
-        char *tmp = str_trim_left(buf + 4);
-        if (strlen(tmp) > 0)
-        {
-            int val = atoi(tmp);
-            if (val >= 1 && val < SET_MAX_WINDOWS)
-            {
-                if (chatWindows[val - 1] != NULL)
-                    currWin = chatWindows[val - 1];
-            }
-            else
-            {
-                printMsgQ(currWin, "Invalid window number '%s'\n", tmp);
-                return 1;
-            }
-        }
-        else
-        {
-            printMsgQ(currWin, "Window   : #%d\n", currWin->num);
-            printMsgQ(currWin, "ID       : %s\n", currWin->id);
-        }
-        return 0;
-    }
-    else if (!th_strncasecmp(buf, "/close", 6))
-    {
-        char *name = str_trim_left(buf + 6);
-        if (strlen(name) > 0)
-        {
-            nn_window_t *win = findWindow(name);
-            if (win != NULL)
-            {
-                closeWindow(win);
-                printMsgQ(currWin, "Closed PRV query to '%s'.\n", name);
-            }
-            else
-            {
-                printMsgQ(currWin, "No PRV query by name '%s'.\n", name);
-            }
-        }
-        else
-        {
-            if (currWin != chatWindows[0])
-            {
-                closeWindow(currWin);
-                currWin = chatWindows[0];
-            }
-        }
-        return 0;
-    }
-    else if (!th_strncasecmp(buf, "/save", 5))
-    {
-        /* Save configuration */
-        FILE *cfgfile = fopen(setConfigFile, "w");
-        if (cfgfile == NULL)
-        {
-            printMsgQ(currWin, "Could not create configuration to file '%s': %s\n",
-                setConfigFile, strerror(errno));
-            return 0;
-        }
-        printMsgQ(currWin, "Configuration saved in file '%s', res=%d\n",
-            setConfigFile,
-            th_cfg_write(cfgfile, setConfigFile, cfg));
-
-        fclose(cfgfile);
-        return 0;
-    }
-    else if (!th_strncasecmp(buf, "/w ", 3))
-    {
-        /* Open given username's profile via firefox in a new tab */
-        char *name = str_trim_left(buf + 3);
-
-        printMsg(currWin, "Opening profile for: '%s'\n", name);
-
-        tmpStr = nn_encode_str1(name);
-#ifdef __WIN32
-        {
-            HINSTANCE status;
-            snprintf(tmpBuf, sizeof(tmpBuf), "http://www.newbienudes.com/profile/%s/", tmpStr);
-            th_free(tmpStr);
-            status = ShellExecute(NULL, "open", tmpBuf, NULL, NULL, SW_SHOWNA);
-            if (status <= (HINSTANCE) 32)
-                printMsgQ(currWin, "Could not launch default web browser: %d\n", status);
-        }
-#else
-        {
-            int status;
-            int fds[2];
-            pid_t pid;
-            snprintf(tmpBuf, sizeof(tmpBuf), "openurl(http://www.newbienudes.com/profile/%s/,new-tab)", tmpStr);
-            th_free(tmpStr);
-
-            if (pipe(fds) == -1)
-            {
-                int ret = errno;
-                printMsgQ(currWin, "Could not open process communication pipe! (%d, %s)\n", ret, strerror(ret));
-                return 0;
-            }
-
-            if ((pid = fork()) < 0)
-            {
-                printMsgQ(currWin, "Could not create sub-process!\n");
-            }
-            else if (pid == 0)
-            {
-                dup2(fds[1], STDOUT_FILENO);
-                dup2(fds[0], STDERR_FILENO);
-                execlp(setBrowser, setBrowser, "-remote", tmpBuf, (void *)NULL);
-                _exit(errno);
-            }
-
-            wait(&status);
-        }
-#endif
-        return 0;
-    }
-    else if (!th_strncasecmp(buf, "/who", 4))
-    {
-        /* Alias /who to /listallusers */
-        snprintf(tmpBuf, sizeof(tmpBuf), "/listallusers");
-        buf = tmpBuf;
-    }
-
-    if (currWin != chatWindows[0])
-    {
-        if (currWin->id != NULL)
-        {
-            snprintf(tmpBuf, sizeof(tmpBuf), "/prv -to %s -msg %s", currWin->id, buf);
-            buf = tmpBuf;
-        }
-        else
-        {
-            printMsgQ(NULL, "No target set, exiting prv mode.\n");
-            return 1;
-        }
-    }
-
-    /* Send double-encoded */
-    tmpStr = nn_dblencode_str(nn_username_decode(buf));
-    if (tmpStr == 0) return -2;
-    result = nn_conn_send_msg(conn, optUserNameEnc, "%s", tmpStr);
-    th_free(tmpStr);
-
-    return result ? 0 : -1;
-}
-
-
-void closeWindows(void)
-{
-    if (mainWin) delwin(mainWin);
-    if (statusWin) delwin(statusWin);
-    if (editWin) delwin(editWin);
-}
-
-
-BOOL initializeWindows(void)
-{
-    int w, h;
-
-    getmaxyx(stdscr, h, w);
-
-    closeWindows();
-
-    mainWin = subwin(stdscr, h - 4, w, 0, 0);
-    statusWin = subwin(stdscr, 1, w, h - 4, 0);
-    editWin = subwin(stdscr, 3, w, h - 3, 0);
-
-    if (mainWin == NULL || statusWin == NULL || editWin == NULL)
-        return FALSE;
-
-    return TRUE;
-}
-
-
-void updateWindows(void)
-{
-    if (mainWin) redrawwin(mainWin);
-    if (statusWin) redrawwin(statusWin);
-    if (editWin) redrawwin(editWin);
-}
-
-
-BOOL performTabCompletion(nn_editbuf_t *buf)
-{
-    static char *previous = NULL, *pattern = NULL;
-    BOOL again = FALSE, hasSeparator = FALSE, newPattern = FALSE, hasSpace = FALSE;
-    char *str = buf->data;
-    int mode = 0;
-    ssize_t endPos, startPos = buf->pos;
-
-    /* previous word */
-    if (startPos >= 2 && str[startPos - 1] == ' ' && str[startPos - 2] != ' ')
-    {
-        startPos -= 2;
-        endPos = startPos;
-        while (startPos > 0 && str[startPos - 1] != ' ') startPos--;
-        mode = 1;
-    }
-    else
-    /* middle of a word, new pattern */
-    if (startPos < buf->len && str[startPos] != ' ')
-    {
-        endPos = startPos;
-        while (startPos > 0 && str[startPos - 1] != ' ') startPos--;
-        while (endPos < buf->len - 1 && str[endPos + 1] != ' ') endPos++;
-        newPattern = TRUE;
-        mode = 2;
-    }
-    else
-    /* previous word, new pattern */
-    if (startPos >= 1 && str[startPos - 1] != ' ')
-    {
-        startPos -= 1;
-        endPos = startPos;
-        while (startPos > 0 && str[startPos - 1] != ' ') startPos--;
-        newPattern = TRUE;
-        mode = 3;
-    }
-    else
-    {
-        if (optDebug)
-            printMsg(currWin, "no mode\n");
-        return FALSE;
-    }
-
-    if (str[endPos] == optNickSep)
-    {
-        endPos--;
-        if (startPos > 0)
-        {
-            if (optDebug)
-                printMsg(currWin, "str[endPos] == optNickSep && startPos > 0 (%d)\n", startPos);
-            return FALSE;
-        }
-        hasSeparator = TRUE;
-    }
-
-    if (buf->pos > 0 && str[buf->pos - 1] == ' ')
-        hasSpace = TRUE;
-    if (buf->pos <= buf->len && str[buf->pos] == ' ')
-        hasSpace = TRUE;
-
-    if (newPattern)
-    {
-        /* Get pattern, check if it matches previous pattern and set 'again' flag */
-        char *npattern = nn_editbuf_get_string(buf, startPos, endPos);
-        if (pattern && npattern && th_strcasecmp(npattern, pattern) == 0)
-            again = TRUE;
-
-        th_free(pattern);
-        pattern = npattern;
-
-        if (!again)
-        {
-            th_free(previous);
-            previous = NULL;
-        }
-    }
-
-    if (optDebug)
-    {
-        printMsg(currWin, "sPos=%d, ePos=%d <-> bPos=%d, bufLen=%d : pat='%s' (again=%s, hassep=%s, hasspc=%s, newpat=%s, mode=%d)\n",
-                 startPos, endPos, buf->pos, buf->len, pattern,
-                 again ? "yes" : "no",
-                 hasSeparator ? "yes" : "no",
-                 hasSpace ? "yes" : "no",
-                 newPattern ? "yes" : "no", mode);
-    }
-
-    if (pattern)
-    {
-        nn_user_t *user = nn_user_match(nnUsers, pattern, previous, again);
-
-        if (user)
-        {
-            int i;
-            char *c = user->name;
-            if (optDebug)
-                printMsg(currWin, "match='%s' / prev='%s'\n", user->name, previous);
-
-            for (i = startPos; i <= endPos; i++)
-                nn_editbuf_delete(buf, startPos);
-
-            for (i = startPos; *c; i++, c++)
-                nn_editbuf_insert(buf, i, *c);
-
-            if (!hasSeparator && startPos == 0)
-            {
-                nn_editbuf_insert(buf, i++, optNickSep);
-                startPos++;
-            }
-            if (hasSeparator)
-                startPos++;
-            if (!hasSpace)
-                nn_editbuf_insert(buf, i++, ' ');
-
-            nn_editbuf_setpos(buf, startPos + 1 + strlen(user->name));
-
-            th_free(previous);
-            previous = th_strdup(user->name);
-
-            return TRUE;
-        }
-    }
-
-    return FALSE;
-}
-
-
-#define VPUTCH(CH)  th_vputch(&bufData, &bufSize, &bufLen, CH)
-#define VPUTS(STR)  th_vputs(&bufData, &bufSize, &bufLen, STR)
-
-char *logParseFilename(const char *fmt, int id)
-{
-    size_t bufSize = strlen(fmt) + TH_BUFGROW, bufLen = 0;
-    char *bufData = th_malloc(bufSize);
-    char tmpBuf[32];
-    const char *s = fmt;
-
-    while (*s)
-    {
-        if (*s == '%')
-        {
-            s++;
-            switch (*s)
-            {
-            case 'i':
-                snprintf(tmpBuf, sizeof(tmpBuf), "%05d", id);
-                VPUTS(tmpBuf);
-                break;
-
-            case 'd':
-                snprintf(tmpBuf, sizeof(tmpBuf), "%d", id);
-                VPUTS(tmpBuf);
-                break;
-
-            case '%':
-                VPUTCH('%');
-                break;
-            }
-            s++;
-        }
-        else
-        {
-            VPUTCH(*s);
-            s++;
-        }
-    }
-
-    VPUTCH(0);
-    return bufData;
-}
-
-
-BOOL logFileOpen(void)
-{
-    char *filename;
-
-    if (optLogFilename == NULL || !optLogEnable)
-        return FALSE;
-
-    filename = logParseFilename(optLogFilename, optPort);
-
-    if ((optLogFile = fopen(filename, "a")) == NULL)
-    {
-        errorMsg("Could not open logfile '%s' for appending!\n", filename);
-        th_free(filename);
-        return FALSE;
-    }
-
-    th_free(filename);
-
-    return TRUE;
-}
-
-
-void logFileClose(void)
-{
-    if (optLogFile)
-    {
-        fclose(optLogFile);
-        optLogFile = NULL;
-    }
-}
-
-
-char *promptRequester(WINDOW *win, const char *info, BOOL allowEmpty)
-{
-    char tmpBuf[512], *ptr;
-    ssize_t pos;
-    int curVis = curs_set(1);
-
-    echo();
-    waddstr(win, info);
-    wgetnstr(win, tmpBuf, sizeof(tmpBuf) - 1);
-    noecho();
-    if (curVis != ERR)
-        curs_set(curVis);
-
-    for (pos = strlen(tmpBuf) - 1; pos > 0 && th_isspace(tmpBuf[pos]); pos--)
-        tmpBuf[pos] = 0;
-
-    ptr = str_trim_left(tmpBuf);
-
-    if (allowEmpty || strlen(ptr) > 0)
-        return th_strdup(ptr);
-    else
-        return NULL;
-}
-
-
-void printHelp(void)
-{
-    printMsgQ(currWin, "\n"
-    "NNChat Help\n"
-    "===========\n"
-    "\n"
-    "F1             This help.\n"
-    "F2             \n"
-    );
-}
-
-
-int main(int argc, char *argv[])
-{
-    nn_conn_t *conn = NULL;
-    int curVis = ERR, updateCount = 0;
-    BOOL argsOK, isError = FALSE,
-        exitProg = FALSE,
-        colorSet = FALSE,
-        cursesInit = FALSE,
-        networkInit = FALSE,
-        insertMode = TRUE,
-        firstUpdate = TRUE;
-    time_t prevTime;
-    char *tmpStr;
-    nn_editbuf_t *editBuf = nn_editbuf_new(NN_TMPBUF_SIZE);
-    nn_editbuf_t *histBuf[SET_MAX_HISTORY+2];
-    int histPos = 0, histMax = 0;
-
-    cfgitem_t *tmpcfg;
-    char *homeDir = NULL;
-
-    memset(histBuf, 0, sizeof(histBuf));
-
-    /* Initialize */
-    th_init("NNChat", "Newbie Nudes chat client", NN_VERSION,
-        "Written and designed by Anonymous Finnish Guy (C) 2008-2012",
-        "This software is freeware, use and distribute as you wish.");
-    th_verbosityLevel = 0;
-
-    /* Read configuration file */
-    tmpcfg = NULL;
-    th_cfg_add_comment(&tmpcfg, "General settings");
-    th_cfg_add_string(&tmpcfg, "username", &optUserName, NULL);
-    th_cfg_add_string(&tmpcfg, "password", &optPassword, NULL);
-
-    th_cfg_add_comment(&tmpcfg, "Default color as a hex-triplet");
-    th_cfg_add_hexvalue(&tmpcfg, "color", &optUserColor, optUserColor);
-
-    th_cfg_add_comment(&tmpcfg, "Default setting of ignore mode");
-    th_cfg_add_bool(&tmpcfg, "ignore", &setIgnoreMode, setIgnoreMode);
-    th_cfg_add_comment(&tmpcfg, "People to be ignored when ignore mode is enabled");
-    th_cfg_add_string_list(&tmpcfg, "ignore_list", &setIgnoreList);
-
-    th_cfg_add_comment(&tmpcfg, "Random messages for idle timeout protection. If none are set, plain '.' is used.");
-    th_cfg_add_string_list(&tmpcfg, "idle_messages", &setIdleMessages);
-
-    th_cfg_add_comment(&tmpcfg, "Character used as nickname auto-completion separator (default is ':')");
-    th_cfg_add_string(&tmpcfg, "nick_separator", &optNickSepStr, NULL);
-
-    th_cfg_add_section(&cfg, "general", tmpcfg);
-
-
-    tmpcfg = NULL;
-    th_cfg_add_comment(&tmpcfg, "Chat server hostname or IP address");
-    th_cfg_add_string(&tmpcfg, "host", &optServer, optServer);
-    th_cfg_add_comment(&tmpcfg, "Default port to connect to (8005 = main room, 8003 = passion pit)");
-    th_cfg_add_int(&tmpcfg, "port", &optPort, optPort);
-    th_cfg_add_section(&cfg, "server", tmpcfg);
-
-    tmpcfg = NULL;
-    th_cfg_add_comment(&tmpcfg, "Proxy server type (0 = none, 1 = SOCKS 4, 2 = SOCKS 4a)");
-    th_cfg_add_int(&tmpcfg, "type", &optProxyType, optProxyType);
-    th_cfg_add_comment(&tmpcfg, "Proxy server host name");
-    th_cfg_add_string(&tmpcfg, "host", &optProxyServer, optProxyServer);
-    th_cfg_add_comment(&tmpcfg, "Proxy port, 1080 is the standard SOCKS port");
-    th_cfg_add_int(&tmpcfg, "port", &optProxyPort, optProxyPort);
-    th_cfg_add_section(&cfg, "proxy", tmpcfg);
-
-    tmpcfg = NULL;
-    th_cfg_add_comment(&tmpcfg, "Enable logging");
-    th_cfg_add_bool(&tmpcfg, "enable", &optLogEnable, optLogEnable);
-    th_cfg_add_comment(&tmpcfg, "Log filename format");
-    th_cfg_add_string(&tmpcfg, "filename", &optLogFilename, optLogFilename);
-    th_cfg_add_section(&cfg, "logging", tmpcfg);
-
-#ifdef __WIN32
-    {
-        char tmpPath[MAX_PATH];
-        if (SHGetFolderPath(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, tmpPath) == S_OK)
-            homeDir = th_strdup(tmpPath);
-
-        CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
-    }
-#else
-    homeDir = th_strdup(getenv("HOME"));
-#endif
-
-    if (homeDir != NULL)
-    {
-        FILE *cfgfile;
-        setConfigFile = th_strdup_printf("%s" SET_DIR_SEPARATOR "%s", homeDir, SET_CONFIG_FILE);
-
-        THMSG(0, "Reading configuration from '%s'.\n", setConfigFile);
-
-        if ((cfgfile = fopen(setConfigFile, "r")) != NULL)
-        {
-            th_cfg_read(cfgfile, setConfigFile, cfg);
-            fclose(cfgfile);
-        }
-    }
-
-    if (optNickSepStr)
-        optNickSep = optNickSepStr[0];
-    else
-        optNickSep = SET_NICK_SEPARATOR;
-
-
-    setBrowser = getenv("BROWSER");
-    if (setBrowser == NULL)
-        setBrowser = "firefox";
-
-    /* Parse command line arguments */
-    argsOK = th_args_process(argc, argv, optList, optListN,
-                             argHandleOpt, argHandleFile, FALSE);
-
-    if (optUserNameCmd != NULL)
-    {
-        optUserName = optUserNameCmd;
-        optPassword = optPasswordCmd;
-    }
-
-    if (!argsOK)
-        return -2;
-
-    /* Allocate userhash */
-    if ((nnUsers = nn_userhash_new()) == NULL)
-    {
-        THERR("Could not allocate userhash. Fatal error.\n");
-        return -105;
-    }
-
-    /* If no idle messages are set, add default */
-    if (setIdleMessages == NULL)
-    {
-        th_llist_append(&setIdleMessages, th_strdup("."));
-    }
-
-    /* Open logfile */
-    logFileOpen();
-
-    /* Initialize network */
-    if (!nn_network_init())
-    {
-        THERR("Could not initialize network subsystem.\n");
-        goto err_exit;
-    }
-    else
-        networkInit = TRUE;
-
-    /* Initialize NCurses */
-    if (!optDaemon)
-    {
-        if (LINES < 0 || LINES > 1000) LINES = 24;
-        if (COLS < 0 || COLS > 1000) COLS = 80;
-        initscr();
-        raw();
-        keypad(stdscr, TRUE);
-        noecho();
-        meta(stdscr, TRUE);
-        timeout(SET_DELAY);
-        curVis = curs_set(0);
-
-        if (has_colors())
-        {
-            start_color();
-
-            init_pair( 1, COLOR_RED,     COLOR_BLACK);
-            init_pair( 2, COLOR_GREEN,   COLOR_BLACK);
-            init_pair( 3, COLOR_YELLOW,  COLOR_BLACK);
-            init_pair( 4, COLOR_BLUE,    COLOR_BLACK);
-            init_pair( 5, COLOR_MAGENTA, COLOR_BLACK);
-            init_pair( 6, COLOR_CYAN,    COLOR_BLACK);
-            init_pair( 7, COLOR_WHITE,   COLOR_BLACK);
-            init_pair( 8, COLOR_BLACK,   COLOR_BLACK);
-
-            init_pair(10, COLOR_BLACK,   COLOR_RED);
-            init_pair(11, COLOR_WHITE,   COLOR_RED);
-            init_pair(12, COLOR_GREEN,   COLOR_RED);
-            init_pair(13, COLOR_YELLOW,  COLOR_RED);
-            init_pair(14, COLOR_BLUE,    COLOR_RED);
-            init_pair(15, COLOR_MAGENTA, COLOR_RED);
-            init_pair(16, COLOR_CYAN,    COLOR_RED);
-        }
-
-        cursesInit = TRUE;
-
-        if (!initializeWindows())
-            goto err_exit;
-
-#ifdef PDCURSES
-        PDC_set_title("NNChat v" NN_VERSION);
-#endif
-
-        memset(chatWindows, 0, sizeof(chatWindows));
-        chatWindows[0] = nn_window_new(NULL);
-        currWin = chatWindows[0];
-        updateStatus();
-    }
-
-    /* Check if we have username and password */
-    if (cursesInit && (optUserName == NULL || optPassword == NULL))
-    {
-        printWin(editWin, "You can avoid this prompt by issuing '/save' after logging in.\n");
-        optUserName = promptRequester(editWin, "NN username: ", FALSE);
-        optPassword = promptRequester(editWin, "NN password: ", TRUE);
-    }
-
-    if (optUserName == NULL || optPassword == NULL)
-    {
-        errorMsg("Username and/or password not specified.\n");
-        goto err_exit;
-    }
-
-    /* Create a connection */
-    conn = nn_conn_new(errorFunc, messageFunc);
-    if (conn == NULL)
-    {
-        errorMsg("Could not create connection structure.\n");
-        goto err_exit;
-    }
-
-    /* Are we using a proxy? */
-    if (optProxyType != NN_PROXY_NONE && optProxyServer != NULL)
-    {
-        if (nn_conn_set_proxy(conn, optProxyType, optProxyPort, optProxyServer) != 0)
-        {
-            errorMsg("Error setting proxy information.\n");
-            goto err_exit;
-        }
-    }
-
-    /* Okay ... */
-    printMsg(currWin, "Trying to resolve host '%s' ...\n", optServer);
-    conn->host = th_strdup(optServer);
-    conn->hst = nn_resolve_host(conn, optServer);
-    if (conn->hst == NULL)
-    {
-        errorMsg("Could not resolve hostname: %s.\n", strerror(h_errno));
-        goto err_exit;
-    }
-
-#ifdef FINAL_BUILD
-    /* To emulate the official client, we first make a request for
-     * policy file, even though we don't use it for anything...
-     */
-    if (nn_conn_open(conn, 843, NULL) != 0)
-    {
-        errorMsg("Policy file request connection setup failed!\n");
-        goto err_exit;
-    }
-
-    tmpStr = "<policy-file-request/>";
-    if (nn_conn_send_buf(conn, tmpStr, strlen(tmpStr) + 1) == FALSE)
-    {
-        errorMsg("Failed to send policy file request.\n");
-        goto err_exit;
-    }
-    else
-    {
-        int cres = nn_conn_pull(conn);
-        if (cres == 0)
-        {
-            printMsg(currWin, "Probe got: %s\n", conn->buf);
-        }
-        else
-        {
-            printMsg(currWin, "Could not get policy probe.\n");
-        }
-    }
-    nn_conn_close(conn);
-#endif
-
-    /* Okay, now do the proper connection ... */
-    if (nn_conn_open(conn, optPort, NULL) != 0)
-    {
-        errorMsg("Main connection setup failed!\n");
-        goto err_exit;
-    }
-
-    /* Send login command */
-    optUserNameEnc = nn_dblencode_str(optUserName);
-    tmpStr = nn_dblencode_str(optSite);
-    nn_conn_send_msg(conn, optUserNameEnc, "%%2Flogin%%20%%2Dsite%%20%s%%20%%2Dpassword%%20%s", tmpStr, optPassword);
-    th_free(tmpStr);
-
-    /* Initialize random numbers */
-    prevTime = time(NULL);
-    srandom((int) prevTime);
-
-    if (cursesInit)
-    {
-        /* Initialize rest of interactive UI code */
-        nn_editbuf_clear(editBuf);
-
-        /* First update of screen */
-        printEditBuf(editBuf);
-        updateStatus();
-
-        printMsg(NULL, "%s v%s - %s\n", th_prog_name, th_prog_version, th_prog_fullname);
-        printMsg(NULL, "%s\n", th_prog_author);
-        printMsg(NULL, "%s\n", th_prog_license);
-    }
-
-    /* Enter mainloop */
-    nn_conn_reset(conn);
-    while (!isError && !exitProg)
-    {
-        nn_conn_reset(conn);
-        do {
-            int cres = nn_conn_pull(conn);
-            if (cres == 0 && *(conn->in_ptr - 1) == 0)
-            {
-                int result = nn_parse_protocol(conn);
-                if (result > 0)
-                {
-                    /* Couldn't handle the message for some reason */
-                    printMsg(currWin, "Could not handle: %s\n", conn->ptr);
-                }
-                else if (result < 0)
-                {
-                    /* Fatal error, quit */
-                    errorMsg("Fatal error with message: %s\n", conn->ptr);
-                    isError = TRUE;
-                }
-            }
-            else if (cres < 0)
-                isError = TRUE;
-            else
-                break;
-        }
-        while (conn->total_bytes > 0 && !isError);
-
-        if (!nn_conn_check(conn))
-            isError = TRUE;
-
-        /* Handle user input */
-        if (cursesInit)
-        {
-            int c, cnt = 0;
-            BOOL update = FALSE, updateMain = FALSE;
-
-            /* Handle several buffered keypresses at once */
-            do
-            {
-                c = wgetch(stdscr);
-                
-                /* Handle various problematic cases where terminal 
-                 * keycodes do not get properly translated by curses
-                 */
-                if (c == 0x1b)
-                {
-                    /* ^[O */
-                    c = wgetch(stdscr);
-                    if (c == 'O')
-                    {
-                        c = wgetch(stdscr);
-                        switch (c)
-                        {
-                        case 'd':
-                            c = 0x204;
-                            break;
-                        case 'c':
-                            c = 0x206;
-                            break;
-                        default:
-                            if (optDebug)
-                                printMsg(currWin, "Unhandled ESC-O key sequence 0x%02x\n", c);
-                            break;
-                        }
-                    }
-                    /* ^[[ */
-                    else if (c == '[')
-                    {
-                        c = wgetch(stdscr);
-                        switch (c)
-                        {
-                        case 0x31:
-                            c = wgetch(stdscr);
-                            if (c >= 0x31 && c <= 0x39)
-                                c = KEY_F(c - 0x30);
-                            else
-                                c = ERR;
-                            break;
-
-                        case 0x32:
-                            c = KEY_IC;
-                            break;
-                        case 0x33:
-                            c = KEY_DC;
-                            break;
-
-                        case 0x35:
-                            c = KEY_PPAGE;
-                            break;
-                        case 0x36:
-                            c = KEY_NPAGE;
-                            break;
-
-                        case 0x37:
-                            c = KEY_HOME;
-                            break;
-                        case 0x38:
-                            c = KEY_END;
-                            break;
-
-                        default:
-                            if (optDebug)
-                                printMsg(currWin, "Unhandled ESC-[*~ key sequence 0x%02x\n", c);
-                            c = ERR;
-                            break;
-                        }
-                        /* Get the trailing ~ */
-                        if (c != ERR)
-                            wgetch(stdscr);
-                    }
-                    if (c >= 0x31 && c <= 0x39)
-                    {
-                        /* Chat window switching via Meta/Esc-[1..9] */
-                        int win = c - 0x31;
-                        if (win < SET_MAX_WINDOWS && chatWindows[win] != NULL)
-                        {
-                            currWin = chatWindows[win];
-                            update = updateMain = TRUE;
-                        }
-                        c = ERR;
-                    }
-                    else
-                    {
-                        if (optDebug)
-                            printMsg(currWin, "Unhandled ESC key sequence 0x%02x\n", c);
-                    }
-                }
-#if defined(__WIN32) && defined(PDCURSES)
-                else if (c >= 0x198 && c <= 0x1a0)
-                {
-                    /* Chat window switching via Meta/Esc-[1..9] */
-                    int win = c - 0x198;
-                    if (win < SET_MAX_WINDOWS && chatWindows[win] != NULL)
-                    {
-                        currWin = chatWindows[win];
-                        update = updateMain = TRUE;
-                    }
-                    c = ERR;
-                }
-#endif
-
-                switch (c)
-                {
-#ifdef KEY_RESIZE
-                case KEY_RESIZE:
-                    resize_term(0, 0);
-                    erase();
-                    timeout(SET_DELAY);
-
-                    if (!initializeWindows())
-                    {
-                        errorMsg("Error resizing curses chatWindows\n");
-                        isError = TRUE;
-                    }
-                    update = updateMain = TRUE;
-                    break;
-#endif
-
-                case KEY_ENTER:
-                case '\n':
-                case '\r':
-                    /* Call the user input handler */
-                    if (editBuf->len > 0)
-                    {
-                        int result;
-
-                        if (histMax > 0)
-                        {
-                            nn_editbuf_free(histBuf[SET_MAX_HISTORY+1]);
-                            histBuf[SET_MAX_HISTORY+1] = NULL;
-                            memmove(&histBuf[2], &histBuf[1], histMax * sizeof(histBuf[0]));
-                        }
-
-                        histPos = 0;
-                        histBuf[1] = nn_editbuf_copy(editBuf);
-                        if (histMax < SET_MAX_HISTORY) histMax++;
-
-                        nn_editbuf_insert(editBuf, editBuf->len, 0);
-                        result = nn_handle_input(conn, editBuf->data, editBuf->len);
-
-                        nn_editbuf_clear(editBuf);
-
-                        if (result < 0)
-                        {
-                            errorMsg("Fatal error handling user input: %s\n", editBuf->data);
-                            isError = TRUE;
-                        }
-                        else
-                        {
-                            /* Update time value of last sent message for unidle timeouts */
-                            prevTime = time(NULL);
-                        }
-
-                        updateMain = update = TRUE;
-                    }
-                    break;
-
-                case KEY_UP: /* Backwards in input history */
-                    if (histPos == 0)
-                    {
-                        nn_editbuf_free(histBuf[0]);
-                        histBuf[0] = nn_editbuf_copy(editBuf);
-                    }
-                    if (histPos < histMax)
-                    {
-                        histPos++;
-                        nn_editbuf_free(editBuf);
-                        editBuf = nn_editbuf_copy(histBuf[histPos]);
-                        update = TRUE;
-                    }
-                    break;
-
-                case KEY_DOWN: /* Forwards in input history */
-                    if (histPos > 0)
-                    {
-                        histPos--;
-                        nn_editbuf_free(editBuf);
-                        editBuf = nn_editbuf_copy(histBuf[histPos]);
-                        update = TRUE;
-                    }
-                    break;
-
-                case 0x204: /* ctrl+left arrow = Skip words left */
-                case 0x20b:
-                    while (editBuf->pos > 0 && isspace((int) editBuf->data[editBuf->pos - 1]))
-                        editBuf->pos--;
-                    while (editBuf->pos > 0 && !isspace((int) editBuf->data[editBuf->pos - 1]))
-                        editBuf->pos--;
-                    update = TRUE;
-                    break;
-
-                case 0x206: /* ctrl+right arrow = Skip words right */
-                case 0x210:
-                    while (editBuf->pos < editBuf->len && isspace((int) editBuf->data[editBuf->pos]))
-                        editBuf->pos++;
-                    while (editBuf->pos < editBuf->len && !isspace((int) editBuf->data[editBuf->pos]))
-                        editBuf->pos++;
-                    update = TRUE;
-                    break;
-
-                case KEY_HOME:
-                    nn_editbuf_setpos(editBuf, 0);
-                    update = TRUE;
-                    break;
-                case KEY_END:
-                    nn_editbuf_setpos(editBuf, editBuf->len);
-                    update = TRUE;
-                    break;
-                case KEY_LEFT:
-                    nn_editbuf_setpos(editBuf, editBuf->pos - 1);
-                    update = TRUE;
-                    break;
-                case KEY_RIGHT:
-                    nn_editbuf_setpos(editBuf, editBuf->pos + 1);
-                    update = TRUE;
-                    break;
-
-                case KEY_BACKSPACE:
-                case 0x08:
-                case 0x7f:
-                    nn_editbuf_delete(editBuf, editBuf->pos - 1);
-                    nn_editbuf_setpos(editBuf, editBuf->pos - 1);
-                    update = TRUE;
-                    break;
-
-                case KEY_DC: /* Delete character */
-                    nn_editbuf_delete(editBuf, editBuf->pos);
-                    update = TRUE;
-                    break;
-
-
-                case KEY_IC: /* Ins = Toggle insert / overwrite mode */
-                    insertMode = !insertMode;
-                    update = TRUE;
-                    break;
-
-                case KEY_F(1): /* F1 = Print help */
-                    printHelp();
-                    updateMain = TRUE;
-                    break;
-
-                case KEY_F(2): /* F2 = Clear editbuffer */
-                    nn_editbuf_clear(editBuf);
-                    update = TRUE;
-                    break;
-
-                case KEY_F(5): /* F5 = Ignore mode */
-                    setIgnoreMode = !setIgnoreMode;
-                    printMsgQ(currWin, "Ignore mode = %s\n", setIgnoreMode ? "ON" : "OFF");
-                    break;
-
-#if 0
-                case KEY_F(8): /* F8 = Debug */
-                    optDebug = !optDebug;
-                    update = TRUE;
-                    break;
-#endif
-
-                case 0x03: /* ^C = quit */
-                case KEY_F(9): /* F9 = Quit */
-                    printMsg(currWin, "Quitting per user request (%d/0x%x).\n", c, c);
-                    exitProg = TRUE;
-                    break;
-
-                case 0x09: /* Tab = complete username */
-                    performTabCompletion(editBuf);
-                    update = TRUE;
-                    break;
-
-                case 0x0c: /* Ctrl + L */
-                    updateWindows();
-                    update = updateMain = TRUE;
-                    break;
-
-                case KEY_NPAGE:
-                case KEY_PPAGE:
-                    /* Page Up / Page Down */
-                    if (currWin != NULL)
-                    {
-                        int oldPos = currWin->pos;
-
-                        currWin->pos += (c == KEY_NPAGE) ? -10 : +10;
-
-                        if (currWin->pos < 0)
-                            currWin->pos = 0;
-                        else if (currWin->pos >= currWin->data->n - 10)
-                            currWin->pos = currWin->data->n - 10;
-
-                        if (oldPos != currWin->pos)
-                            updateMain = TRUE;
-                    }
-                    break;
-
-                case ERR:
-                    /* Ignore */
-                    break;
-
-                default:
-                    if (isprint(c) || c == 0xe4 || c == 0xf6 || c == 0xc4 || c == 0xd6)
-                    {
-                        if (insertMode)
-                            nn_editbuf_insert(editBuf, editBuf->pos, c);
-                        else
-                            nn_editbuf_write(editBuf, editBuf->pos, c);
-                        nn_editbuf_setpos(editBuf, editBuf->pos + 1);
-                        update = TRUE;
-                    }
-                    else
-                    {
-                        if (optDebug)
-                            printMsg(currWin, "Unhandled key: 0x%02x\n", c);
-                    }
-                    break;
-                }
-            }
-            while (c != ERR && !exitProg && ++cnt < 10);
-
-            updateMainWin(updateMain);
-
-            if (update || firstUpdate)
-            {
-                /* Update edit line */
-                updateStatus();
-                printEditBuf(editBuf);
-                firstUpdate = FALSE; /* a nasty hack ... */
-            }
-
-        } /* cursesInit */
-
-        if (++updateCount > 10)
-        {
-            time_t tmpTime = time(NULL);
-            if (tmpTime - prevTime > SET_KEEPALIVE)
-            {
-                int n = random() % th_llist_length(setIdleMessages);
-                qlist_t *node = th_llist_get_nth(setIdleMessages, n);
-                nn_conn_send_msg(conn, optUserNameEnc, node->data);
-                prevTime = tmpTime;
-            }
-
-            if (!colorSet)
-            {
-                colorSet = TRUE;
-                nn_conn_send_msg(conn, optUserNameEnc, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor);
-            }
-
-            updateStatus();
-            printEditBuf(editBuf);
-            updateCount = 0;
-        }
-
-    }
-
-    /* Shutdown */
-err_exit:
-    th_cfg_free(cfg);
-    th_free(homeDir);
-    th_llist_free_func(setIdleMessages, th_free);
-    nn_userhash_free(nnUsers);
-    nn_editbuf_free(editBuf);
-
-    {
-        int i;
-        for (i = 0; i <= SET_MAX_HISTORY; i++)
-            nn_editbuf_free(histBuf[i]);
-
-        for (i = 0; i < SET_MAX_WINDOWS; i++)
-            nn_window_free(chatWindows[i]);
-    }
-
-#ifdef __WIN32
-    if (errorMessages)
-    {
-        char *tmp;
-        wclear(editWin);
-        tmp = promptRequester(editWin, "Press enter to quit.\n", FALSE);
-        th_free(tmp);
-    }
-#endif
-
-    if (cursesInit)
-    {
-        if (curVis != ERR)
-            curs_set(curVis);
-        closeWindows();
-        endwin();
-        THMSG(1, "NCurses deinitialized.\n");
-    }
-
-#ifndef __WIN32
-    if (errorMessages)
-        THERR("%s", errorMessages);
-#endif
-
-    th_free(optUserNameEnc);
-
-    nn_conn_close(conn);
-
-    if (networkInit)
-        nn_network_close();
-
-    THMSG(1, "Connection terminated.\n");
-
-    logFileClose();
-
-    return 0;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util.c	Thu May 24 06:41:07 2012 +0300
@@ -0,0 +1,739 @@
+/*
+ * NNChat - Custom chat client for NewbieNudes.com chatrooms
+ * Written by Matti 'ccr' Hämäläinen
+ * (C) Copyright 2008-2012 Tecnic Software productions (TNSP)
+ */
+#include "util.h"
+
+
+#define PUSHCHAR(x) th_vputch(&result, &resSize, &resPos, x)
+#define PUSHSTR(x) th_vputs(&result, &resSize, &resPos, x)
+
+char *nn_encode_str1(const char *str)
+{
+    const char *s = str;
+    char *result;
+    size_t resSize, resPos = 0;
+
+    if (str == NULL) return NULL;
+
+    resSize = strlen(str) + NN_ALLOC_SIZE;
+    if ((result = th_malloc(resSize)) == NULL)
+        return NULL;
+
+    while (*s)
+    {
+        switch (*s)
+        {
+        case 32:
+            PUSHCHAR('+');
+            break;
+
+        default:
+            if (th_isalnum(*s))
+                PUSHCHAR(*s);
+            else
+            {
+                char tmpStr[4];
+                snprintf(tmpStr, sizeof(tmpStr), "%2X", (unsigned char) *s);
+                PUSHCHAR('%');
+                PUSHSTR(tmpStr);
+            }
+            break;
+        }
+        s++;
+    }
+    PUSHCHAR(0);
+
+    return result;
+}
+
+
+static int nn_get_hexdigit(const int c, const int shift)
+{
+    int i;
+
+    if (c >= 'A' && c <= 'F')
+        i = c - 'A' + 10;
+    else if (c >= 'a' && c <= 'f')
+        i = c - 'a' + 10;
+    else if (c >= '0' && c <= '9')
+        i = c - '0';
+    else
+        return -1;
+
+    return i << shift;
+}
+
+
+char *nn_decode_str1(const char *str)
+{
+    const char *s = str;
+    char *result;
+    size_t resSize, resPos = 0;
+    int c;
+
+    if (str == NULL) return NULL;
+
+    resSize = strlen(str) + NN_ALLOC_SIZE;
+    if ((result = th_malloc(resSize)) == NULL)
+        return NULL;
+
+    while (*s)
+    {
+        switch (*s)
+        {
+        case '+':
+            PUSHCHAR(' ');
+            s++;
+            break;
+
+        case '½':
+            /* Escape these .. */
+            PUSHCHAR('½');
+            PUSHCHAR('½');
+            s++;
+            break;
+
+        case '\r':
+            PUSHCHAR(' ');
+            s++;
+            break;
+
+        case '%':
+            s++;
+            if (*s == '%')
+                PUSHCHAR('%');
+            else if ((c = nn_get_hexdigit(*s, 4)) >= 0)
+            {
+                int i = nn_get_hexdigit(*(++s), 0);
+                if (i >= 0)
+                {
+                    PUSHCHAR(c | i);
+                }
+                else
+                {
+                    PUSHCHAR('§');
+                    PUSHCHAR(*s);
+                }
+            }
+            else
+            {
+                PUSHCHAR('§');
+                PUSHCHAR(*s);
+            }
+            s++;
+            break;
+
+        default:
+            PUSHCHAR(*s);
+            s++;
+        }
+    }
+    PUSHCHAR(0);
+
+    return result;
+}
+
+
+char *nn_strip_tags(const char *str)
+{
+    const char *s = str;
+    char *result;
+    size_t resSize, resPos = 0;
+
+    if (str == NULL) return NULL;
+
+    resSize = strlen(str) + NN_ALLOC_SIZE;
+    if ((result = th_malloc(resSize)) == NULL)
+        return NULL;
+
+    while (*s)
+    {
+        if (*s == '<')
+        {
+            while (*s && *s != '>') s++;
+            if (*s == '>') s++;
+        }
+        else
+            PUSHCHAR(*s++);
+    }
+    PUSHCHAR(0);
+
+    return result;
+}
+
+
+typedef struct
+{
+    char c;
+    char *ent;
+} html_entity_t;
+
+
+static const html_entity_t HTMLEntities[] =
+{
+    { '<', "&lt;" },
+    { '>', "&gt;" },
+};
+
+static const int numHTMLEntities = sizeof(HTMLEntities) / sizeof(HTMLEntities[0]);
+
+
+char *nn_encode_str2(const char *str)
+{
+    const char *s = str;
+    char *result;
+    size_t resSize, resPos = 0;
+
+    if (str == NULL) return NULL;
+
+    resSize = strlen(str) + NN_ALLOC_SIZE;
+    if ((result = th_malloc(resSize)) == NULL)
+        return NULL;
+
+    while (*s)
+    {
+        int i;
+        BOOL found = FALSE;
+        for (i = 0; i < numHTMLEntities; i++)
+            if (HTMLEntities[i].c == *s)
+            {
+                PUSHSTR(HTMLEntities[i].ent);
+                found = TRUE;
+                break;
+            }
+        if (!found) PUSHCHAR(*s);
+
+        s++;
+    }
+    PUSHCHAR(0);
+
+    return result;
+}
+
+
+char *nn_decode_str2(const char *str)
+{
+    const char *s = str;
+    char *result;
+    size_t resSize, resPos = 0;
+
+    if (str == NULL) return NULL;
+
+    resSize = strlen(str);
+    if ((result = th_malloc(resSize)) == NULL)
+        return NULL;
+
+    while (*s)
+    {
+        if (*s == '&')
+        {
+            int i;
+            BOOL found = FALSE;
+            for (i = 0; i < numHTMLEntities; i++)
+            {
+                const html_entity_t *ent = &HTMLEntities[i];
+                int len = strlen(ent->ent);
+                if (!strncmp(s, ent->ent, len))
+                {
+                    PUSHCHAR(ent->c);
+                    s += len;
+                    found = TRUE;
+                    break;
+                }
+            }
+            if (!found) PUSHCHAR(*s++);
+        }
+        else
+            PUSHCHAR(*s++);
+    }
+    PUSHCHAR(0);
+
+    return result;
+}
+
+
+char *nn_dbldecode_str(const char *str)
+{
+    char *res, *tmp;
+
+    if ((tmp = nn_decode_str1(str)) == NULL)
+        return NULL;
+
+    res = nn_decode_str2(tmp);
+    th_free(tmp);
+
+    return res;
+}
+
+
+char *nn_dblencode_str(const char *str)
+{
+    char *res, *tmp;
+
+    if ((tmp = nn_encode_str2(str)) == NULL)
+        return NULL;
+
+    res = nn_encode_str1(tmp);
+    th_free(tmp);
+
+    return res;
+}
+
+
+int nn_editbuf_write(nn_editbuf_t *buf, ssize_t pos, int ch)
+{
+    if (buf->len+1 >= buf->size) return -3;
+
+    if (pos < 0)
+        return -1;
+    else if (pos >= buf->len)
+        buf->data[buf->len++] = ch;
+    else
+        buf->data[pos] = ch;
+
+    return 0;
+}
+
+
+int nn_editbuf_insert(nn_editbuf_t *buf, ssize_t pos, int ch)
+{
+    if (buf->len+1 >= buf->size) return -3;
+
+    if (pos < 0)
+        return -1;
+    else if (pos >= buf->len)
+    {
+        buf->data[buf->len] = ch;
+    }
+    else
+    {
+        memmove(&(buf->data[pos+1]), &(buf->data[pos]), buf->len - pos + 1);
+        buf->data[pos] = ch;
+    }
+    buf->len++;
+    return 0;
+}
+
+
+int nn_editbuf_delete(nn_editbuf_t *buf, ssize_t pos)
+{
+    if (pos < 0)
+        return -1;
+    else if (pos < buf->len)
+    {
+        memmove(&(buf->data[pos]), &(buf->data[pos+1]), buf->len - pos);
+        buf->len--;
+        return 0;
+    }
+    else
+        return -2;
+}
+
+
+void nn_editbuf_clear(nn_editbuf_t *buf)
+{
+    buf->len = 0;
+    buf->pos = 0;
+}
+
+
+nn_editbuf_t * nn_editbuf_new(ssize_t n)
+{
+    nn_editbuf_t *res = th_calloc(1, sizeof(nn_editbuf_t));
+
+    res->data = (char *) th_malloc(n);
+    res->size = n;
+
+    return res;
+}
+
+
+void nn_editbuf_free(nn_editbuf_t *buf)
+{
+    if (buf != NULL)
+    {
+        th_free(buf->data);
+        th_free(buf);
+    }
+}
+
+
+nn_editbuf_t * nn_editbuf_copy(nn_editbuf_t *src)
+{
+    nn_editbuf_t *res;
+
+    assert(src != NULL);
+
+    if (src == NULL) return NULL;
+
+    if ((res = nn_editbuf_new(src->size)) == NULL)
+        return NULL;
+
+    memcpy(res->data, src->data, src->size);
+    res->pos = res->len = src->len;
+
+    return res;
+}
+
+
+char * nn_editbuf_get_string(nn_editbuf_t *buf, ssize_t start, ssize_t end)
+{
+    char *str;
+    ssize_t siz;
+
+    if (buf == NULL)
+        return NULL;
+
+    if (start < 0 || end > buf->len || start >= buf->len)
+        return NULL;
+
+    if (end < 0)
+    {
+        siz = buf->len - start + 1;
+    }
+    else if (start <= end)
+    {
+        siz = end - start + 1;
+    }
+    else
+        return NULL;
+
+    if ((str = th_malloc(siz + 1)) == NULL)
+        return NULL;
+
+    memcpy(str, buf->data + start, siz);
+    str[siz] = 0;
+
+    return str;
+}
+
+
+void nn_editbuf_setpos(nn_editbuf_t *buf, ssize_t pos)
+{
+    assert(buf != NULL);
+
+    if (pos < 0)
+        buf->pos = 0;
+    else if (pos >= buf->len)
+        buf->pos = buf->len;
+    else
+        buf->pos = pos;
+}
+
+
+static uint8_t nn_hash_user(const char *name)
+{
+/*
+    int n = 0;
+    const uint8_t *c = (uint8_t *)name;
+    uint8_t hash = 0xff;
+    
+    while (*c && n < 4)
+    {
+        hash = (hash << 1) ^ tolower(*c);
+        c++; n++;
+    }
+    
+    return (hash & 0xff);
+*/
+    return tolower(name[0]);
+}
+
+
+static void nn_user_insert(nn_user_t **list, nn_user_t *node)
+{
+    node->next = *list;
+    *list = node;
+}
+
+
+nn_user_t *nn_userhash_foreach(const nn_userhash_t *list, int (*func)(const nn_user_t *))
+{
+    int i;
+
+    if (list == NULL) return NULL;
+
+    for (i = 0; i < NN_NUM_BUCKETS; i++)
+        if (list->buckets[i] != NULL)
+        {
+            nn_user_t *curr = list->buckets[i];
+            while (curr != NULL)
+            {
+                if (func(curr) != 0)
+                    return curr;
+                curr = curr->next;
+            }
+        }
+
+    return NULL;
+}
+
+
+nn_user_t *nn_user_find(const nn_userhash_t *list, const char *name)
+{
+    uint8_t hash;
+
+    if (list == NULL) return NULL;
+
+    hash = nn_hash_user(name);
+    if (list->buckets[hash] != NULL)
+    {
+        nn_user_t *curr = list->buckets[hash];
+        while (curr != NULL)
+        {
+            if (th_strcasecmp(curr->name, name) == 0)
+                return curr;
+            curr = curr->next;
+        }
+    }
+
+    return NULL;
+}
+
+
+static nn_user_t *nn_user_match_do(nn_user_t *list, const char *pattern, size_t len)
+{
+    nn_user_t *curr = list;
+
+    while (curr != NULL)
+    {
+        if (len <= strlen(curr->name) && th_strncasecmp(curr->name, pattern, len) == 0)
+            return curr;
+        curr = curr->next;
+    }
+    return NULL;
+}
+
+
+nn_user_t *nn_user_match(const nn_userhash_t *list, const char *pattern, const char *current, BOOL again)
+{
+    uint8_t hash;
+
+    if (list == NULL || pattern == NULL) return NULL;
+
+    hash = nn_hash_user(pattern);
+    if (list->buckets[hash] != NULL)
+    {
+        nn_user_t *curr = list->buckets[hash];
+        size_t len = strlen(pattern);
+
+        if (current != NULL)
+        {
+            nn_user_t *found = NULL;
+            while (curr != NULL)
+            {
+                if (th_strcasecmp(curr->name, current) == 0)
+                {
+                    if (again)
+                        return curr;
+                    found = curr->next;
+                    break;
+                }
+                curr = curr->next;
+            }
+
+            if (found != NULL && (found = nn_user_match_do(found, pattern, len)) != NULL)
+                return found;
+        }
+
+        if ((curr = nn_user_match_do(list->buckets[hash], pattern, len)) != NULL)
+            return curr;
+    }
+
+    return NULL;
+}
+
+
+nn_userhash_t *nn_userhash_new(void)
+{
+    return th_calloc(1, sizeof(nn_userhash_t));
+}
+
+
+int nn_userhash_insert(nn_userhash_t *list, const char *name)
+{
+    uint8_t hash;
+    nn_user_t *user;
+
+    /* Check arguments */
+    if (list == NULL || name == NULL)
+        return -1;
+
+    /* Check if username is already there */
+    if (nn_user_find(list, name) != NULL)
+        return 1;
+
+    /* No, we'll add it */
+    if ((user = th_calloc(1, sizeof(nn_user_t))) == NULL)
+        return -3;
+
+    user->name = th_strdup(name);
+    if (user->name == NULL)
+        return -4;
+
+    hash = nn_hash_user(name);
+    nn_user_insert(&(list->buckets[hash]), user);
+
+    return 0;
+}
+
+
+int nn_userhash_delete(nn_userhash_t *list, const char *name)
+{
+    uint8_t hash;
+
+    /* Check arguments */
+    if (list == NULL || name == NULL)
+        return -1;
+
+    /* Check if username is already there */
+    hash = nn_hash_user(name);
+    if (list->buckets[hash] != NULL)
+    {
+        nn_user_t *curr, *prev;
+        curr = list->buckets[hash];
+        prev = NULL;
+        while (curr != NULL)
+        {
+            if (th_strcasecmp(curr->name, name) == 0)
+            {
+                if (prev)
+                    prev->next = curr->next;
+                else
+                    list->buckets[hash] = curr->next;
+
+                nn_user_free(curr);
+
+                return 0;
+            }
+            else
+            {
+                prev = curr;
+                curr = curr->next;
+            }
+        }
+    }
+
+    return 1;
+}
+
+
+nn_user_t *nn_user_copy(const nn_user_t *src)
+{
+    nn_user_t *user;
+
+    if (src == NULL) return NULL;
+
+    if ((user = th_calloc(1, sizeof(nn_user_t))) == NULL)
+        return NULL;
+
+    /* Copy relevant data */
+    user->name = th_strdup(src->name);
+    user->lastspoke = src->lastspoke;
+    user->joined = src->joined;
+
+    return user;
+}
+
+
+void nn_user_free(nn_user_t *user)
+{
+    th_free(user->name);
+    th_free(user);
+}
+
+
+void nn_user_free_list(nn_user_t *list)
+{
+    nn_user_t *next, *curr = list;
+
+    while (curr != NULL)
+    {
+        next = curr->next;
+        nn_user_free(curr);
+        curr = next;
+    }
+}
+
+void nn_userhash_free(nn_userhash_t *hash)
+{
+    int i;
+    if (hash == NULL)
+        return;
+
+    for (i = 0; i < NN_NUM_BUCKETS; i++)
+    {
+        nn_user_free_list(hash->buckets[i]);
+        hash->buckets[i] = NULL;
+    }
+
+    th_free(hash);
+}
+
+
+char *nn_username_encode(char *str)
+{
+    unsigned char *c = (unsigned char *) str;
+    if (str == NULL) return NULL;
+    for (; *c ; c++)
+        if (*c == ' ') *c = 255;
+    return str;
+}
+
+
+char *nn_username_decode(char *str)
+{
+    unsigned char *c = (unsigned char *) str;
+    if (str == NULL) return NULL;
+    for (; *c ; c++)
+        if (*c == 255) *c = ' ';
+    return str;
+}
+
+
+nn_window_t *nn_window_new(const char *id)
+{
+    nn_window_t *res = th_calloc(1, sizeof(nn_window_t));
+
+    if (res == NULL) return NULL;
+
+    res->data = th_ringbuf_new(NN_BACKBUF_LEN, th_free);
+    if (res->data == NULL)
+    {
+        th_free(res);
+        return NULL;
+    }
+
+    res->id = th_strdup(id);
+
+    return res;
+}
+
+
+void nn_window_free(nn_window_t *win)
+{
+    if (win != NULL)
+    {
+        th_ringbuf_free(win->data);
+        th_free(win->id);
+        th_free(win);
+    }
+}
+
+
+nn_strtuple_t *nn_strtuple_new(size_t len, char *str)
+{
+    nn_strtuple_t *tuple = th_calloc(1, sizeof(nn_strtuple_t));
+    tuple->len = len;
+    tuple->str = str;
+    return tuple;
+}
+
+
+void nn_strtuple_free(nn_strtuple_t *tuple)
+{
+    th_free(tuple->str);
+    th_free(tuple);
+}