changeset 412:3e64acb433e8

Split libnnchat into libnnet and libnnutil
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 24 May 2012 06:38:26 +0300
parents 664b94a58dbe
children 14b685cdbd2c
files Makefile Makefile.gen libnnchat.c libnnchat.h libnnnet.c libnnnet.h libnnutil.c libnnutil.h nnchat.c
diffstat 9 files changed, 1519 insertions(+), 1493 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Thu May 24 06:23:43 2012 +0300
+++ b/Makefile	Thu May 24 06:38:26 2012 +0300
@@ -5,7 +5,7 @@
 
 # C-compiler, flags and linker flags
 CC=gcc
-CFLAGS=-DHAVE_STRING_H -DHAVE_INT_TYPES
+CFLAGS=-DHAVE_STRING_H -DHAVE_STDINT_H
 LDFLAGS=-lncurses
 
 #CFLAGS += -DHAVE_STDINT_H
--- a/Makefile.gen	Thu May 24 06:23:43 2012 +0300
+++ b/Makefile.gen	Thu May 24 06:38:26 2012 +0300
@@ -36,7 +36,7 @@
 
 nnchat.c: VERSION
 
-$(NNCHAT_BIN): nnchat.c $(OBJPATH)libnnchat.o $(THLIBS_A) $(EXTRAOBJS)
+$(NNCHAT_BIN): nnchat.c $(OBJPATH)libnnutil.o $(OBJPATH)libnnnet.o $(THLIBS_A) $(EXTRAOBJS)
 	$(CC) $(CFLAGS) -o $@ $+ $(LDFLAGS) -DNN_VERSION=\"$(NN_VERSION)\" -I$(THLIBS)
 
 #
