changeset 352:b54c8545dcb0

Overhaul network code a bit, add initial implementation of SOCKS4/4A proxy support -- which may not work yet, it is untested.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 23 Jun 2011 06:28:40 +0300
parents b18b97d3e4f5
children 83ae825bb8c1
files libnnchat.c libnnchat.h nnchat.c
diffstat 3 files changed, 224 insertions(+), 40 deletions(-) [+]
line wrap: on
line diff
--- a/libnnchat.c	Thu Jun 23 04:39:10 2011 +0300
+++ b/libnnchat.c	Thu Jun 23 06:28:40 2011 +0300
@@ -53,8 +53,6 @@
 
 void nn_conn_err(nn_conn_t *conn, const char *fmt, ...)
 {
-    conn->err = TRUE;
-
     if (conn->errfunc) {
         va_list ap;
         va_start(ap, fmt);
@@ -74,42 +72,153 @@
     }
 }
 
+static const char *nn_proxy_types[] = {
+    "none",
+    "SOCKS 4",
+    "SOCKS 4a",
+    NULL
+};
 
-nn_conn_t *nn_conn_open(struct in_addr *addr, const int port)
+nn_conn_t * nn_conn_open(
+        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 ptype, int pport, struct in_addr *paddr,
+        struct in_addr *addr, const int port, const char *host)
 {
     nn_conn_t *conn = th_calloc(1, sizeof(nn_conn_t));
     struct sockaddr_in dest;
-    
+    static const char *userid = "James Bond";
+
     if (conn == NULL)
         return NULL;
 
-    dest.sin_family = AF_INET;
-    dest.sin_port = htons(port);
-    dest.sin_addr = *addr;
+    /* Initialize data */
+    conn->errfunc = errfunc;
+    conn->msgfunc = msgfunc;
+    
+    conn->proxy.type = ptype;
+    conn->proxy.port = pport;
+    if (paddr != NULL)
+        conn->proxy.addr = *paddr;
+
+    conn->port = port;
+    conn->addr = *addr;
+
+    if (ptype == NN_PROXY_SOCKS4A && host == NULL) {
+        nn_conn_err(conn, "Host string NULL and proxy type SOCKS 4a specified. Using IP address instead.\n");
+        conn->host = th_strdup(inet_ntoa(dest.sin_addr));
+    } else
+        conn->host = th_strdup(host);
 
-    nn_conn_msg(conn, "Connecting to %s:%d ...\n",
-        inet_ntoa(dest.sin_addr), port);
+
+    /* Prepare for connection */
+    if (ptype > NN_PROXY_NONE && ptype < NN_PROXY_LAST) {
+        dest.sin_family = AF_INET;
+        dest.sin_port = htons(pport);
+        dest.sin_addr = conn->proxy.addr;
+
+        nn_conn_msg(conn, "Connecting to %s proxy %s:%d ...\n",
+            nn_proxy_types[ptype],
+            inet_ntoa(dest.sin_addr), port);
+    } else {
+        dest.sin_family = AF_INET;
+        dest.sin_port = htons(port);
+        dest.sin_addr = *addr;
+
+        nn_conn_msg(conn, "Connecting to %s:%d ...\n",
+            inet_ntoa(dest.sin_addr), port);
+    }
 
     if ((conn->socket = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
-        nn_conn_err(conn, "Could not open socket: %s\n", strerror(errno));
-        conn->status = NN_CONN_CLOSED;
-        return conn;
+        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) {
-        nn_conn_err(conn, "Could not connect: %s\n", strerror(errno));
-        conn->status = NN_CONN_CLOSED;
-        return conn;
+        conn->err = nn_get_socket_errno();
+        nn_conn_err(conn, "Could not connect: %s\n", nn_get_socket_errstr(conn->err));
+        goto error;
     }
 
-    conn->port = port;
-    conn->address = *addr;
     FD_ZERO(&(conn->sockfds));
     FD_SET(conn->socket, &(conn->sockfds));
+
+    /* Proxy-specific setup */
+    if (ptype == NN_PROXY_SOCKS4 || ptype == NN_PROXY_SOCKS4A) {
+        struct nn_socks_t *socksh;
+        size_t bufsiz = sizeof(struct nn_socks_t) + strlen(userid) + 1 + strlen(conn->host) + 1;
+        char *ptr, *buf;
+        int tries, status = -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 = (ptype == NN_PROXY_SOCKS4) ? 4 : 5;
+        socksh->command = SOCKS_CMD_CONNECT;
+        socksh->port = htons(port);
+        if (ptype == NN_PROXY_SOCKS4A)
+            socksh->addr = htonl(0x00000032);
+        else
+            socksh->addr = dest.sin_addr.s_addr;
+        ptr += sizeof(struct nn_socks_t);
+
+        strcpy(ptr, userid);
+        ptr += strlen(userid) + 1;
+        
+        strcpy(ptr, conn->host);
+        
+        /* Send request */
+        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 (tries = 0; tries < 20; tries++) {
+            status = nn_conn_pull(conn);
+            if (status <= 0) break;
+        }
+        
+        /* 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;
+            }
+        }
+        else
+            goto error;
+    }
+
     conn->status = NN_CONN_OPEN;
-    
+    return conn;
+
+error:
+    conn->status = NN_CONN_CLOSED;
     return conn;
 }
 
@@ -143,7 +252,8 @@
         ssize_t bufSent;
         bufSent = send(conn->socket, bufPtr, bufLeft, 0);
         if (bufSent < 0) {
-            nn_conn_err(conn, "nn_conn_send_buf() failed: %s", strerror(errno));
+            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;
@@ -154,7 +264,7 @@
 }
 
 
-BOOL nn_conn_pull(nn_conn_t *conn)
+int nn_conn_pull(nn_conn_t *conn)
 {
     int result;
     struct timeval socktv;
@@ -171,19 +281,18 @@
     tmpfds = conn->sockfds;
 
     if ((result = select(conn->socket + 1, &tmpfds, NULL, NULL, &socktv)) == -1) {
-        int err = nn_get_socket_errno();
-        if (err != EINTR) {
+        conn->err = nn_get_socket_errno();
+        if (conn->err != EINTR) {
             nn_conn_err(conn, "Error occured in select(%d, sockfds): %d, %s\n",
-                socket, err, nn_get_socket_errstr(err));
+                socket, conn->err, nn_get_socket_errstr(conn->err));
             return -1;
         }
     } else
     if (FD_ISSET(conn->socket, &tmpfds)) {
         conn->got = recv(conn->socket, conn->ptr, NN_CONNBUF_SIZE, 0);
-
         if (conn->got < 0) {
-            int res = nn_get_socket_errno();
-            nn_conn_err(conn, "Error in recv: %d, %s\n", res, nn_get_socket_errstr(res));
+            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 == 0) {
             nn_conn_err(conn, "Server closed connection.\n");
@@ -933,5 +1042,3 @@
     th_free(tuple->str);
     th_free(tuple);
 }
-
-
--- a/libnnchat.h	Thu Jun 23 04:39:10 2011 +0300
+++ b/libnnchat.h	Thu Jun 23 06:28:40 2011 +0300
@@ -34,14 +34,49 @@
 
 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 {
+        int type;
+        int port;
+        struct in_addr addr;
+    } proxy;
+
+    char *host;
     int socket;
     int port;
-    struct in_addr address;
+    struct in_addr addr;
     fd_set sockfds;
 
     void (*errfunc)(struct _nn_conn_t *conn, const char *fmt, va_list ap);
@@ -55,13 +90,18 @@
     ssize_t got;
 } nn_conn_t;
 
+
 const char *nn_get_errstr(int err);
-BOOL        nn_network_init(void);
+BOOL        nn_network_init();
 void        nn_network_close(void);
 
-nn_conn_t * nn_conn_open(struct in_addr *addr, const int port);
+nn_conn_t * nn_conn_open(
+        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 ptype, int pport, struct in_addr *paddr,
+        struct in_addr *addr, const int port, const char *host);
 void        nn_conn_close(nn_conn_t *);
-BOOL        nn_conn_pull(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 *);
--- a/nnchat.c	Thu Jun 23 04:39:10 2011 +0300
+++ b/nnchat.c	Thu Jun 23 06:28:40 2011 +0300
@@ -39,9 +39,12 @@
 
 /* Options
  */
-int     optPort = 8005;
+int     optPort = 8005,
+        optProxyPort = 1080,
+        optProxyType = NN_PROXY_NONE;
 int     optUserColor = 0x000000;
 char    *optServer = "chat.newbienudes.com",
+        *optProxyServer = NULL,
         *optUserName = NULL,
         *optUserNameCmd = NULL,
         *optUserNameEnc = NULL,
@@ -89,8 +92,12 @@
     { 4, 'C', "color",      "Initial color in RGB hex 000000", OPT_ARGREQ },
     { 5, 'l', "logfile",    "Log filename", OPT_ARGREQ },
     { 6, 'D', "daemon",     "A pseudo-daemon mode for logging", OPT_NONE },
-    { 7, 'S', "site",       "Site (default: NN)", OPT_ARGREQ },
+    { 7, 'f', "force-site", "Force site (default: NN)", OPT_ARGREQ },
     { 8, 'd', "debug",      "Enable various debug features", OPT_NONE },
+    
+    {10, '4', "socks4",     "SOCKS4 proxy server", OPT_ARGREQ },
+    {11, 'A', "socks4a",    "SOCKS4A proxy server", OPT_ARGREQ },
+    {12, 'P', "proxy-port", "Proxy port (default: 1080)", OPT_ARGREQ },
 };
 
 const int optListN = (sizeof(optList) / sizeof(optList[0]));
@@ -151,6 +158,22 @@
         THMSG(1, "Debug mode enabled.\n");
         break;
 
+
+    case 10:
+        optProxyServer = optArg;
+        optProxyType = NN_PROXY_SOCKS4;
+        break;
+
+    case 11:
+        optProxyServer = optArg;
+        optProxyType = NN_PROXY_SOCKS4A;
+        break;
+
+    case 12:
+        optPort = atoi(optArg);
+        break;
+
+
     default:
         THERR("Unknown option '%s'.\n", currArg);
         return FALSE;
@@ -1249,7 +1272,7 @@
 int main(int argc, char *argv[])
 {
     nn_conn_t *conn = NULL;
-    struct hostent *tmpHost;
+    struct hostent *tmpHost = NULL, *proxyHost = NULL;
     int curVis = ERR, updateCount = 0;
     BOOL argsOK, isError = FALSE,
         exitProg = FALSE,
@@ -1429,6 +1452,17 @@
         goto err_exit;
     }
 
+    /* Are we using a proxy? */
+    if (optProxyType != NN_PROXY_NONE && optProxyServer != NULL) {
+        printMsg(currWin, "Trying to resolve proxy host '%s' ...\n", optProxyServer);
+        tmpHost = gethostbyname(optProxyServer);
+        if (tmpHost == NULL) {
+            errorMsg("Could not resolve hostname: %s.\n", strerror(h_errno));
+            goto err_exit;
+        }
+        printMsg(currWin, "True hostname: %s\n", tmpHost->h_name);
+    }
+    
     /* Okay ... */
     printMsg(currWin, "Trying to resolve host '%s' ...\n", optServer);
     tmpHost = gethostbyname(optServer);
@@ -1442,7 +1476,10 @@
     /* To emulate the official client, we first make a request for
      * policy file, even though we don't use it for anything...
      */
-    conn = nn_conn_open((struct in_addr *) tmpHost->h_addr, 843);
+    conn = nn_conn_open(errorFunc, messageFunc,
+        optProxyType, optProxyPort, proxyHost != NULL ? (struct in_addr *) proxyHost->h_addr : NULL,
+        (struct in_addr *) tmpHost->h_addr, 843, optServer);
+
     if (!nn_conn_check(conn)) {
         errorMsg("Policy file request connection setup failed!\n");
         goto err_exit;
@@ -1464,15 +1501,15 @@
 #endif
 
     /* Okay, now do the proper connection ... */
-    conn = nn_conn_open((struct in_addr *) tmpHost->h_addr, optPort);
+    conn = nn_conn_open(errorFunc, messageFunc,
+        optProxyType, optProxyPort, proxyHost != NULL ? (struct in_addr *) proxyHost->h_addr : NULL,
+        (struct in_addr *) tmpHost->h_addr, optPort, optServer);
+
     if (!nn_conn_check(conn)) {
         errorMsg("Main connection setup failed!\n");
         goto err_exit;
     }
     
-    conn->errfunc = errorFunc;
-    conn->msgfunc = messageFunc;
-
     /* Send login command */
     optUserNameEnc = nn_dblencode_str(optUserName);
     tmpStr = nn_dblencode_str(optSite);