changeset 102:6ca407bfbeaf

New TCP network module, rather simple and only useful for client applications now.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 21 Jun 2014 02:41:29 +0300
parents a8f732601fdc
children f7bec3f7181d
files th_network.c th_network.h
diffstat 2 files changed, 1018 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/th_network.c	Sat Jun 21 02:41:29 2014 +0300
@@ -0,0 +1,863 @@
+/*
+ * Simple TCP network connection handling
+ * Programmed and designed by Matti 'ccr' Hamalainen
+ * (C) Copyright 2013-2014 Tecnic Software productions (TNSP)
+ *
+ * Please read file 'COPYING' for information on license and distribution.
+ */
+#include "th_network.h"
+#include "th_string.h"
+#include <errno.h>
+
+
+static BOOL th_network_inited = FALSE;
+static qlist_t *th_conn_list = NULL;
+
+
+enum
+{
+    TH_SOCKS5_AUTH_NONE = 0,
+    TH_SOCKS5_AUTH_USER = 2,
+};
+
+
+struct th_socks4_t
+{
+    uint8_t version;
+    uint8_t command;
+    in_port_t port;
+    in_addr_t addr;
+} __attribute__((__packed__));
+
+
+struct th_socks4_res_t
+{
+    uint8_t nb;
+    uint8_t result;
+    in_port_t port;
+    in_addr_t addr;
+} __attribute__((__packed__));
+
+
+static const char *th_proxy_types[] =
+{
+    "none",
+    "SOCKS 4",
+    "SOCKS 4a",
+    "SOCKS 5",
+    NULL
+};
+
+
+static int th_get_socket_errno(void)
+{
+    return errno;
+}
+
+
+void th_conn_err(th_conn_t *conn, int err, const char *fmt, ...)
+{
+    if (conn->errfunc != NULL)
+    {
+        char *msg;
+        va_list ap;
+        va_start(ap, fmt);
+        msg = th_strdup_vprintf(fmt, ap);
+        va_end(ap);
+
+        conn->errfunc(conn, err, msg);
+        th_free(msg);
+    }
+}
+
+
+void th_conn_msg(th_conn_t *conn, int loglevel, const char *fmt, ...)
+{
+    if (conn->msgfunc != NULL)
+    {
+        char *msg;
+        va_list ap;
+        va_start(ap, fmt);
+        msg = th_strdup_vprintf(fmt, ap);
+        va_end(ap);
+
+        conn->msgfunc(conn, loglevel, msg);
+        th_free(msg);
+    }
+}
+
+
+struct hostent *th_resolve_host(th_conn_t *conn, const char *name)
+{
+    struct hostent *res = gethostbyname(name);
+
+    if (res == NULL)
+    {
+        th_conn_err(conn, h_errno,
+            "Could not resolve hostname '%s': %s\n",
+            name, th_error_str(h_errno));
+    }
+    else
+    {
+        th_conn_msg(conn, THLOG_INFO, "True hostname for %s is %s\n",
+            name, res->h_name);
+    }
+
+    return res;
+}
+
+
+th_conn_t * th_conn_new(
+    void (*errfunc)(th_conn_t *conn, int err, const char *msg),
+    void (*msgfunc)(th_conn_t *conn, int loglevel, const char *msg))
+{
+    th_conn_t *conn = th_malloc0(sizeof(th_conn_t));
+
+    if (conn == NULL)
+        return NULL;
+
+    conn->errfunc = errfunc;
+    conn->msgfunc = msgfunc;
+
+    return conn;
+}
+
+
+static BOOL th_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 th_conn_set_proxy(th_conn_t *conn, int type, int port, const char *host, int auth_type)
+{
+    if (conn == NULL)
+        return -1;
+
+    conn->proxy.type = type;
+    conn->proxy.port = port;
+    conn->proxy.auth_type = auth_type;
+    
+    th_free(conn->proxy.host);
+    conn->proxy.host = th_strdup(host);
+
+    if (host != NULL)
+    {
+        conn->proxy.hst = th_resolve_host(conn, host);
+        th_get_addr(&(conn->proxy.addr), conn->proxy.hst);
+    }
+    else
+        return -2;
+
+    return 0;
+}
+
+
+int th_conn_set_proxy_auth_user(th_conn_t *conn, const char *userid, const char *passwd)
+{
+    if (conn == NULL)
+        return -1;
+
+    th_free(conn->proxy.userid);
+    conn->proxy.userid = th_strdup(userid);
+
+    th_free(conn->proxy.passwd);
+    conn->proxy.passwd = th_strdup(passwd);
+
+    return 0;
+}
+
+
+static BOOL th_conn_proxy_wait(th_conn_t *conn)
+{
+    int status, tries;
+
+    for (status = tries = 1; tries <= 20 && status > 0; tries++)
+    {
+#ifdef __WIN32
+        Sleep(50);
+#else
+        usleep(50000);
+#endif
+        th_conn_reset(conn);
+        status = th_conn_pull(conn);
+    }
+
+    if (status < 0)
+    {
+        th_conn_err(conn, status, "Proxy negotiation failed at try %d with network error: %d\n",
+            tries, status);
+    }
+    else
+        th_conn_err(conn, THERR_TIMED_OUT, "Proxy negotiation timed out.\n");
+
+    return status == FALSE;
+}
+
+
+static BOOL th_conn_proxy_send(th_conn_t *conn, void *buf, size_t bufsiz)
+{
+    BOOL ret;
+    th_conn_reset(conn);
+    ret = th_conn_send_buf(conn, buf, bufsiz);
+    th_free(buf);
+    return ret;
+}
+
+
+static int th_conn_socks4_negotiate(th_conn_t *conn, const int port, const char *host)
+{
+    struct th_socks4_res_t *sockres;
+    struct th_socks4_t *socksh;
+    size_t bufsiz;
+    uint8_t *ptr, *buf;
+    int err = THERR_INIT_FAIL;
+
+    (void) host;
+
+    th_conn_msg(conn, THLOG_INFO, "Initializing SOCKS 4/a proxy negotiation.\n");
+
+    bufsiz = sizeof(struct th_socks4_t) + strlen(conn->proxy.userid) + 1;
+    if (conn->proxy.type == TH_PROXY_SOCKS4A)
+        bufsiz += strlen(conn->host) + 1;
+
+    if ((ptr = buf = th_malloc(bufsiz)) == NULL)
+    {
+        err = THERR_MALLOC;
+        th_conn_err(conn, err,
+            "Could not allocate memory for SOCKS negotiation buffer, %d bytes.\n", bufsiz);
+        return err;
+    }
+
+    // Create SOCKS 4 handshake
+    socksh = (struct th_socks4_t *) buf;
+    socksh->version = 4;
+    socksh->command = TH_PROXY_CMD_CONNECT;
+    socksh->port    = htons(port);
+    socksh->addr    = (conn->proxy.type == TH_PROXY_SOCKS4A) ?  htonl(0x00000032) : conn->addr.s_addr;
+    ptr += sizeof(struct th_socks4_t);
+
+    strcpy((char *) ptr, conn->proxy.userid);
+
+    if (conn->proxy.type == TH_PROXY_SOCKS4A)
+    {
+        ptr += strlen(conn->proxy.userid) + 1;
+        strcpy((char *)ptr, conn->host);
+    }
+
+    // Send request
+    if (!th_conn_proxy_send(conn, buf, bufsiz))
+        return FALSE;
+
+    // Wait for SOCKS server to reply
+    if (th_conn_proxy_wait(conn) != 0)
+        return FALSE;
+
+    sockres = (struct th_socks4_res_t *) &(conn->buf);
+    if (sockres->nb != 0)
+    {
+        th_conn_err(conn, THERR_INIT_FAIL,
+            "Invalid SOCKS 4 server reply, does not begin with NUL byte (%d).\n", sockres->nb);
+        return FALSE;
+    }
+    if (sockres->result != 0x5a)
+    {
+        const char *s = NULL;
+        switch (sockres->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 4 error response"; break;
+        }
+        th_conn_err(conn, THERR_INIT_FAIL,
+            "SOCKS 4 setup failed, 0x%02x: %s.\n",
+            sockres->result, s);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+static int th_conn_socks5_negotiate(th_conn_t *conn, const int port, const char *host)
+{
+    size_t bufsiz, userid_len = 0, passwd_len = 0;
+    uint8_t *ptr, *buf;
+    int avail, auth;
+
+    th_conn_msg(conn, THLOG_INFO, "Initializing SOCKS 5 proxy negotiation.\n");
+
+    switch (conn->proxy.auth_type)
+    {
+        case TH_PROXY_AUTH_NONE:
+            avail = 1;
+            break;
+
+        case TH_PROXY_AUTH_USER:
+            avail = 2;
+            if (conn->proxy.userid == NULL || conn->proxy.passwd == NULL)
+            {
+                th_conn_err(conn, THERR_INVALID_DATA,
+                    "SOCKS 5 user authentication chosen, but no user/pass set.\n");
+                return FALSE;
+            }
+
+            userid_len = strlen(conn->proxy.userid);
+            passwd_len = strlen(conn->proxy.passwd);
+            if (userid_len > 255 || passwd_len > 255)
+            {
+                th_conn_err(conn, THERR_INVALID_DATA,
+                    "SOCKS 5 proxy userid or password is too long.\n");
+                return FALSE;
+            }
+            break;
+
+        default:
+            th_conn_err(conn, THERR_NOT_SUPPORTED,
+                "Unsupported proxy authentication method %d.\n",
+                conn->proxy.auth_type);
+            return FALSE;
+    }
+
+    bufsiz = 2 + avail;
+    if ((ptr = buf = th_malloc(bufsiz)) == NULL)
+    {
+        th_conn_err(conn, THERR_MALLOC,
+            "Could not allocate memory for SOCKS negotiation buffer, %d bytes.\n", bufsiz);
+        return FALSE;
+    }
+
+    // Form handshake packet
+    *ptr++ = 0x05;   // Protocol version
+    *ptr++ = avail;  // # of available auth methods
+    switch (conn->proxy.auth_type)
+    {
+        case TH_PROXY_AUTH_NONE:
+            *ptr++ = TH_SOCKS5_AUTH_NONE;
+
+        case TH_PROXY_AUTH_USER:
+            *ptr++ = TH_SOCKS5_AUTH_USER;
+            break;
+    }
+
+    // Send request
+    if (!th_conn_proxy_send(conn, buf, bufsiz))
+        return FALSE;
+
+    // Wait for SOCKS server to reply
+    if (th_conn_proxy_wait(conn) != 0)
+        return FALSE;
+
+    ptr = (uint8_t *) conn->buf;
+    if (*ptr != 0x05)
+    {
+        th_conn_err(conn, THERR_INVALID_DATA,
+            "Invalid SOCKS 5 server reply, does not begin with protocol version byte (%d).\n", *ptr);
+        return FALSE;
+    }
+    ptr++;
+    auth = *ptr;
+
+    if (auth == 0xff)
+    {
+        th_conn_err(conn, THERR_NOT_SUPPORTED,
+            "No authentication method could be negotiated with the server.\n");
+        return FALSE;
+    }
+
+    if (auth == TH_SOCKS5_AUTH_USER)
+    {
+        // Attempt user/pass authentication (RFC 1929)
+        bufsiz = 1 + 1 + 1 + userid_len + passwd_len;
+        if ((ptr = buf = th_malloc(bufsiz)) == NULL)
+        {
+            th_conn_err(conn, THERR_MALLOC,
+                "Could not allocate memory for SOCKS negotiation buffer, %d bytes.\n", bufsiz);
+            return FALSE;
+        }
+
+        *ptr++ = 0x01;
+
+        *ptr++ = userid_len;
+        memcpy(ptr, conn->proxy.userid, userid_len);
+        ptr += userid_len;
+
+        *ptr++ = passwd_len;
+        memcpy(ptr, conn->proxy.passwd, passwd_len);
+        ptr += passwd_len;
+
+        // Send request
+        if (!th_conn_proxy_send(conn, buf, bufsiz))
+            return FALSE;
+
+        // Wait for SOCKS server to reply
+        if (th_conn_proxy_wait(conn) != 0)
+            return FALSE;
+
+        ptr = (uint8_t *) conn->buf;
+        if (*ptr != 0x01)
+        {
+            th_conn_err(conn, THERR_INVALID_DATA,
+                "Invalid SOCKS 5 server reply, does not begin with protocol version byte (%d).\n", *ptr);
+            return FALSE;
+        }
+        ptr++;
+        if (*ptr != 0)
+        {
+            th_conn_err(conn, THERR_AUTH_FAILED,
+                "SOCKS 5 proxy user/pass authentication failed! Code %d.\n", *ptr);
+            return FALSE;
+        }
+    }
+    else
+    if (auth != TH_SOCKS5_AUTH_NONE)
+    {
+        th_conn_err(conn, THERR_NOT_SUPPORTED,
+            "Proxy server chose an unsupported SOCKS 5 authentication method %d.\n",
+            auth);
+        return FALSE;
+    }
+
+
+#if 0
+    // Form client connection request packet
+    bufsiz = 4;
+    
+    if ((ptr = buf = th_malloc(bufsiz)) == NULL)
+    {
+        th_conn_err(conn, THERR_MALLOC,
+            "Could not allocate memory for SOCKS negotiation buffer, %d bytes.\n", bufsiz);
+        return FALSE;
+    }
+
+    *ptr++ = 0x05;   // Protocol version
+    *ptr++ = cmd;
+    *ptr++ = 0x00;
+    *ptr++ = SOCKS5_ADDR_IPV4;
+
+    memcpy(ptr, conn->addr.s_addr, sizeof(conn->addr.s_addr));
+    ptr += sizeof(conn->addr.s_addr);
+    tmpPort = htons(port);
+    memcpy(ptr, tmpPort, sizeof(tmpPort));
+
+
+    // Send request
+    if (!th_conn_proxy_send(conn, buf, bufsiz))
+        return FALSE;
+
+    // Wait for SOCKS server to reply
+    if (th_conn_proxy_wait(conn) != 0)
+        return FALSE;
+
+    ptr = (uint8_t *) conn->buf;
+    if (*ptr != 0x05)
+    {
+        th_conn_err(conn, "Invalid SOCKS 5 server reply, does not begin with protocol version byte (%d).\n", *ptr);
+        return FALSE;
+    }
+    ptr++;
+    if (*ptr != 0)
+    {
+        if (*ptr >= 0 && *ptr < sizeof())
+            th_conn_err(conn, "%s.\n", nn_socks5_results_msgs[*ptr]);
+        else
+            th_conn_err(conn, ".\n");
+        return FALSE;
+    }
+    ptr++;
+    if (*ptr != 0)
+    {
+        th_conn_err(conn, ".\n");
+        return FALSE;
+    }
+
+#endif
+
+    return TRUE;
+}
+
+
+int th_conn_open(th_conn_t *conn, const int port, const char *host)
+{
+    struct sockaddr_in dest;
+    int err = THERR_INIT_FAIL;
+
+    if (conn == NULL)
+        return THERR_NULLPTR;
+
+    conn->port = port;
+    if (host != NULL)
+    {
+        conn->host = th_strdup(host);
+        conn->hst = th_resolve_host(conn, host);
+    }
+
+    th_get_addr(&(conn->addr), conn->hst);
+
+    // Prepare for connection
+    dest.sin_family = AF_INET;
+
+    if (conn->proxy.type > TH_PROXY_NONE && conn->proxy.type < TH_PROXY_LAST)
+    {
+        // If using a proxy, we connect to the proxy server
+        dest.sin_port = htons(conn->proxy.port);
+        dest.sin_addr = conn->proxy.addr;
+
+        th_conn_msg(conn, THLOG_INFO, "Connecting to %s proxy %s:%d ...\n",
+            th_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;
+
+        th_conn_msg(conn, THLOG_INFO, "Connecting to %s:%d ...\n",
+            inet_ntoa(conn->addr), conn->port);
+    }
+
+    if ((conn->socket = socket(PF_INET, SOCK_STREAM, 0)) == -1)
+    {
+        err = th_errno_to_error(th_get_socket_errno());
+        th_conn_err(conn, err, "Could not open socket: %s\n", th_error_str(err));
+        goto error;
+    }
+
+    if (connect(conn->socket, (struct sockaddr *) &dest, sizeof(dest)) == -1)
+    {
+        err = th_errno_to_error(th_get_socket_errno());
+        th_conn_err(conn, err, "Could not connect: %s\n", th_error_str(err));
+        goto error;
+    }
+
+    FD_ZERO(&(conn->sockfds));
+    FD_SET(conn->socket, &(conn->sockfds));
+
+    // Proxy-specific setup
+    switch (conn->proxy.type)
+    {
+        case TH_PROXY_SOCKS4:
+        case TH_PROXY_SOCKS4A:
+            if (!th_conn_socks4_negotiate(conn, port, host))
+                goto error;
+            th_conn_msg(conn, THLOG_INFO, "SOCKS 4 connection established!\n");
+            break;
+        
+        case TH_PROXY_SOCKS5:
+            if (!th_conn_socks5_negotiate(conn, port, host))
+                goto error;
+            th_conn_msg(conn, THLOG_INFO, "SOCKS 5 connection established!\n");
+            break;
+    }
+
+    th_conn_reset(conn);
+    conn->status = TH_CONN_OPEN;
+    
+    // Insert to connection list
+    conn->node = th_llist_append(&th_conn_list, conn);
+    
+    return THERR_OK;
+
+error:
+    th_conn_close(conn);
+    return err;
+}
+
+
+int th_conn_close(th_conn_t *conn)
+{
+    if (conn == NULL)
+        return -1;
+
+    if (conn->socket >= 0)
+    {
+#ifdef __WIN32
+        closesocket(conn->socket);
+#else
+        close(conn->socket);
+#endif
+        conn->socket = -1;
+    }
+
+    conn->status = TH_CONN_CLOSED;
+    return 0;
+}
+
+
+static void th_conn_free_nodelete(th_conn_t *conn)
+{
+    th_conn_close(conn);
+    th_free(conn->host);
+    th_free(conn->proxy.host);
+    th_free(conn);
+}
+
+
+void th_conn_free(th_conn_t *conn)
+{
+    if (conn != NULL)
+    {
+        // Remove from linked list
+        if (conn->node != NULL)
+            th_llist_delete_node_fast(&th_conn_list, conn->node);
+        
+        // Free connection data
+        th_conn_free_nodelete(conn);
+    }
+}
+
+
+BOOL th_conn_send_buf(th_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)
+        {
+            int err = th_errno_to_error(th_get_socket_errno());
+            th_conn_err(conn, err, "th_conn_send_buf() failed: %s", th_error_str(err));
+            return FALSE;
+        }
+        bufLeft -= bufSent;
+        bufPtr += bufSent;
+    }
+
+    return TRUE;
+}
+
+
+void th_conn_reset(th_conn_t *conn)
+{
+    if (conn != NULL)
+    {
+        conn->ptr = conn->in_ptr = conn->buf;
+        conn->got_bytes = conn->total_bytes = 0;
+    }
+}
+
+
+int th_conn_pull(th_conn_t *conn)
+{
+    int result;
+    struct timeval socktv;
+    fd_set tmpfds;
+
+    if (conn == NULL)
+        return TH_CONN_ERROR;
+
+    // Shift the input buffer
+    if (conn->ptr > conn->buf)
+    {
+        size_t left = conn->in_ptr - conn->ptr;
+        if (left > 0)
+        {
+            size_t moved = conn->ptr - conn->buf;
+            memmove(conn->buf, conn->ptr, left);
+            conn->ptr = conn->buf;
+            conn->in_ptr -= moved;
+            conn->total_bytes -= moved;
+        }
+        else
+            th_conn_reset(conn);        
+    }
+
+    // Check for incoming data
+    socktv.tv_sec = 0;
+    socktv.tv_usec = TH_DELAY_USEC;
+    tmpfds = conn->sockfds;
+
+    if ((result = select(conn->socket + 1, &tmpfds, NULL, NULL, &socktv)) == -1)
+    {
+        int err = th_get_socket_errno();
+        if (err != EINTR)
+        {
+            err = th_errno_to_error(err);
+            th_conn_err(conn, err, "Error occured in select(%d, sockfds): %s\n",
+                socket, th_error_str(err));
+            return TH_CONN_ERROR;
+        }
+    }
+    else if (FD_ISSET(conn->socket, &tmpfds))
+    {
+        conn->got_bytes = recv(conn->socket, conn->in_ptr, TH_CONNBUF_SIZE - conn->total_bytes, 0);
+        if (conn->got_bytes < 0)
+        {
+            int err = th_errno_to_error(th_get_socket_errno());
+            th_conn_err(conn, err, "Error in recv: %s\n", th_error_str(err));
+            return TH_CONN_ERROR;
+        }
+        else if (conn->got_bytes == 0)
+        {
+            th_conn_err(conn, ECONNABORTED, "Server closed connection.\n");
+            conn->status = TH_CONN_CLOSED;
+            return TH_CONN_CLOSED;
+        }
+        else
+        {
+            conn->total_bytes += conn->got_bytes;
+            conn->in_ptr += conn->got_bytes;
+            return TH_CONN_DATA_AVAIL;
+        }
+    }
+
+    return TH_CONN_NO_DATA;
+}
+
+
+BOOL th_conn_check(th_conn_t *conn)
+{
+    if (conn == NULL)
+        return FALSE;
+
+    return conn->err == 0 && conn->status == TH_CONN_OPEN;
+}
+
+
+int th_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 THERR_INIT_FAIL;
+    }
+#endif
+
+    th_network_inited = TRUE;
+
+    th_conn_list = NULL;
+
+    return THERR_OK;
+}
+
+
+void th_network_close(void)
+{
+    if (th_network_inited)
+    {
+        // Close connections
+        qlist_t *curr = th_conn_list;
+        while (curr != NULL)
+        {
+            qlist_t *next = curr->next;
+            th_conn_free_nodelete(curr->data);
+            curr = next;
+        }
+        
+#ifdef __WIN32
+        WSACleanup();
+#endif
+    }
+
+    th_network_inited = FALSE;
+}
+
+
+BOOL th_conn_buf_check(th_conn_t *conn, size_t n)
+{
+    return conn && (conn->ptr + n <= conn->in_ptr);
+}
+
+
+BOOL th_conn_buf_skip(th_conn_t *conn, size_t n)
+{
+    if (th_conn_buf_check(conn, n))
+    {
+        conn->ptr += n;
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int th_conn_buf_strncmp(th_conn_t *conn, const char *str, const size_t n)
+{
+    int ret;
+    if (!th_conn_buf_check(conn, n))
+        return -1;
+
+    if ((ret = strncmp(conn->ptr, str, n)) == 0)
+    {
+        conn->ptr += n;
+        return 0;
+    }
+    else
+        return ret;
+}
+
+
+int th_conn_buf_strcmp(th_conn_t *conn, const char *str)
+{
+    return th_conn_buf_strncmp(conn, str, strlen(str));
+}
+
+
+char *th_conn_buf_strstr(th_conn_t *conn, const char *str)
+{
+    char *pos;
+    size_t n = strlen(str);
+
+    if (th_conn_buf_check(conn, n) && ((pos = strstr(conn->ptr, str)) != NULL))
+    {
+        conn->ptr = pos + n;
+        return pos;
+    }
+    else
+        return NULL;
+}
+
+
+void th_conn_dump_buffer(FILE *f, th_conn_t *conn)
+{
+    char *p;
+    size_t offs, left;
+    
+    fprintf(f,
+    "\n--------------------------------------------------------------\n"
+    "err=%d, status=%d, got_bytes=%d, total_bytes=%d\n"
+    "buf=0x%p, in_ptr=0x%04x, ptr=0x%04x\n",
+    conn->err, conn->status, conn->got_bytes, conn->total_bytes,
+    conn->buf, conn->in_ptr - conn->buf, conn->ptr - conn->buf);
+    
+    // Dump buffer contents as a hexdump
+    for (offs = 0, left = conn->total_bytes, p = conn->buf; p < conn->in_ptr;)
+    {
+        char buf[TH_DUMP_BYTES + 1];
+        size_t bufoffs, amount = left < TH_DUMP_BYTES ? left : TH_DUMP_BYTES;
+        left -= amount;
+
+        // Dump offs | xx xx xx xx | and fill string
+        fprintf(f, "%04x | ", offs);
+        for (bufoffs = 0; bufoffs < amount; offs++, bufoffs++, p++)
+        {
+            fprintf(f, "%02x ", *p);
+            buf[bufoffs] = th_isprint(*p) ? *p : '.';
+        }
+        buf[bufoffs] = 0;
+
+        // Add padding
+        for (; bufoffs < TH_DUMP_BYTES; bufoffs++)
+            fprintf(f, "   ");
+
+        // Print string
+        fprintf(f, "| %s\n", buf);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/th_network.h	Sat Jun 21 02:41:29 2014 +0300
@@ -0,0 +1,155 @@
+/*
+ * Simple TCP network connection handling
+ * Programmed and designed by Matti 'ccr' Hamalainen
+ * (C) Copyright 2013-2014 Tecnic Software productions (TNSP)
+ *
+ * Please read file 'COPYING' for information on license and distribution.
+ */
+#ifndef TH_NETWORK_H
+#define TH_NETWORK_H
+
+#include <stdio.h>
+#include <unistd.h>
+#include "th_types.h"
+#include "th_util.h"
+
+
+#ifdef __WIN32
+#define __OBJC_BOOL // A nasty hack
+#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
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Global defines
+ */
+#define TH_CONNBUF_SIZE   (64 * 1024)
+#define TH_DELAY_USEC     (15 * 1000)
+#define TH_DUMP_BYTES	  16
+
+
+enum
+{
+    TH_CONN_UNINIT = 0,
+    TH_CONN_PROXY_NEG,
+    TH_CONN_OPEN,
+    TH_CONN_CLOSED,
+    
+    TH_CONN_ERROR,
+    TH_CONN_DATA_AVAIL,
+    TH_CONN_NO_DATA,
+};
+
+enum
+{
+    TH_PROXY_NONE = 0,
+    TH_PROXY_SOCKS4,
+    TH_PROXY_SOCKS4A,
+    TH_PROXY_SOCKS5,
+
+    TH_PROXY_LAST
+};
+
+enum
+{
+    TH_PROXY_AUTH_NONE,
+    TH_PROXY_AUTH_USER,
+};
+
+enum
+{
+    TH_PROXY_CMD_CONNECT = 1,
+    TH_PROXY_CMD_BIND = 2,
+    TH_PROXY_CMD_ASSOC_UDP = 3,
+};
+
+
+typedef struct _th_conn_t
+{
+    // Proxy settings and data
+    struct
+    {
+        char *host;
+        struct hostent *hst;
+        int type, auth_type;
+        int port;
+        struct in_addr addr;
+        char *userid, *passwd;
+    } proxy;
+
+    // Target host data
+    char *host;
+    struct hostent *hst;
+    int port;
+
+    // Socket data
+    int socket;
+    struct in_addr addr;
+    fd_set sockfds;
+
+    // Error handling and status message functors
+    void (*errfunc)(struct _th_conn_t *conn, int err, const char *msg);
+    void (*msgfunc)(struct _th_conn_t *conn, int loglevel, const char *msg);
+
+    int err;
+    int status;
+
+    // Data buffer
+    char buf[TH_CONNBUF_SIZE + 16];
+    char *ptr, *in_ptr;
+    ssize_t got_bytes, total_bytes;
+
+    void *node;
+} th_conn_t;
+
+
+int         th_network_init();
+void        th_network_close(void);
+
+struct hostent *th_resolve_host(th_conn_t *conn, const char *name);
+th_conn_t * th_conn_new(
+    void (*errfunc)(th_conn_t *conn, int err, const char *msg),
+    void (*msgfunc)(th_conn_t *conn, int loglevel, const char *msg));
+
+void        th_conn_err(th_conn_t *conn, int err, const char *fmt, ...);
+void        th_conn_msg(th_conn_t *conn, int loglevel, const char *fmt, ...);
+
+int         th_conn_set_proxy(th_conn_t *conn, int type, int port, const char *host, int auth_type);
+int         th_conn_set_proxy_auth_user(th_conn_t *conn, const char *userid, const char *passwd);
+int         th_conn_open(th_conn_t *conn, const int port, const char *host);
+int         th_conn_close(th_conn_t *);
+void        th_conn_free(th_conn_t *);
+void        th_conn_reset(th_conn_t *);
+
+int         th_conn_pull(th_conn_t *);
+BOOL        th_conn_send_buf(th_conn_t *, const char *buf, const size_t len);
+BOOL        th_conn_check(th_conn_t *);
+
+
+BOOL        th_conn_buf_check(th_conn_t *conn, size_t n);
+BOOL        th_conn_buf_skip(th_conn_t *conn, size_t n);
+int         th_conn_buf_strncmp(th_conn_t *conn, const char *str, const size_t n);
+int         th_conn_buf_strcmp(th_conn_t *conn, const char *str);
+char *      th_conn_buf_strstr(th_conn_t *conn, const char *str);
+
+void        th_conn_dump_buffer(FILE *f, th_conn_t *conn);
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* TH_NETWORK_H */