--- a/libnnchat.c	Thu May 24 06:23:43 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1272 +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 "libnnchat.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;
-}
-
-
-#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;
-}
-
-
-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;
-}
-
-
-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);
-}
--- a/libnnchat.h	Thu May 24 06:23:43 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,215 +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"
-#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>
-#include "th_string.h"
-
-#define NN_TMPBUF_SIZE    (4096)
-#define NN_ALLOC_SIZE     (128)
-#define NN_CONNBUF_SIZE   (64 * 1024)
-#define NN_NUM_BUCKETS    (256)
-#define NN_DELAY_USEC     (15 * 1000)
-#define NN_BACKBUF_LEN    (512)       /* Backbuffer size (in lines) */
-
-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);
-
-
-typedef struct _nn_user_t
-{
-    char *name;
-    time_t lastspoke, joined;
-    struct _nn_user_t *next;
-} nn_user_t;
-
-typedef struct
-{
-    nn_user_t *buckets[NN_NUM_BUCKETS];
-} nn_userhash_t;
-
-nn_userhash_t *nn_userhash_new(void);
-nn_user_t * nn_userhash_foreach(const nn_userhash_t *, int (*func)(const nn_user_t *));
-nn_user_t * nn_user_match(const nn_userhash_t *list, const char *str, const char *current, BOOL again);
-int         nn_userhash_insert(nn_userhash_t *, const char *name);
-int         nn_userhash_delete(nn_userhash_t *, const char *name);
-void        nn_userhash_free(nn_userhash_t *);
-void        nn_user_free(nn_user_t *);
-void        nn_user_free_list(nn_user_t *);
-nn_user_t * nn_user_copy(const nn_user_t *src);
-nn_user_t * nn_user_find(const nn_userhash_t *list, const char *name);
-
-
-char *      nn_encode_str1(const char *str);
-char *      nn_decode_str1(const char *str);
-char *      nn_encode_str2(const char *str);
-char *      nn_decode_str2(const char *str);
-char *      nn_strip_tags(const char *str);
-char *      nn_dbldecode_str(const char *str);
-char *      nn_dblencode_str(const char *str);
-
-char *      nn_username_encode(char *str);
-char *      nn_username_decode(char *str);
-
-
-typedef struct
-{
-    ssize_t pos, len, size;
-    char *data;
-} nn_editbuf_t;
-
-int         nn_editbuf_write(nn_editbuf_t *buf, ssize_t pos, int ch);
-int         nn_editbuf_insert(nn_editbuf_t *buf, ssize_t pos, int ch);
-int         nn_editbuf_delete(nn_editbuf_t *buf, ssize_t pos);
-void        nn_editbuf_clear(nn_editbuf_t *buf);
-nn_editbuf_t * nn_editbuf_new(ssize_t n);
-void        nn_editbuf_free(nn_editbuf_t *buf);
-nn_editbuf_t * nn_editbuf_copy(nn_editbuf_t *src);
-void        nn_editbuf_setpos(nn_editbuf_t *buf, ssize_t pos);
-char *      nn_editbuf_get_string(nn_editbuf_t *buf, ssize_t start, ssize_t end);
-
-
-typedef struct
-{
-    qringbuf_t *data;   /* "Backbuffer" data for this window */
-    int pos;            /* Current position in the window, 0 = real time */
-    BOOL dirty;
-
-    char *id;           /* Chatter ID, NULL = main window */
-    int num;		/* Window number */
-
-    char *buf;
-    size_t len, bufsize;
-    size_t chlen;
-} nn_window_t;
-
-nn_window_t *nn_window_new(const char *);
-void        nn_window_free(nn_window_t *);
-
-
-typedef struct
-{
-    size_t len;
-    char *str;
-} nn_strtuple_t;
-
-
-nn_strtuple_t *nn_strtuple_new(size_t, char *);
-void nn_strtuple_free(nn_strtuple_t *);
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libnnnet.c	Thu May 24 06:38:26 2012 +0300
@@ -0,0 +1,540 @@
+/*
+ * 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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libnnnet.h	Thu May 24 06:38:26 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libnnutil.c	Thu May 24 06:38:26 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 "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/libnnutil.h	Thu May 24 06:38:26 2012 +0300
@@ -0,0 +1,104 @@
+/*
+ * 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 LIBNNUTIL_H
+#define LIBNNUTIL_H
+
+#include <stdio.h>
+#include <unistd.h>
+#include "th_types.h"
+#include "th_string.h"
+
+
+#define NN_TMPBUF_SIZE    (4096)
+#define NN_ALLOC_SIZE     (128)
+#define NN_NUM_BUCKETS    (256)
+#define NN_BACKBUF_LEN    (512)       /* Backbuffer size (in lines) */
+
+
+typedef struct _nn_user_t
+{
+    char *name;
+    time_t lastspoke, joined;
+    struct _nn_user_t *next;
+} nn_user_t;
+
+
+typedef struct
+{
+    nn_user_t *buckets[NN_NUM_BUCKETS];
+} nn_userhash_t;
+
+
+nn_userhash_t *nn_userhash_new(void);
+nn_user_t * nn_userhash_foreach(const nn_userhash_t *, int (*func)(const nn_user_t *));
+nn_user_t * nn_user_match(const nn_userhash_t *list, const char *str, const char *current, BOOL again);
+int         nn_userhash_insert(nn_userhash_t *, const char *name);
+int         nn_userhash_delete(nn_userhash_t *, const char *name);
+void        nn_userhash_free(nn_userhash_t *);
+void        nn_user_free(nn_user_t *);
+void        nn_user_free_list(nn_user_t *);
+nn_user_t * nn_user_copy(const nn_user_t *src);
+nn_user_t * nn_user_find(const nn_userhash_t *list, const char *name);
+
+
+char *      nn_encode_str1(const char *str);
+char *      nn_decode_str1(const char *str);
+char *      nn_encode_str2(const char *str);
+char *      nn_decode_str2(const char *str);
+char *      nn_strip_tags(const char *str);
+char *      nn_dbldecode_str(const char *str);
+char *      nn_dblencode_str(const char *str);
+
+char *      nn_username_encode(char *str);
+char *      nn_username_decode(char *str);
+
+
+typedef struct
+{
+    ssize_t pos, len, size;
+    char *data;
+} nn_editbuf_t;
+
+int         nn_editbuf_write(nn_editbuf_t *buf, ssize_t pos, int ch);
+int         nn_editbuf_insert(nn_editbuf_t *buf, ssize_t pos, int ch);
+int         nn_editbuf_delete(nn_editbuf_t *buf, ssize_t pos);
+void        nn_editbuf_clear(nn_editbuf_t *buf);
+nn_editbuf_t * nn_editbuf_new(ssize_t n);
+void        nn_editbuf_free(nn_editbuf_t *buf);
+nn_editbuf_t * nn_editbuf_copy(nn_editbuf_t *src);
+void        nn_editbuf_setpos(nn_editbuf_t *buf, ssize_t pos);
+char *      nn_editbuf_get_string(nn_editbuf_t *buf, ssize_t start, ssize_t end);
+
+
+typedef struct
+{
+    qringbuf_t *data;   /* "Backbuffer" data for this window */
+    int pos;            /* Current position in the window, 0 = real time */
+    BOOL dirty;
+
+    char *id;           /* Chatter ID, NULL = main window */
+    int num;		/* Window number */
+
+    char *buf;
+    size_t len, bufsize;
+    size_t chlen;
+} nn_window_t;
+
+nn_window_t *nn_window_new(const char *);
+void        nn_window_free(nn_window_t *);
+
+
+typedef struct
+{
+    size_t len;
+    char *str;
+} nn_strtuple_t;
+
+
+nn_strtuple_t *nn_strtuple_new(size_t, char *);
+void nn_strtuple_free(nn_strtuple_t *);
+
+#endif
--- a/nnchat.c	Thu May 24 06:23:43 2012 +0300
+++ b/nnchat.c	Thu May 24 06:38:26 2012 +0300
@@ -3,12 +3,10 @@
  * Written by Matti 'ccr' Hämäläinen
  * (C) Copyright 2008-2011 Tecnic Software productions (TNSP)
  */
-#include "libnnchat.h"
-#include <stdlib.h>
+#include "libnnutil.h"
+#include "libnnnet.h"
 #include "th_args.h"
 #include "th_config.h"
-#include <string.h>
-#include <errno.h>
 #ifdef __WIN32
 /* Undefine because both windows.h and curses.h #define it */
 #undef MOUSE_MOVED