diff libnnchat.c @ 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 8e509d6546d3
children c01e42fc9adb
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);
 }
-
-