/*
TSOCKS - Wrapper library for transparent SOCKS
Copyright (C) 2000 Shaun Clowes
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* PreProcessor Defines */
#include <config.h>
#ifdef USE_GNU_SOURCE
#define _GNU_SOURCE
#endif
/* Global configuration variables */
char *progname = "libtsocks"; /* Name used in err msgs */
/* Header Files */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <strings.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <pwd.h>
#include <errno.h>
#include <fcntl.h>
#include <common.h>
#include <stdarg.h>
#ifdef USE_SOCKS_DNS
#include <resolv.h>
#endif
#include <parser.h>
#include <tsocks.h>
/* Global Declarations */
#ifdef USE_SOCKS_DNS
static int (*realresinit)(void);
#endif
static int (*realconnect)(CONNECT_SIGNATURE);
static int (*realselect)(SELECT_SIGNATURE);
static int (*realpoll)(POLL_SIGNATURE);
static int (*realclose)(CLOSE_SIGNATURE);
static struct parsedfile *config;
static struct connreq *requests = NULL;
static int suid = 0;
static char *conffile = NULL;
/* Exported Function Prototypes */
void _init(void);
int connect(CONNECT_SIGNATURE);
int select(SELECT_SIGNATURE);
int poll(POLL_SIGNATURE);
int close(CLOSE_SIGNATURE);
#ifdef USE_SOCKS_DNS
int res_init(void);
#endif
/* Private Function Prototypes */
static int get_config();
static int get_environment();
static int connect_server(struct connreq *conn);
static int send_socks_request(struct connreq *conn);
static struct connreq *new_socks_request(int sockid, struct sockaddr_in *connaddr,
struct sockaddr_in *serveraddr,
struct serverent *path);
static void kill_socks_request(struct connreq *conn);
static int handle_request(struct connreq *conn);
static struct connreq *find_socks_request(int sockid, int includefailed);
static int connect_server(struct connreq *conn);
static int send_socks_request(struct connreq *conn);
static int send_socksv4_request(struct connreq *conn);
static int send_socksv5_method(struct connreq *conn);
static int send_socksv5_connect(struct connreq *conn);
static int send_buffer(struct connreq *conn);
static int recv_buffer(struct connreq *conn);
static int read_socksv5_method(struct connreq *conn);
static int read_socksv4_req(struct connreq *conn);
static int read_socksv5_connect(struct connreq *conn);
static int read_socksv5_auth(struct connreq *conn);
void _init(void) {
#ifdef USE_OLD_DLSYM
void *lib;
#endif
/* We could do all our initialization here, but to be honest */
/* most programs that are run won't use our services, so */
/* we do our general initialization on first call */
/* Determine the logging level */
suid = (getuid() != geteuid());
#ifndef USE_OLD_DLSYM
realconnect = dlsym(RTLD_NEXT, "connect");
realselect = dlsym(RTLD_NEXT, "select");
realpoll = dlsym(RTLD_NEXT, "poll");
realclose = dlsym(RTLD_NEXT, "close");
#ifdef USE_SOCKS_DNS
realresinit = dlsym(RTLD_NEXT, "res_init");
#endif
#else
lib = dlopen(LIBCONNECT, RTLD_LAZY);
realconnect = dlsym(lib, "connect");
realselect = dlsym(lib, "select");
realpoll = dlsym(lib, "poll");
#ifdef USE_SOCKS_DNS
realresinit = dlsym(lib, "res_init");
#endif
dlclose(lib);
lib = dlopen(LIBC, RTLD_LAZY);
realclose = dlsym(lib, "close");
dlclose(lib);
#endif
}
static int get_environment() {
static int done = 0;
int loglevel = MSGERR;
char *logfile = NULL;
char *env;
if (done)
return(0);
/* Determine the logging level */
#ifndef ALLOW_MSG_OUTPUT
set_log_options(-1, stderr, 0);
#else
if ((env = getenv("TSOCKS_DEBUG")))
loglevel = atoi(env);
if (((env = getenv("TSOCKS_DEBUG_FILE"))) && !suid)
logfile = env;
set_log_options(loglevel, logfile, 1);
#endif
done = 1;
return(0);
}
static int get_config () {
static int done = 0;
if (done)
return(0);
/* Determine the location of the config file */
#ifdef ALLOW_ENV_CONFIG
if (!suid)
conffile = getenv("TSOCKS_CONF_FILE");
#endif
/* Read in the config file */
config = malloc(sizeof(*config));
if (!config)
return(0);
read_config(conffile, config);
if (config->paths)
show_msg(MSGDEBUG, "First lineno for first path is %d\n", config->paths->lineno);
done = 1;
return(0);
}
int connect(CONNECT_SIGNATURE) {
struct sockaddr_in *connaddr;
struct sockaddr_in peer_address;
struct sockaddr_in server_address;
int gotvalidserver = 0, rc, namelen = sizeof(peer_address);
int sock_type = -1;
int sock_type_len = sizeof(sock_type);
unsigned int res = -1;
struct serverent *path;
struct connreq *newconn;
get_environment();
/* If the real connect doesn't exist, we're stuffed */
if (realconnect == NULL) {
show_msg(MSGERR, "Unresolved symbol: connect\n");
return(-1);
}
show_msg(MSGDEBUG, "Got connection request\n");
connaddr = (struct sockaddr_in *) __addr;
/* Get the type of the socket */
getsockopt(__fd, SOL_SOCKET, SO_TYPE,
(void *) &sock_type, &sock_type_len);
/* If this isn't an INET socket for a TCP stream we can't */
/* handle it, just call the real connect now */
if ((connaddr->sin_family != AF_INET) ||
(sock_type != SOCK_STREAM)) {
show_msg(MSGDEBUG, "Connection isn't a TCP stream ignoring\n");
return(realconnect(__fd, __addr, __len));
}
/* If we haven't initialized yet, do it now */
get_config();
/* Are we already handling this connect? */
if ((newconn = find_socks_request(__fd, 1))) {
if (memcmp(&newconn->connaddr, connaddr, sizeof(*connaddr))) {
/* Ok, they're calling connect on a socket that is in our
* queue but this connect() isn't to the same destination,
* they're obviously not trying to check the status of
* they're non blocking connect, they must have close()d
* the other socket and created a new one which happens
* to have the same fd as a request we haven't had the chance
* to delete yet, so we delete it here. */
show_msg(MSGDEBUG, "Call to connect received on old "
"tsocks request for socket %d but to "
"new destination, deleting old request\n",
newconn->sockid);
kill_socks_request(newconn);
} else {
/* Ok, this call to connect() is to check the status of
* a current non blocking connect(). */
if (newconn->state == FAILED) {
show_msg(MSGDEBUG, "Call to connect received on failed "
"request %d, returning %d\n",
newconn->sockid, newconn->err);
errno = newconn->err;
rc = -1;
} else if (newconn->state == DONE) {
show_msg(MSGERR, "Call to connect received on completed "
"request %d\n",
newconn->sockid, newconn->err);
rc = 0;
} else {
show_msg(MSGDEBUG, "Call to connect received on current request %d\n",
newconn->sockid);
rc = handle_request(newconn);
errno = rc;
}
if ((newconn->state == FAILED) || (newconn->state == DONE))
kill_socks_request(newconn);
return((rc ? -1 : 0));
}
}
/* If the socket is already connected, just call connect */
/* and get its standard reply */
if (!getpeername(__fd, (struct sockaddr *) &peer_address, &namelen)) {
show_msg(MSGDEBUG, "Socket is already connected, defering to "
"real connect\n");
return(realconnect(__fd, __addr, __len));
}
show_msg(MSGDEBUG, "Got connection request for socket %d to "
"%s\n", __fd, inet_ntoa(connaddr->sin_addr));
/* If the address is local call realconnect */
if (!(is_local(config, &(connaddr->sin_addr)))) {
show_msg(MSGDEBUG, "Connection for socket %d is local\n", __fd);
return(realconnect(__fd, __addr, __len));
}
/* Ok, so its not local, we need a path to the net */
pick_server(config, &path, &(connaddr->sin_addr), ntohs(connaddr->sin_port));
show_msg(MSGDEBUG, "Picked server %s for connection\n",
(path->address ? path->address : "(Not Provided)"));
if (path->address == NULL) {
if (path == &(config->defaultserver))
show_msg(MSGERR, "Connection needs to be made "
"via default server but "
"the default server has not "
"been specified\n");
else
show_msg(MSGERR, "Connection needs to be made "
"via path specified at line "
"%d in configuration file but "
"the server has not been "
"specified for this path\n",
path->lineno);
} else if ((res = resolve_ip(path->address, 0, HOSTNAMES)) == -1) {
show_msg(MSGERR, "The SOCKS server (%s) listed in the configuration "
"file which needs to be used for this connection "
"is invalid\n", path->address);
} else {
/* Construct the addr for the socks server */
server_address.sin_family = AF_INET; /* host byte order */
server_address.sin_addr.s_addr = res;
server_address.sin_port = htons(path->port);
bzero(&(server_address.sin_zero), 8);
/* Complain if this server isn't on a localnet */
if (is_local(config, &server_address.sin_addr)) {
show_msg(MSGERR, "SOCKS server %s (%s) is not on a local subnet!\n",
path->address, inet_ntoa(server_address.sin_addr));
} else
gotvalidserver = 1;
}
/* If we haven't found a valid server we return connection refused */
if (!gotvalidserver ||
!(newconn = new_socks_request(__fd, connaddr, &server_address, path))) {
errno = ECONNREFUSED;
return(-1);
} else {
/* Now we call the main function to handle the connect. */
rc = handle_request(newconn);
/* If the request completed immediately it mustn't have been
* a non blocking socket, in this case we don't need to know
* about this socket anymore. */
if ((newconn->state == FAILED) || (newconn->state == DONE))
kill_socks_request(newconn);
errno = rc;
return((rc ? -1 : 0));
}
}
int select(SELECT_SIGNATURE) {
int nevents = 0;
int rc = 0;
int setevents = 0;
int monitoring = 0;
struct connreq *conn, *nextconn;
fd_set mywritefds, myreadfds, myexceptfds;
/* If we're not currently managing any requests we can just
* leave here */
if (!requests)
return(realselect(n, readfds, writefds, exceptfds, timeout));
get_environment();
show_msg(MSGDEBUG, "Intercepted call to select with %d fds, "
"0x%08x 0x%08x 0x%08x, timeout %08x\n", n,
readfds, writefds, exceptfds, timeout);
for (conn = requests; conn != NULL; conn = conn->next) {
if ((conn->state == FAILED) || (conn->state == DONE))
continue;
conn->selectevents = 0;
show_msg(MSGDEBUG, "Checking requests for socks enabled socket %d\n",
conn->sockid);
conn->selectevents |= (writefds ? (FD_ISSET(conn->sockid, writefds) ? WRITE : 0) : 0);
conn->selectevents |= (readfds ? (FD_ISSET(conn->sockid, readfds) ? READ : 0) : 0);
conn->selectevents |= (exceptfds ? (FD_ISSET(conn->sockid, exceptfds) ? EXCEPT : 0) : 0);
if (conn->selectevents) {
show_msg(MSGDEBUG, "Socket %d was set for events\n", conn->sockid);
monitoring = 1;
}
}
if (!monitoring)
return(realselect(n, readfds, writefds, exceptfds, timeout));
/* This is our select loop. In it we repeatedly call select(). We
* pass select the same fdsets as provided by the caller except we
* modify the fdsets for the sockets we're managing to get events
* we're interested in (while negotiating with the socks server). When
* events we're interested in happen we go off and process the result
* ourselves, without returning the events to the caller. The loop
* ends when an event which isn't one we need to handle occurs or
* the select times out */
do {
/* Copy the clients fd events, we'll change them as we wish */
if (readfds)
memcpy(&myreadfds, readfds, sizeof(myreadfds));
else
FD_ZERO(&myreadfds);
if (writefds)
memcpy(&mywritefds, writefds, sizeof(mywritefds));
else
FD_ZERO(&mywritefds);
if (exceptfds)
memcpy(&myexceptfds, exceptfds, sizeof(myexceptfds));
else
FD_ZERO(&myexceptfds);
/* Now enable our sockets for the events WE want to hear about */
for (conn = requests; conn != NULL; conn = conn->next) {
if ((conn->state == FAILED) || (conn->state == DONE) ||
(conn->selectevents == 0))
continue;
/* We always want to know about socket exceptions */
FD_SET(conn->sockid, &myexceptfds);
/* If we're waiting for a connect or to be able to send
* on a socket we want to get write events */
if ((conn->state == SENDING) || (conn->state == CONNECTING))
FD_SET(conn->sockid,&mywritefds);
else
FD_CLR(conn->sockid,&mywritefds);
/* If we're waiting to receive data we want to get
* read events */
if (conn->state == RECEIVING)
FD_SET(conn->sockid,&myreadfds);
else
FD_CLR(conn->sockid,&myreadfds);
}
nevents = realselect(n, &myreadfds, &mywritefds, &myexceptfds, timeout);
/* If there were no events we must have timed out or had an error */
if (nevents <= 0)
break;
/* Loop through all the sockets we're monitoring and see if
* any of them have had events */
for (conn = requests; conn != NULL; conn = nextconn) {
nextconn = conn->next;
if ((conn->state == FAILED) || (conn->state == DONE))
continue;
show_msg(MSGDEBUG, "Checking socket %d for events\n", conn->sockid);
/* Clear all the events on the socket (if any), we'll reset
* any that are necessary later. */
setevents = 0;
if (FD_ISSET(conn->sockid, &mywritefds)) {
nevents--;
setevents |= WRITE;
show_msg(MSGDEBUG, "Socket had write event\n");
FD_CLR(conn->sockid, &mywritefds);
}
if (FD_ISSET(conn->sockid, &myreadfds)) {
nevents--;
setevents |= READ;
show_msg(MSGDEBUG, "Socket had write event\n");
FD_CLR(conn->sockid, &myreadfds);
}
if (FD_ISSET(conn->sockid, &myexceptfds)) {
nevents--;
setevents |= EXCEPT;
show_msg(MSGDEBUG, "Socket had except event\n");
FD_CLR(conn->sockid, &myexceptfds);
}
if (!setevents) {
show_msg(MSGDEBUG, "No events on socket %d\n", conn->sockid);
continue;
}
if (setevents & EXCEPT) {
conn->state = FAILED;
} else {
rc = handle_request(conn);
}
/* If the connection hasn't failed or completed there is nothing
* to report to the client */
if ((conn->state != FAILED) &&
(conn->state != DONE))
continue;
/* Ok, the connection is completed, for good or for bad. We now
* hand back the relevant events to the caller. We don't delete the
* connection though since the caller should call connect() to
* check the status, we delete it then */
if (conn->state == FAILED) {
/* Damn, the connection failed. Whatever the events the socket
* was selected for we flag */
if (conn->selectevents & EXCEPT) {
FD_SET(conn->sockid, &myexceptfds);
nevents++;
}
if (conn->selectevents & READ) {
FD_SET(conn->sockid, &myreadfds);
nevents++;
}
if (conn->selectevents & WRITE) {
FD_SET(conn->sockid, &mywritefds);
nevents++;
}
/* We should use setsockopt to set the SO_ERROR errno for this
* socket, but this isn't allowed for some silly reason which
* leaves us a bit hamstrung.
* We don't delete the request so that hopefully we can
* return the error on the socket if they call connect() on it */
} else {
/* The connection is done, if the client selected for
* writing we can go ahead and signal that now (since the socket must
* be ready for writing), otherwise we'll just let the select loop
* come around again (since we can't flag it for read, we don't know
* if there is any data to be read and can't be bothered checking) */
if (conn->selectevents & WRITE) {
FD_SET(conn->sockid, &mywritefds);
nevents++;
}
}
}
} while (nevents == 0);
show_msg(MSGDEBUG, "Finished intercepting select(), %d events\n", nevents);
/* Now copy our event blocks back to the client blocks */
if (readfds)
memcpy(readfds, &myreadfds, sizeof(myreadfds));
if (writefds)
memcpy(writefds, &mywritefds, sizeof(mywritefds));
if (exceptfds)
memcpy(exceptfds, &myexceptfds, sizeof(myexceptfds));
return(nevents);
}
int poll(POLL_SIGNATURE) {
int nevents = 0;
int rc = 0, i;
int setevents = 0;
int monitoring = 0;
struct connreq *conn, *nextconn;
/* If we're not currently managing any requests we can just
* leave here */
if (!requests)
return(realpoll(fds, nfds, timeout));
get_environment();
show_msg(MSGDEBUG, "Intercepted call to poll with %d fds, "
"0x%08x timeout %d\n", nfds, fds, timeout);
for (conn = requests; conn != NULL; conn = conn->next)
conn->selectevents = 0;
/* Record what events on our sockets the caller was interested
* in */
for (i = 0; i < nfds; i++) {
if (!(conn = find_socks_request(fds[i].fd, 0)))
continue;
show_msg(MSGDEBUG, "Have event checks for socks enabled socket %d\n",
conn->sockid);
conn->selectevents = fds[i].events;
monitoring = 1;
}
if (!monitoring)
return(realpoll(fds, nfds, timeout));
/* This is our poll loop. In it we repeatedly call poll(). We
* pass select the same event list as provided by the caller except we
* modify the events for the sockets we're managing to get events
* we're interested in (while negotiating with the socks server). When
* events we're interested in happen we go off and process the result
* ourselves, without returning the events to the caller. The loop
* ends when an event which isn't one we need to handle occurs or
* the poll times out */
do {
/* Enable our sockets for the events WE want to hear about */
for (i = 0; i < nfds; i++) {
if (!(conn = find_socks_request(fds[i].fd, 0)))
continue;
/* We always want to know about socket exceptions but they're
* always returned (i.e they don't need to be in the list of
* wanted events to be returned by the kernel */
fds[i].events = 0;
/* If we're waiting for a connect or to be able to send
* on a socket we want to get write events */
if ((conn->state == SENDING) || (conn->state == CONNECTING))
fds[i].events |= POLLOUT;
/* If we're waiting to receive data we want to get
* read events */
if (conn->state == RECEIVING)
fds[i].events |= POLLIN;
}
nevents = realpoll(fds, nfds, timeout);
/* If there were no events we must have timed out or had an error */
if (nevents <= 0)
break;
/* Loop through all the sockets we're monitoring and see if
* any of them have had events */
for (conn = requests; conn != NULL; conn = nextconn) {
nextconn = conn->next;
if ((conn->state == FAILED) || (conn->state == DONE))
continue;
/* Find the socket in the poll list */
for (i = 0; ((i < nfds) && (fds[i].fd != conn->sockid)); i++)
/* Empty Loop */;
if (i == nfds)
continue;
show_msg(MSGDEBUG, "Checking socket %d for events\n", conn->sockid);
if (!fds[i].revents) {
show_msg(MSGDEBUG, "No events on socket\n");
continue;
}
/* Clear any read or write events on the socket, we'll reset
* any that are necessary later. */
setevents = fds[i].revents;
if (setevents & POLLIN) {
show_msg(MSGDEBUG, "Socket had read event\n");
fds[i].revents &= ~POLLIN;
nevents--;
}
if (setevents & POLLOUT) {
show_msg(MSGDEBUG, "Socket had write event\n");
fds[i].revents &= ~POLLOUT;
nevents--;
}
if (setevents & (POLLERR | POLLNVAL | POLLHUP))
show_msg(MSGDEBUG, "Socket had error event\n");
/* Now handle this event */
if (setevents & (POLLERR | POLLNVAL | POLLHUP)) {
conn->state = FAILED;
} else {
rc = handle_request(conn);
}
/* If the connection hasn't failed or completed there is nothing
* to report to the client */
if ((conn->state != FAILED) &&
(conn->state != DONE))
continue;
/* Ok, the connection is completed, for good or for bad. We now
* hand back the relevant events to the caller. We don't delete the
* connection though since the caller should call connect() to
* check the status, we delete it then */
if (conn->state == FAILED) {
/* Damn, the connection failed. Just copy back the error events
* from the poll call, error events are always valid even if not
* requested by the client */
/* We should use setsockopt to set the SO_ERROR errno for this
* socket, but this isn't allowed for some silly reason which
* leaves us a bit hamstrung.
* We don't delete the request so that hopefully we can
* return the error on the socket if they call connect() on it */
} else {
/* The connection is done, if the client polled for
* writing we can go ahead and signal that now (since the socket must
* be ready for writing), otherwise we'll just let the select loop
* come around again (since we can't flag it for read, we don't know
* if there is any data to be read and can't be bothered checking) */
if (conn->selectevents & WRITE) {
setevents |= POLLOUT;
nevents++;
}
}
}
} while (nevents == 0);
show_msg(MSGDEBUG, "Finished intercepting poll(), %d events\n", nevents);
/* Now restore the events polled in each of the blocks */
for (i = 0; i < nfds; i++) {
if (!(conn = find_socks_request(fds[i].fd, 1)))
continue;
fds[i].events = conn->selectevents;
}
return(nevents);
}
int close(CLOSE_SIGNATURE) {
int rc;
struct connreq *conn;
if (realclose == NULL) {
show_msg(MSGERR, "Unresolved symbol: close\n");
return(-1);
}
show_msg(MSGDEBUG, "Call to close(%d)\n", fd);
rc = realclose(fd);
/* If we have this fd in our request handling list we
* remove it now */
if ((conn = find_socks_request(fd, 1))) {
show_msg(MSGDEBUG, "Call to close() received on file descriptor "
"%d which is a connection request of status %d\n",
conn->sockid, conn->state);
kill_socks_request(conn);
}
return(rc);
}
static struct connreq *new_socks_request(int sockid, struct sockaddr_in *connaddr,
struct sockaddr_in *serveraddr,
struct serverent *path) {
struct connreq *newconn;
if ((newconn = malloc(sizeof(*newconn))) == NULL) {
/* Could not malloc, we're stuffed */
show_msg(MSGERR, "Could not allocate memory for new socks request\n");
return(NULL);
}
/* Add this connection to be proxied to the list */
memset(newconn, 0x0, sizeof(*newconn));
newconn->sockid = sockid;
newconn->state = UNSTARTED;
newconn->path = path;
memcpy(&(newconn->connaddr), connaddr, sizeof(newconn->connaddr));
memcpy(&(newconn->serveraddr), serveraddr, sizeof(newconn->serveraddr));
newconn->next = requests;
requests = newconn;
return(newconn);
}
static void kill_socks_request(struct connreq *conn) {
struct connreq *connnode;
if (requests == conn)
requests = conn->next;
else {
for (connnode = requests; connnode != NULL; connnode = connnode->next) {
if (connnode->next == conn) {
connnode->next = conn->next;
break;
}
}
}
free(conn);
}
static struct connreq *find_socks_request(int sockid, int includefinished) {
struct connreq *connnode;
for (connnode = requests; connnode != NULL; connnode = connnode->next) {
if (connnode->sockid == sockid) {
if (((connnode->state == FAILED) || (connnode->state == DONE)) &&
!includefinished)
break;
else
return(connnode);
}
}
return(NULL);
}
static int handle_request(struct connreq *conn) {
int rc = 0;
int i = 0;
show_msg(MSGDEBUG, "Beginning handle loop for socket %d\n", conn->sockid);
while ((rc == 0) &&
(conn->state != FAILED) &&
(conn->state != DONE) &&
(i++ < 20)) {
show_msg(MSGDEBUG, "In request handle loop for socket %d, "
"current state of request is %d\n", conn->sockid,
conn->state);
switch(conn->state) {
case UNSTARTED:
case CONNECTING:
rc = connect_server(conn);
break;
case CONNECTED:
rc = send_socks_request(conn);
break;
case SENDING:
rc = send_buffer(conn);
break;
case RECEIVING:
rc = recv_buffer(conn);
break;
case SENTV4REQ:
show_msg(MSGDEBUG, "Receiving reply to SOCKS V4 connect request\n");
conn->datalen = sizeof(struct sockrep);
conn->datadone = 0;
conn->state = RECEIVING;
conn->nextstate = GOTV4REQ;
break;
case GOTV4REQ:
rc = read_socksv4_req(conn);
break;
case SENTV5METHOD:
show_msg(MSGDEBUG, "Receiving reply to SOCKS V5 method negotiation\n");
conn->datalen = 2;
conn->datadone = 0;
conn->state = RECEIVING;
conn->nextstate = GOTV5METHOD;
break;
case GOTV5METHOD:
rc = read_socksv5_method(conn);
break;
case SENTV5AUTH:
show_msg(MSGDEBUG, "Receiving reply to SOCKS V5 authentication negotiation\n");
conn->datalen = 2;
conn->datadone = 0;
conn->state = RECEIVING;
conn->nextstate = GOTV5AUTH;
break;
case GOTV5AUTH:
rc = read_socksv5_auth(conn);
break;
case SENTV5CONNECT:
show_msg(MSGDEBUG, "Receiving reply to SOCKS V5 connect request\n");
conn->datalen = 10;
conn->datadone = 0;
conn->state = RECEIVING;
conn->nextstate = GOTV5CONNECT;
break;
case GOTV5CONNECT:
rc = read_socksv5_connect(conn);
break;
}
conn->err = errno;
}
if (i == 20)
show_msg(MSGERR, "Ooops, state loop while handling request %d\n",
conn->sockid);
show_msg(MSGDEBUG, "Handle loop completed for socket %d in state %d, "
"returning %d\n", conn->sockid, conn->state, rc);
return(rc);
}
static int connect_server(struct connreq *conn) {
int rc;
/* Connect this socket to the socks server */
show_msg(MSGDEBUG, "Connecting to %s port %d\n",
inet_ntoa(conn->serveraddr.sin_addr), ntohs(conn->serveraddr.sin_port));
rc = realconnect(conn->sockid, (CONNECT_SOCKARG) &(conn->serveraddr),
sizeof(conn->serveraddr));
show_msg(MSGDEBUG, "Connect returned %d, errno is %d\n", rc, errno);
if (rc) {
if (errno != EINPROGRESS) {
show_msg(MSGERR, "Error %d attempting to connect to SOCKS "
"server (%s)\n", errno, strerror(errno));
conn->state = FAILED;
} else {
show_msg(MSGDEBUG, "Connection in progress\n");
conn->state = CONNECTING;
}
} else {
show_msg(MSGDEBUG, "Socket %d connected to SOCKS server\n", conn->sockid);
conn->state = CONNECTED;
}
return((rc ? errno : 0));
}
static int send_socks_request(struct connreq *conn) {
int rc = 0;
if (conn->path->type == 4)
rc = send_socksv4_request(conn);
else
rc = send_socksv5_method(conn);
return(rc);
}
static int send_socksv4_request(struct connreq *conn) {
struct passwd *user;
struct sockreq *thisreq;
/* Determine the current username */
user = getpwuid(getuid());
thisreq = (struct sockreq *) conn->buffer;
/* Check the buffer has enough space for the request */
/* and the user name */
conn->datalen = sizeof(struct sockreq) +
(user == NULL ? 0 : strlen(user->pw_name)) + 1;
if (sizeof(conn->buffer) < conn->datalen) {
show_msg(MSGERR, "The SOCKS username is too long");
conn->state = FAILED;
return(ECONNREFUSED);
}
/* Create the request */
thisreq->version = 4;
thisreq->command = 1;
thisreq->dstport = conn->connaddr.sin_port;
thisreq->dstip = conn->connaddr.sin_addr.s_addr;
/* Copy the username */
strcpy((char *) thisreq + sizeof(struct sockreq),
(user == NULL ? "" : user->pw_name));
conn->datadone = 0;
conn->state = SENDING;
conn->nextstate = SENTV4REQ;
return(0);
}
static int send_socksv5_method(struct connreq *conn) {
char verstring[] = { 0x05, /* Version 5 SOCKS */
0x02, /* No. Methods */
0x00, /* Null Auth */
0x02 }; /* User/Pass Auth */
show_msg(MSGDEBUG, "Constructing V5 method negotiation\n");
conn->state = SENDING;
conn->nextstate = SENTV5METHOD;
memcpy(conn->buffer, verstring, sizeof(verstring));
conn->datalen = sizeof(verstring);
conn->datadone = 0;
return(0);
}
static int send_socksv5_connect(struct connreq *conn) {
char constring[] = { 0x05, /* Version 5 SOCKS */
0x01, /* Connect request */
0x00, /* Reserved */
0x01 }; /* IP Version 4 */
show_msg(MSGDEBUG, "Constructing V5 connect request\n");
conn->datadone = 0;
conn->state = SENDING;
conn->nextstate = SENTV5CONNECT;
memcpy(conn->buffer, constring, sizeof(constring));
conn->datalen = sizeof(constring);
memcpy(&conn->buffer[conn->datalen], &(conn->connaddr.sin_addr.s_addr),
sizeof(conn->connaddr.sin_addr.s_addr));
conn->datalen += sizeof(conn->connaddr.sin_addr.s_addr);
memcpy(&conn->buffer[conn->datalen], &(conn->connaddr.sin_port), sizeof(conn->connaddr.sin_port));
conn->datalen += sizeof(conn->connaddr.sin_port);
return(0);
}
static int send_buffer(struct connreq *conn) {
int rc = 0;
show_msg(MSGDEBUG, "Writing to server (sending %d bytes)\n", conn->datalen);
while ((rc == 0) && (conn->datadone != conn->datalen)) {
rc = send(conn->sockid, conn->buffer + conn->datadone,
conn->datalen - conn->datadone, 0);
if (rc > 0) {
conn->datadone += rc;
rc = 0;
} else {
if (errno != EWOULDBLOCK)
show_msg(MSGDEBUG, "Write failed, %s\n", strerror(errno));
rc = errno;
}
}
if (conn->datadone == conn->datalen)
conn->state = conn->nextstate;
show_msg(MSGDEBUG, "Sent %d bytes of %d bytes in buffer, return code is %d\n",
conn->datadone, conn->datalen, rc);
return(rc);
}
static int recv_buffer(struct connreq *conn) {
int rc = 0;
show_msg(MSGDEBUG, "Reading from server (expecting %d bytes)\n", conn->datalen);
while ((rc == 0) && (conn->datadone != conn->datalen)) {
rc = recv(conn->sockid, conn->buffer + conn->datadone,
conn->datalen - conn->datadone, 0);
if (rc > 0) {
conn->datadone += rc;
rc = 0;
} else {
if (errno != EWOULDBLOCK)
show_msg(MSGDEBUG, "Read failed, %s\n", strerror(errno));
rc = errno;
}
}
if (conn->datadone == conn->datalen)
conn->state = conn->nextstate;
show_msg(MSGDEBUG, "Received %d bytes of %d bytes expected, return code is %d\n",
conn->datadone, conn->datalen, rc);
return(rc);
}
static int read_socksv5_method(struct connreq *conn) {
struct passwd *nixuser;
char *uname, *upass;
/* See if we offered an acceptable method */
if (conn->buffer[1] == '\xff') {
show_msg(MSGERR, "SOCKS V5 server refused authentication methods\n");
conn->state = FAILED;
return(ECONNREFUSED);
}
/* If the socks server chose username/password authentication */
/* (method 2) then do that */
if ((unsigned short int) conn->buffer[1] == 2) {
show_msg(MSGDEBUG, "SOCKS V5 server chose username/password authentication\n");
/* Determine the current *nix username */
nixuser = getpwuid(getuid());
if (((uname = conn->path->defuser) == NULL) &&
((uname = getenv("TSOCKS_USERNAME")) == NULL) &&
((uname = (nixuser == NULL ? NULL : nixuser->pw_name)) == NULL)) {
show_msg(MSGERR, "Could not get SOCKS username from "
"local passwd file, tsocks.conf "
"or $TSOCKS_USERNAME to authenticate "
"with");
conn->state = FAILED;
return(ECONNREFUSED);
}
if (((upass = getenv("TSOCKS_PASSWORD")) == NULL) &&
((upass = conn->path->defpass) == NULL)) {
show_msg(MSGERR, "Need a password in tsocks.conf or "
"$TSOCKS_PASSWORD to authenticate with");
conn->state = FAILED;
return(ECONNREFUSED);
}
/* Check that the username / pass specified will */
/* fit into the buffer */
if ((3 + strlen(uname) + strlen(upass)) >= sizeof(conn->buffer)) {
show_msg(MSGERR, "The supplied socks username or "
"password is too long");
conn->state = FAILED;
return(ECONNREFUSED);
}
conn->datalen = 0;
conn->buffer[conn->datalen] = '\x01';
conn->datalen++;
conn->buffer[conn->datalen] = (int8_t) strlen(uname);
conn->datalen++;
memcpy(&(conn->buffer[conn->datalen]), uname, strlen(uname));
conn->datalen = conn->datalen + strlen(uname);
conn->buffer[conn->datalen] = (int8_t) strlen(upass);
conn->datalen++;
memcpy(&(conn->buffer[conn->datalen]), upass, strlen(upass));
conn->datalen = conn->datalen + strlen(upass);
conn->state = SENDING;
conn->nextstate = SENTV5AUTH;
conn->datadone = 0;
} else
return(send_socksv5_connect(conn));
return(0);
}
static int read_socksv5_auth(struct connreq *conn) {
if (conn->buffer[1] != '\x00') {
show_msg(MSGERR, "SOCKS authentication failed, check username and password\n");
conn->state = FAILED;
return(ECONNREFUSED);
}
/* Ok, we authenticated ok, send the connection request */
return(send_socksv5_connect(conn));
}
static int read_socksv5_connect(struct connreq *conn) {
/* See if the connection succeeded */
if (conn->buffer[1] != '\x00') {
show_msg(MSGERR, "SOCKS V5 connect failed: ");
conn->state = FAILED;
switch ((int8_t) conn->buffer[1]) {
case 1:
show_msg(MSGERR, "General SOCKS server failure\n");
return(ECONNABORTED);
case 2:
show_msg(MSGERR, "Connection denied by rule\n");
return(ECONNABORTED);
case 3:
show_msg(MSGERR, "Network unreachable\n");
return(ENETUNREACH);
case 4:
show_msg(MSGERR, "Host unreachable\n");
return(EHOSTUNREACH);
case 5:
show_msg(MSGERR, "Connection refused\n");
return(ECONNREFUSED);
case 6:
show_msg(MSGERR, "TTL Expired\n");
return(ETIMEDOUT);
case 7:
show_msg(MSGERR, "Command not supported\n");
return(ECONNABORTED);
case 8:
show_msg(MSGERR, "Address type not supported\n");
return(ECONNABORTED);
default:
show_msg(MSGERR, "Unknown error\n");
return(ECONNABORTED);
}
}
conn->state = DONE;
return(0);
}
static int read_socksv4_req(struct connreq *conn) {
struct sockrep *thisrep;
thisrep = (struct sockrep *) conn->buffer;
if (thisrep->result != 90) {
show_msg(MSGERR, "SOCKS V4 connect rejected:\n");
conn->state = FAILED;
switch(thisrep->result) {
case 91:
show_msg(MSGERR, "SOCKS server refused connection\n");
return(ECONNREFUSED);
case 92:
show_msg(MSGERR, "SOCKS server refused connection "
"because of failed connect to identd "
"on this machine\n");
return(ECONNREFUSED);
case 93:
show_msg(MSGERR, "SOCKS server refused connection "
"because identd and this library "
"reported different user-ids\n");
return(ECONNREFUSED);
default:
show_msg(MSGERR, "Unknown reason\n");
return(ECONNREFUSED);
}
}
conn->state = DONE;
return(0);
}
#ifdef USE_SOCKS_DNS
int res_init(void) {
int rc;
if (realresinit == NULL) {
show_msg(MSGERR, "Unresolved symbol: res_init\n");
return(-1);
}
/* Call normal res_init */
rc = realresinit();
/* Force using TCP protocol for DNS queries */
_res.options |= RES_USEVC;
return(rc);
}
#endif
#if 0
/* Get the flags of the socket, (incase its non blocking */
if ((sockflags = fcntl(sockid, F_GETFL)) == -1) {
sockflags = 0;
}
/* If the flags show the socket as blocking, set it to */
/* blocking for our connection to the socks server */
if ((sockflags & O_NONBLOCK) != 0) {
fcntl(sockid, F_SETFL, sockflags & (~(O_NONBLOCK)));
}
#endif
#if 0
/* If the socket was in non blocking mode, restore that */
if ((sockflags & O_NONBLOCK) != 0) {
fcntl(sockid, F_SETFL, sockflags);
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1