socat/xio-socks5.c
2009-04-03 11:30:01 +02:00

505 lines
15 KiB
C

/* source: xio-socks5.c */
/* Copyright Gerhard Rieger 2004-2009 */
/* Published under the GNU General Public License V.2, see file COPYING */
/* this file contains the source for opening addresses of socks5 type */
#include "xiosysincludes.h"
#include "xioopen.h"
#include "xio-socket.h"
#include "xio-ipapp.h"
#include "xio-socks5.h"
#if WITH_SOCKS5
#define SOCKSPORT "1080"
#define SOCKS5_MAXLEN 512
static int
xioopen_socks5_client(int argc, const char *argv[],
struct opt *opts, int xioflags, xiofile_t *xfd,
unsigned groups, int dummy1, int dummy2,
int dummy3);
const struct optdesc opt_socks5_username = { "socks5-username", "socks5user", OPT_SOCKS5_USERNAME, GROUP_SOCKS5, PH_LATE, TYPE_STRING, OFUNC_SPEC };
const struct optdesc opt_socks5_password = { "socks5-password", "socks5pass", OPT_SOCKS5_PASSWORD, GROUP_SOCKS5, PH_LATE, TYPE_STRING, OFUNC_SPEC };
static const struct xioaddr_inter_desc xiointer_socks5_client = { XIOADDR_PROT, "socks5", 2, XIOBIT_ALL, GROUP_SOCKS5, XIOSHUT_DOWN, XIOCLOSE_CLOSE, xioopen_socks5_client, 0, 0, 0, XIOBIT_RDWR HELP(":<host>:<port>") };
const union xioaddr_desc *xioaddrs_socks5_client[] = {
(union xioaddr_desc *)&xiointer_socks5_client, NULL };
/* read until buflen bytes received or EOF */
/* returns STAT_OK, STAT_RETRYLATER, or STAT_NOTRETRY */
static int xiosocks5_recvbytes(struct single *xfd,
uint8_t *buff, size_t buflen, int level) {
size_t bytes;
ssize_t result;
bytes = 0;
while (bytes >= 0) { /* loop over answer chunks until complete or error */
/* receive socks answer */
Debug("waiting for data from peer");
do {
result = Read(xfd->rfd, buff+bytes, buflen-bytes);
} while (result < 0 && errno == EINTR);
if (result < 0) {
Msg4(level, "read(%d, %p, "F_Zu"): %s",
xfd->rfd, buff+bytes, buflen-bytes,
strerror(errno));
if (Close(xfd->rfd) < 0) {
Warn2("close(%d): %s", xfd->rfd, strerror(errno));
}
if (Close(xfd->wfd) < 0) {
Warn2("close(%d): %s", xfd->wfd, strerror(errno));
}
return STAT_RETRYLATER;
}
if (result == 0) {
Msg(level, "read(): EOF during read of socks reply");
if (Close(xfd->rfd) < 0) {
Warn2("close(%d): %s", xfd->rfd, strerror(errno));
}
if (Close(xfd->wfd) < 0) {
Warn2("close(%d): %s", xfd->wfd, strerror(errno));
}
return STAT_RETRYLATER;
}
bytes += result;
if (bytes == buflen) {
Debug1("received all "F_Zd" bytes", bytes);
break;
}
Debug2("received "F_Zd" bytes, waiting for "F_Zu" more bytes",
result, buflen-bytes);
}
if (result <= 0) { /* we had a problem while reading socks answer */
return STAT_RETRYLATER; /* retry complete open cycle */
}
return STAT_OK;
}
static int xioopen_socks5_client(int argc, const char *argv[],
struct opt *opts, int xioflags,
xiofile_t *xxfd,
unsigned groups, int dummy1, int dummy2,
int dummy3) {
/* we expect the form: host:port */
xiosingle_t *xfd = &xxfd->stream;
const char *targetname, *targetservice;
int level;
int result;
if (argc != 3) {
Error("SOCKS5 syntax: socks5-connect:<host>:<port>"); /*! UDP? */
return STAT_NORETRY;
}
targetname = argv[1];
targetservice = argv[2];
applyopts(-1, opts, PH_INIT);
applyopts_single(xfd, opts, PH_INIT);
#if 0
result = _xioopen_socks5_prepare(a3, opts, &socksport, sockhead, &buflen);
if (result != STAT_OK) return result;
#endif
if (xfd->rfd < 0) {
Error("socks5 must be used as embedded address");
return -1;
}
Notice2("opening connection to %s:%s using socks5",
targetname, targetservice);
do { /* loop over failed connect and socks-request attempts */
#if WITH_RETRY
if (xfd->forever || xfd->retry) {
level = E_INFO;
} else
#endif /* WITH_RETRY */
level = E_ERROR;
#if 0
/* we try to resolve the target address _before_ connecting to the socks
server: this avoids unnecessary socks connects and timeouts */
result =
_xioopen_socks5_connect0(xfd, targetname, sockhead, opts,
(ssize_t *)&buflen, level);
switch (result) {
case STAT_OK: break;
#if WITH_RETRY
case STAT_RETRYLATER:
case STAT_RETRYNOW:
if (xfd->forever || xfd->retry--) {
if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL);
continue;
}
#endif /* WITH_RETRY */
default:
return result;
}
#endif
#if 0
if (xfd->fd1 < 0) {
/* this cannot fork because we retrieved fork option above */
result =
_xioopen_connect (xfd,
needbind?(struct sockaddr *)us:NULL, sizeof(*us),
(struct sockaddr *)them, sizeof(struct sockaddr_in),
opts, PF_INET, socktype, IPPROTO_TCP, lowport, level);
switch (result) {
case STAT_OK: break;
#if WITH_RETRY
case STAT_RETRYLATER:
case STAT_RETRYNOW:
if (xfd->forever || xfd->retry--) {
if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL);
continue;
}
#endif /* WITH_RETRY */
default:
return result;
}
xfd->fd1 = xfd->wfd = xfd->fd;
} else
#endif
xfd->dtype = XIODATA_STREAM;
result =
_xioopen_socks5_connect(xfd, targetname, targetservice, opts, level);
switch (result) {
case STAT_OK: break;
#if WITH_RETRY
case STAT_RETRYLATER:
case STAT_RETRYNOW:
if (xfd->forever || xfd->retry--) {
if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL);
continue;
}
#endif /* WITH_RETRY */
default:
return result;
}
/*!*/
applyopts(xfd->rfd, opts, PH_ALL);
if ((result = _xio_openlate(xfd, opts)) < 0)
return result;
{
break;
}
} while (true); /* end of complete open loop - drop out on success */
return 0;
}
/* perform socks5 client dialog on existing FD.
Called within fork/retry loop, after connect() */
int _xioopen_socks5_connect(struct single *xfd,
const char *targetname, const char *targetservice,
struct opt *opts, int level) {
uint16_t targetport;
unsigned char sendbuff[SOCKS5_MAXLEN]; size_t sendlen;
unsigned char recvbuff[SOCKS5_MAXLEN];
struct socks5_method *sendmethod;
struct socks5_select *recvselect;
struct socks5_request *sendrequest;
struct socks5_reply *recvreply;
size_t namelen;
size_t addrlen;
size_t readpos;
char *emsg;
int result;
/* prepare */
targetport = parseport(targetservice, IPPROTO_TCP/*!*/);
/* just the simplest authentications */
sendmethod = (struct socks5_method *)sendbuff;
sendmethod->version = 5; /* protocol version */
sendmethod->nmethods = 2; /* number of proposed authentication types */
sendmethod->methods[0] = SOCKS5_METHOD_NOAUTH; /* no auth at all */
sendmethod->methods[1] = SOCKS5_METHOD_USERPASS; /* username/password */
/*sendmethod->methods[2] = SOCKS5_METHOD_AVENTAIL;*/ /* Aventail Connect */
sendlen = 2+sendmethod->nmethods;
/* send socks header (target addr+port, +auth) */
Info("sending socks5 identifier/method selection message");
do {
result = Write(xfd->wfd, sendmethod, sendlen);
} while (result < 0 && errno == EINTR);
if (result < 0) {
Msg4(level, "write(%d, %p, "F_Zu"): %s",
xfd->wfd, sendmethod, sendlen, strerror(errno));
if (Close(xfd->wfd) < 0) {
Warn2("close(%d): %s", xfd->wfd, strerror(errno));
}
if (Close(xfd->rfd) < 0) {
Warn2("close(%d): %s", xfd->rfd, strerror(errno));
}
return STAT_RETRYLATER; /* retry complete open cycle */
}
Info1("waiting for socks5 select reply ("F_Zu" bytes)", SOCKS5_SELECT_LENGTH);
if ((result =
xiosocks5_recvbytes(xfd, recvbuff, SOCKS5_SELECT_LENGTH, level)) !=
STAT_OK) {
/* we had a problem while reading socks answer */
/*! Close(); */
return result; /* ev. retry complete open cycle */
}
recvselect = (struct socks5_select *)recvbuff;
Info2("socks5 select: {%u, %u}", recvselect->version, recvselect->method);
if (recvselect->version != 5) {
Error1("socks5: server protocol version is %u",
recvbuff[0]);
}
if (recvselect->method == SOCKS5_METHOD_NONE) {
Error("socks5: server did not accept our authentication methods");
}
/*! check if selected methods is one of our proposals */
switch (recvselect->method) {
case SOCKS5_METHOD_NOAUTH:
break;
case SOCKS5_METHOD_USERPASS:
if (xio_socks5_username_password(level, opts, xfd) < 0) {
Error("username/password not accepted");
}
break;
default:
Error("socks5 select: unimplemented authentication method selected");
break;
}
sendrequest = (struct socks5_request *)sendbuff;
sendrequest->version = SOCKS5_VERSION;
sendrequest->command = SOCKS5_COMMAND_CONNECT;
sendrequest->reserved = 0;
sendrequest->addrtype = SOCKS5_ADDRTYPE_NAME;
switch (sendrequest->addrtype) {
case SOCKS5_ADDRTYPE_IPV4:
break;
case SOCKS5_ADDRTYPE_NAME:
if ((namelen = strlen(targetname)) > 255) {
Error1("socks5: target name is longer than 255 bytes: \"%s\"",
targetname);
}
sendrequest->destaddr[0] = strlen(targetname);
strncpy(sendrequest->destaddr+1, targetname, sizeof(sendbuff)-5);
break;
case SOCKS5_ADDRTYPE_IPV6:
break;
default: Fatal("socks5: undefined address type in socks request");
}
*((uint16_t *)&sendrequest->destaddr[strlen(targetname)+1]) = targetport;
sendlen = sizeof(struct socks5_request) + namelen + 2;
/* send socks request (target addr+port, +auth) */
Info("sending socks5 request selection");
do {
result = Write(xfd->wfd, sendrequest, sendlen);
} while (result < 0 && errno == EINTR);
if (result < 0) {
Msg4(level, "write(%d, %p, "F_Zu"): %s",
xfd->wfd, sendmethod, sendlen, strerror(errno));
if (Close(xfd->wfd) < 0) {
Warn2("close(%d): %s", xfd->wfd, strerror(errno));
}
if (Close(xfd->rfd) < 0) {
Warn2("close(%d): %s", xfd->rfd, strerror(errno));
}
return STAT_RETRYLATER; /* retry complete open cycle */
}
Info("waiting for socks5 reply");
recvreply = (struct socks5_reply *)recvbuff;
if ((result =
xiosocks5_recvbytes(xfd, recvbuff, SOCKS5_REPLY_LENGTH1, level))
!= STAT_OK) {
/*! close... */
return result;
}
if (recvreply->version != SOCKS5_VERSION) {
Error1("socks5: version of reply is %d", recvreply->version);
}
switch (recvreply->reply) {
case SOCKS5_REPLY_SUCCESS: emsg = NULL; break;
case SOCKS5_REPLY_FAILURE: emsg = "general socks server failure"; break;
case SOCKS5_REPLY_DENIED: emsg = "connection not allowed by ruleset"; break;
case SOCKS5_REPLY_NETUNREACH: emsg = "network unreachable"; break;
case SOCKS5_REPLY_HOSTUNREACH: emsg = "host unreachable"; break;
case SOCKS5_REPLY_REFUSED: emsg = "connection refused"; break;
case SOCKS5_REPLY_TTLEXPIRED: emsg = "TTL expired"; break;
case SOCKS5_REPLY_CMDUNSUPP: emsg = "command not supported"; break;
case SOCKS5_REPLY_ADDRUNSUPP: emsg = "address type not supported"; break;
default: emsg = "undefined error code"; break;
}
if (recvreply->reply != SOCKS5_REPLY_SUCCESS) {
Error1("socks5 reply: %s", emsg);
}
switch (recvreply->addrtype) {
case SOCKS5_ADDRTYPE_IPV4:
addrlen = 4; /*!*/
if ((result =
xiosocks5_recvbytes(xfd, recvbuff+SOCKS5_REPLY_LENGTH1, addrlen,
level))
!= STAT_OK) {
/*! close... */
return result;
}
readpos = sizeof(recvreply)-1+4;
break;
case SOCKS5_ADDRTYPE_NAME:
/* read 1 byte containing domain name length */
if ((result =
xiosocks5_recvbytes(xfd, recvbuff+SOCKS5_REPLY_LENGTH1, 1, level))
!= STAT_OK) {
/*! close... */
return result;
}
/* read domain name */
if ((result =
xiosocks5_recvbytes(xfd, recvbuff+SOCKS5_REPLY_LENGTH1+1,
recvreply->destaddr[0], level))
!= STAT_OK) {
/*! close... */
return result;
}
readpos = sizeof(recvreply)+recvreply->destaddr[0];
addrlen = recvreply->destaddr[0]+1;
break;
case SOCKS5_ADDRTYPE_IPV6:
addrlen = 16; /*!*/
if ((result =
xiosocks5_recvbytes(xfd, recvbuff+SOCKS5_REPLY_LENGTH1, addrlen,
level))
== EOF) {
/*! close... */
return result;
}
readpos = sizeof(recvreply)-1+4;
break;
default: Error("socks5: undefined address type in answer");
addrlen = 0;
readpos = sizeof(recvreply)-1;
}
if ((result =
xiosocks5_recvbytes(xfd, recvbuff+readpos, 2, level))
!= STAT_OK) {
/*! close... */
return result;
}
/*! Infos(...); */
return STAT_OK;
}
int xio_socks5_username_password(int level, struct opt *opts,
struct single *xfd) {
/*!!! */
unsigned char sendbuff[513];
unsigned char *pos;
char *username = NULL;
char *password = NULL;
unsigned char recvbuff[2];
struct socks5_userpass_reply *reply;
int result;
retropt_string(opts, OPT_SOCKS5_USERNAME, (char **)&username);
if (username == NULL) {
Error("socks5: username required");
return STAT_NORETRY;
}
retropt_string(opts, OPT_SOCKS5_PASSWORD, (char **)&password);
if (password == NULL) {
Error("socks5: password required");
return STAT_NORETRY;
}
pos = sendbuff;
*pos++ = SOCKS5_USERPASS_VERSION;
*pos++ = strlen(username);
strncpy(pos, username, 255);
pos += strlen(username);
*pos++ = strlen(password);
strncpy(pos, password, 255);
pos += strlen(password);
result =
xio_socks5_dialog(level, xfd, sendbuff, pos-sendbuff,
recvbuff, 2,
"username/password authentication");
if (result != STAT_OK) {
Msg(level, "socks5 username/password dialog failed");
return result;
}
Info("socks5 username/password authentication succeeded");
reply = (struct socks5_userpass_reply *)recvbuff;
if (reply->version != SOCKS5_USERPASS_VERSION) {
Msg(level, "socks5 username/password authentication version mismatch");
return STAT_NORETRY;
}
if (reply->status != SOCKS5_REPLY_SUCCESS) {
Msg(level, "socks5 username/password authentication failure");
return STAT_RETRYLATER;
}
return STAT_OK;
}
int xio_socks5_dialog(int level, struct single *xfd,
unsigned char *sendbuff, size_t sendlen,
unsigned char *recvbuff, size_t recvlen,
const char *descr /* identifier/method selection */) {
int result;
/* send socks header (target addr+port, +auth) */
Info1("sending socks5 %s message", descr);
do {
result = Write(xfd->wfd, sendbuff, sendlen);
} while (result < 0 && errno == EINTR);
if (result < 0) {
Msg4(level, "write(%d, %p, "F_Zu"): %s",
xfd->wfd, sendbuff, sendlen, strerror(errno));
if (Close(xfd->wfd) < 0) {
Warn2("close(%d): %s", xfd->wfd, strerror(errno));
}
if (Close(xfd->rfd) < 0) {
Warn2("close(%d): %s", xfd->rfd, strerror(errno));
}
return STAT_RETRYLATER; /* retry complete open cycle */
}
Info2("waiting for socks5 %s reply ("F_Zu" bytes)",
descr, recvlen);
if ((result =
xiosocks5_recvbytes(xfd, recvbuff, recvlen, level))
== EOF) {
/* we had a problem while reading socks answer */
/*! Close(); */
return result; /* retry complete open cycle */
}
#if 0
replyversion = ((struct socks5_version *)recvbuff)->version;
Info1("socks5 server reply version: {%u}", replyversion);
if (replyversion != SOCKS5_VERSION) {
Error1("socks5: server protocol version is %u",
replyversion);
}
#endif
return 0;
}
#endif /* WITH_SOCKS5 */