# HG changeset patch # User Matti Hamalainen # Date 1308799720 -10800 # Node ID b54c8545dcb082181353a851bf0a8de8d67728a2 # Parent b18b97d3e4f5c17e0e5d323c6adb08bcc3661543 Overhaul network code a bit, add initial implementation of SOCKS4/4A proxy support -- which may not work yet, it is untested. diff -r b18b97d3e4f5 -r b54c8545dcb0 libnnchat.c --- 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); } - - diff -r b18b97d3e4f5 -r b54c8545dcb0 libnnchat.h --- 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 *); diff -r b18b97d3e4f5 -r b54c8545dcb0 nnchat.c --- 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);