/* source: xio-socks5.c */
/* Copyright Gerhard Rieger and contributors (see file CHANGES) */
/* Published under the GNU General Public License V.2, see file COPYING */

/* This file contains the source for opening addresses of socks5 type */

/*
* At the moment UDP ASSOCIATE is not supported, but CONNECT and BIND are.
* At the moment GSSAPI authentication method is not supported,
* which is technically not compliant with RFC1928.
*/

#include "xiosysincludes.h"

#if WITH_SOCKS5

#include "xioopen.h"
#include "xio-ascii.h"
#include "xio-socket.h"
#include "xio-ip.h"
#include "xio-ipapp.h"
#include "xio-socks.h" 	/* _xioopen_opt_socksport() */

#include "xio-socks5.h"


#define SOCKS5_VERSION 5

#define SOCKS5_MAX_REPLY_SIZE	(6 + 256)

#define SOCKS5_AUTH_NONE		0
#define SOCKS5_AUTH_USER_PASS		2

#define SOCKS5_AUTH_SUCCESS		0
#define SOCKS5_AUTH_NO_METH		0xff

#define SOCKS5_COMMAND_CONNECT		1
#define SOCKS5_COMMAND_BIND		2
#define SOCKS5_COMMAND_UDP_ASSOCIATE	3

#define SOCKS5_ATYPE_IPv4		1
#define SOCKS5_ATYPE_DOMAINNAME		3
#define SOCKS5_ATYPE_IPv6		4

#define SOCKS5_STATUS_SUCCESS				0
#define SOCKS5_STATUS_GENERAL_FAILURE			1
#define SOCKS5_STATUS_CONNECTION_NOT_ALLOWED		2
#define SOCKS5_STATUS_NETWORK_UNREACHABLE		3
#define SOCKS5_STATUS_HOST_UNREACHABLE			4
#define SOCKS5_STATUS_CONNECTION_REFUSED		5
#define SOCKS5_STATUS_TTL_EXPIRED			6
#define SOCKS5_STATUS_COMMAND_NOT_SUPPORTED		7
#define SOCKS5_STATUS_ADDRESS_TYPE_NOT_SUPPORTED	8

static int xioopen_socks5(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, const struct addrdesc *addrdesc);

const struct addrdesc xioaddr_socks5_connect = { "SOCKS5-CONNECT", 1+XIO_RDWR, xioopen_socks5, GROUP_FD|GROUP_SOCKET|GROUP_SOCK_IP4|GROUP_SOCK_IP6|GROUP_IP_TCP|GROUP_IP_SOCKS|GROUP_CHILD|GROUP_RETRY, SOCKS5_COMMAND_CONNECT, 0, 0 HELP(":<socks-server>[:<socks-port>]:<target-host>:<target-port>") };

const struct addrdesc xioaddr_socks5_listen  = { "SOCKS5-LISTEN",  1+XIO_RDWR, xioopen_socks5, GROUP_FD|GROUP_SOCKET|GROUP_SOCK_IP4|GROUP_SOCK_IP6|GROUP_IP_TCP|GROUP_IP_SOCKS|GROUP_CHILD|GROUP_RETRY, SOCKS5_COMMAND_BIND,    0, 0 HELP(":<socks-server>[:<socks-port>]:<listen-host>:<listen-port>") };


static const char * _xioopen_socks5_strerror(uint8_t r)
{
	switch(r) {
	case SOCKS5_STATUS_SUCCESS:
		return "succeeded";
	case SOCKS5_STATUS_GENERAL_FAILURE:
		return "general SOCKS server failure";
	case SOCKS5_STATUS_CONNECTION_NOT_ALLOWED:
		return "connection not allowed by ruleset";
	case SOCKS5_STATUS_NETWORK_UNREACHABLE:
		return "network unreachable";
	case SOCKS5_STATUS_HOST_UNREACHABLE:
		return "host unreachable";
	case SOCKS5_STATUS_CONNECTION_REFUSED:
		return "connection refused";
	case SOCKS5_STATUS_TTL_EXPIRED:
		return "TTL expired";
	case SOCKS5_STATUS_COMMAND_NOT_SUPPORTED:
		return "command not supported";
	case SOCKS5_STATUS_ADDRESS_TYPE_NOT_SUPPORTED:
		return "address type not supported";
	default:
		return "unknown error";
	}
}

/*
* Performs the SOCKS5 handshake, i.e sends client hello and receives server
* hello back.
* If successful the connection is now ready for sending a SOCKS5 request.
*
* The code is unnecessarily complex right now, for what is essentially
* send(0x050100) followed by "return read() == 0x0500", but will be easier to
* extend for other auth mode support.
*/
static int _xioopen_socks5_handshake(
	struct single *sfd,
	char *socksuser,
	char *sockspass,
	int level)
{
	int result;
	ssize_t bytes;
	struct socks5_server_hello server_hello;
	uint8_t auth_method = SOCKS5_AUTH_NONE;
	int nmethods = 1;	/* support 2 auth method2: none, user+pw */
	int client_hello_size;
	struct socks5_client_hello *client_hello;

	if (socksuser != NULL ||
	    sockspass != NULL) {
		auth_method = SOCKS5_AUTH_USER_PASS;
		++nmethods;
	}
	client_hello_size =
		sizeof(struct socks5_client_hello) +
		(nmethods * sizeof(uint8_t));

	client_hello = Malloc(client_hello_size);
	if (client_hello == NULL) {
		Msg2(level, "malloc(%d): %s",
			client_hello_size, strerror(errno));
		if (Close(sfd->fd) < 0) {
			Info2("close(%d): %s", sfd->fd, strerror(errno));
		}

		/* malloc failed - could succeed later, so retry then */
		return STAT_RETRYLATER;
	}

	unsigned char *server_hello_ptr = (unsigned char *)&server_hello;

	/* SOCKS5 Hello with 1 or 2 authentication mechanisms */
	client_hello->version	= SOCKS5_VERSION;
	client_hello->nmethods	= nmethods;
	client_hello->methods[0]= SOCKS5_AUTH_NONE;
	if (auth_method == SOCKS5_AUTH_USER_PASS)
		client_hello->methods[1] = SOCKS5_AUTH_USER_PASS;

	/* Send SOCKS5 Client Hello */
	Info2("sending SOCKS5 client hello version=%d nmethods=%d",
		client_hello->version,
		client_hello->nmethods);
#if WITH_MSGLEVEL <= E_DEBUG
	{
		char *msgbuf;
		if ((msgbuf = Malloc(3 * client_hello_size)) != NULL) {
			xiohexdump((unsigned char *)client_hello,
				   client_hello_size, msgbuf);
			Debug1("sending SOCKS5 client hello %s", msgbuf);
			free(msgbuf);
		}
	}
#endif

	if (writefull(sfd->fd, client_hello, client_hello_size, NULL) < 0) {
		Msg4(level, "write(%d, %p, %d): %s",
		     sfd->fd, client_hello, client_hello_size,
		     strerror(errno));
		if (Close(sfd->fd) < 0) {
			Info2("close(%d): %s", sfd->fd, strerror(errno));
		}
		free(client_hello);

		/* writefull() failed, but might succeed later, so RETRYLATER */
		return STAT_RETRYLATER;
	}
	free(client_hello);

	bytes = 0;
	Info("waiting for SOCKS5 server hello");
	while (bytes >= 0) {
		do {
			result = Read(sfd->fd, server_hello_ptr + bytes,
				      sizeof(struct socks5_server_hello)-bytes);
		} while (result < 0 && errno == EINTR);
		if (result < 0) {
			Msg4(level, "read(%d, %p, "F_Zu"): %s",
			     sfd->fd, server_hello_ptr + bytes,
			     sizeof(struct socks5_server_hello)-bytes,
			     strerror(errno));
			if (Close(sfd->fd) < 0) {
				Info2("close(%d): %s", sfd->fd, strerror(errno));
			}
		}
		if (result == 0) {
			Msg(level, "read(): EOF during read of SOCKS5 server hello, peer might not be a SOCKS5 server");
			if (Close(sfd->fd) < 0) {
				Info2("close(%d): %s", sfd->fd,
				      strerror(errno));
			}

			return STAT_RETRYLATER;
		}

		bytes += result;
		if (bytes == sizeof(struct socks5_server_hello)) {
			Debug1("received all "F_Zd" bytes", bytes);
			break;
		}
		Debug2("received %d bytes, waiting for "F_Zu" more bytes",
			result, sizeof(struct socks5_server_hello)-bytes);
	}
	if (result <= 0) {
		return STAT_RETRYLATER;
	}

	Debug2("received SOCKS5 server hello %02x %02x",
	       server_hello_ptr[0], server_hello_ptr[1]);
	Info2("received SOCKS5 server hello version=%d method=%d",
		server_hello.version,
		server_hello.method);

	if (server_hello.version != SOCKS5_VERSION) {
		Msg2(level, "SOCKS5 server hello version was %d, not the expected %d, peer might not be a SOCKS5 server",
			server_hello.version, SOCKS5_VERSION);
		if (Close(sfd->fd) < 0) {
			Info2("close(%d): %s", sfd->fd, strerror(errno));
		}
		return STAT_RETRYLATER;
	}

	if (server_hello.method == SOCKS5_AUTH_NO_METH) {
		Msg(level, "SOCKS5 authentication negotiation failed - client & server have no common supported methods");
		if (Close(sfd->fd) < 0) {
			Info2("close(%d): %s", sfd->fd, strerror(errno));
		}
		return STAT_RETRYLATER;
	}
	Info1("SOCKS5 server chose authentication method %u", server_hello.method);

	if (server_hello.method == SOCKS5_AUTH_NONE) {
		/* Server accepted using no auth */
		Notice("SOCKS5 session is authenticated");
		return STAT_OK;
	}

	if (auth_method == SOCKS5_AUTH_NONE) {
		Error("authentication with SOCKS5 server failed");
		return STAT_NORETRY;
	}
	if (server_hello.method != SOCKS5_AUTH_NONE &&
	    (auth_method == SOCKS5_AUTH_USER_PASS &&
	     server_hello.method != SOCKS5_AUTH_USER_PASS)) {
		Msg1(level, "SOCKS5 server requested unsupported auth method (%d)",
		     server_hello.method);
		if (Close(sfd->fd) < 0) {
			Info2("close(%d): %s", sfd->fd, strerror(errno));
		}
		return STAT_RETRYLATER;
	}

	if (auth_method == SOCKS5_AUTH_USER_PASS) {
		unsigned int username_len;
		unsigned int password_len;
		size_t auth_request_size;
		uint8_t *auth_request;

		username_len = strlen(socksuser);
		password_len = strlen(sockspass);
		auth_request_size = (1 +1+username_len +1+password_len);
		auth_request = Malloc(auth_request_size);
		if (auth_request == NULL) {
			Msg2(level, "malloc("F_Zu"): %s",
			     auth_request_size, strerror(errno));
			if (Close(sfd->fd) < 0) {
				Info2("close(%d): %s", sfd->fd, strerror(errno));
			}

			/* malloc failed - could succeed later, so retry then */
			return STAT_RETRYLATER;
		}
		auth_request[0] = 0x01; 	/* by spec */
		auth_request[1] = username_len;
		memcpy(&auth_request[2], socksuser, username_len);
		auth_request[2+username_len] = password_len;
		memcpy(&auth_request[2+username_len+1], sockspass,
		       password_len);
		Info("sending SOCKS5 user-pass authentication");
#if WITH_MSGLEVEL <= E_INFO
		{
			size_t pwlen = strlen(sockspass);
			if (pwlen >= 256)
				pwlen = 255;
			char stars[256];
			memset(stars, '*', pwlen);
			stars[pwlen] = '\0';
			Info2("sending SOCKS5 auth request \"%s\":%s",
			      socksuser, stars);
		}
#endif
		if (writefull(sfd->fd, auth_request, auth_request_size, NULL) < 0) {
			Msg4(level, "write(%d, %p, "F_Zu"): %s",
			     sfd->fd, auth_request, auth_request_size,
			     strerror(errno));
			if (Close(sfd->fd) < 0) {
				Info2("close(%d): %s", sfd->fd, strerror(errno));
			}
			free(auth_request);

			/* writefull() failed, but might succeed later, so RETRYLATER */
			return STAT_RETRYLATER;
		}

	}
	Info("SOCKS5: waiting for authentication result");
	{
		struct {
			uint8_t ver;
			uint8_t status;
		} socks5_auth_reply;
		ssize_t result;

		result = readfull(sfd->fd, &socks5_auth_reply,
				  sizeof(socks5_auth_reply), NULL, E_WARN);
		if (result < 0) {
			Error1("reading SOCKS5 authentication result failed: %s",
			       strerror(result));
			return STAT_RETRYLATER;
		}
		if (result == 0) {
			Error("reading SOCKS5 authentication result failed: EOF");
			return STAT_RETRYLATER;
		}
		if (socks5_auth_reply.status != SOCKS5_AUTH_SUCCESS) {
			Error1("SOCKS5 authentication for user \"%s\" failed",
			       socksuser);
			return STAT_RETRYLATER;
		}
		Notice("SOCKS5 authentication succeeded");
	}
	return STAT_OK;
}

/*
* Generates the SOCKS5 request for a given command, host and port
*/
static struct socks5_request *_xioopen_socks5_prepare_request(
	int *bytes, const char *target_name, const char *target_port,
	uint8_t socks_command, int level)
{
	struct socks5_request *req;
	char ipaddr[16];
	uint16_t *dstport;

	*bytes = 0;

	if (inet_pton(AF_INET, target_name, ipaddr)){ /* if(valid_ipv4) */
		*bytes = sizeof(struct socks5_request) + 4 + sizeof(uint16_t);
		req = (struct socks5_request *)Malloc(*bytes);
		if (req == NULL){
			Info2("Malloc(%d): %s", *bytes, strerror(errno));
			return NULL;
		}

		req->address_type = SOCKS5_ATYPE_IPv4;
		memcpy(req->dstdata, ipaddr, 4);

		dstport = (uint16_t *) &req->dstdata[4];
		*dstport = parseport(target_port, IPPROTO_TCP);
	} else if (inet_pton(AF_INET6, target_name, ipaddr)) { /* else if(valid_ipv6) */
		*bytes = sizeof(struct socks5_request) + 16 + sizeof(uint16_t);
		req = (struct socks5_request *)Malloc(*bytes);
		if (req == NULL){
			Info2("Malloc(%d): %s", *bytes, strerror(errno));
			return NULL;
		}

		req->address_type = SOCKS5_ATYPE_IPv6;
		memcpy(req->dstdata, ipaddr, 16);

		dstport = (uint16_t *) &req->dstdata[16];
		*dstport = parseport(target_port, IPPROTO_TCP);
	} else { /* invalid IP, assume hostname */
		int hlen = strlen(target_name);
		if (hlen > 255) {
			Msg(level, "target hostname too long (>255 bytes), aborting");
			return NULL;
		}

		*bytes = sizeof(struct socks5_request) + 1 + hlen +
			sizeof(uint16_t);
		req = (struct socks5_request *)Malloc(*bytes);
		if (req == NULL ){
			Info2("malloc(%d): %s", *bytes, strerror(errno));
			return NULL;
		}

		req->address_type = SOCKS5_ATYPE_DOMAINNAME;
		req->dstdata[0] = (unsigned char) hlen;
		memcpy(&req->dstdata[1], target_name, hlen);

		dstport = (uint16_t *) &req->dstdata[hlen + 1];
		*dstport = parseport(target_port, IPPROTO_TCP);
	}


	if (*dstport == 0){
		free(req);
		return NULL;
	}

	req->version = SOCKS5_VERSION;
	req->command = socks_command;
	req->reserved = 0;

	return req;
}

/*
* Reads a server reply after a request has been sent
*/
static int _xioopen_socks5_read_reply(
	struct single *sfd, struct socks5_reply *reply, int level)
{
	int result = 0;
	int bytes_read = 0;
	int bytes_to_read = 5;
	bool typechecked = false;

	while (bytes_to_read >= 0) {
		Info("reading SOCKS5 reply");
		do {
			result = readfull(sfd->fd,
					  ((unsigned char *)reply) + bytes_read,
					  bytes_to_read-bytes_read,
					  NULL, level-1);
		} while (result < 0 && errno == EINTR);
		if (result < 0) {
			Msg4(level, "read(%d, %p, %d): %s",
			     sfd->fd, ((unsigned char *)reply) + bytes_read,
			     bytes_to_read-bytes_read, strerror(errno));
			if (Close(sfd->fd) < 0) {
				Info2("close(%d): %s", sfd->fd, strerror(errno));
			}
			return STAT_RETRYLATER;
		}
		if (result == 0) {
			Msg(level, "read(): EOF during read of SOCKS5 reply");
			if (Close(sfd->fd) < 0) {
				Info2("close(%d): %s",
				      sfd->fd, strerror(errno));
			}
			return STAT_RETRYLATER;
		}
		Debug5("received SOCKS5 reply %02x %02x %02x %02x %02x",
		       ((unsigned char *)reply+bytes_read)[0],
		       ((unsigned char *)reply+bytes_read)[1],
		       ((unsigned char *)reply+bytes_read)[2],
		       ((unsigned char *)reply+bytes_read)[3],
		       ((unsigned char *)reply+bytes_read)[4]);
		bytes_read += result;

		/* Once we've read 5 bytes, figure out total message length and
		*  update bytes_to_read accordingly. */
		if (!typechecked && bytes_read <= 5) {
			switch(reply->address_type) {
			case SOCKS5_ATYPE_IPv4:
				/* 6 fixed bytes, and 4 bytes for v4 address */
				bytes_to_read = 10;
				break;
			case SOCKS5_ATYPE_IPv6:
				/* 6 fixed bytes, and 16 bytes for v6 address */
				bytes_to_read = 22;
				break;
			case SOCKS5_ATYPE_DOMAINNAME:
				/* 6 fixed bytes, 1 byte for strlen,
				   and 0-255 bytes for domain name */
				bytes_to_read = 7 + reply->dstdata[0];
				break;
			default:
				Msg1(level, "invalid SOCKS5 reply address type (%d)",
				     reply->address_type);
				if (Close(sfd->fd) < 0) {
					Info2("close(%d): %s",
					      sfd->fd, strerror(errno));
				}
				return STAT_RETRYLATER;
			}
			typechecked = true;
			continue;
		}

		if (bytes_to_read == bytes_read) {
			Debug1("received all %d bytes", bytes_read);
			break;
		}

		Debug2("received %d of %d bytes, waiting",
		       bytes_read, bytes_to_read);
	}

	if (result <= 0) {
		return STAT_RETRYLATER;
	}

	return STAT_OK;
}


/*
* Sends a request and receives the reply.
* If command is BIND we receive two replies.
*/
static int _xioopen_socks5_request(
	struct single *sfd, const char *target_name, const char *target_port,
	uint8_t socks_command, int level)
{
	struct socks5_request *req;
	int bytes, result = 0;

	req = _xioopen_socks5_prepare_request(&bytes, target_name, target_port,
					      socks_command, level);
	if (req == NULL) {
		if (Close(sfd->fd) < 0) {
			Info2("close(%d): %s", sfd->fd, strerror(errno));
		}

		/* Prepare_request could fail due to malloc, but most likely
		the destination is invalid, e.g too long hostname, so NORETRY */
		return STAT_NORETRY;
	}

	Info4("sending SOCKS5 request version=%d command=%d reserved=%d address_type=%d",
		req->version, req->command, req->reserved, req->address_type);

#if WITH_MSGLEVEL <= E_DEBUG
	{
		char *msgbuf;
		if ((msgbuf = Malloc(3 * bytes)) != NULL) {
			xiohexdump((const unsigned char *)req, bytes, msgbuf);
			Debug1("sending SOCKS5 request %s", msgbuf);
			free(msgbuf);
		}
	}
#endif

	if (writefull(sfd->fd, req, bytes, NULL) < 0) {
		Msg4(level, "write(%d, %p, %d): %s",
			sfd->fd, req, bytes, strerror(errno));
		if (Close(sfd->fd) < 0) {
			Info2("close(%d): %s", sfd->fd, strerror(errno));
		}
		free(req);
		return STAT_RETRYLATER;
	}
	free(req);
	req = NULL;

	struct socks5_reply *reply = Malloc(SOCKS5_MAX_REPLY_SIZE);
	if (reply == NULL) {
		if (Close(sfd->fd) < 0) {
			Info2("close(%d): %s", sfd->fd, strerror(errno));
		}

		return STAT_RETRYLATER;
	}

	result = _xioopen_socks5_read_reply(sfd, reply, level);
	if (result != STAT_OK) {
		free(reply);
		return result;
	}

	/* TODO: maybe output nicer debug, like including address */
	Info3("received SOCKS5 reply version=%d reply=%d address_type=%d",
	      reply->version, reply->reply, reply->address_type);

	if (reply->version != SOCKS5_VERSION) {
		Msg2(level, "SOCKS5 reply version was %d, not the expected %d, peer might not be a SOCKS5 server",
			reply->version, SOCKS5_VERSION);
		if (Close(sfd->fd) < 0) {
			Info2("close(%d): %s", sfd->fd, strerror(errno));
		}
		free(reply);
		return STAT_RETRYLATER;
	}

	if (reply->reply == SOCKS5_STATUS_SUCCESS &&
	    socks_command == SOCKS5_COMMAND_BIND) {
		Notice("listening on remote host, waiting for connection");
			/* TODO: nicer debug output */
		/* For BIND, we read two replies */
		result = _xioopen_socks5_read_reply(sfd, reply, level);
		if (result != STAT_OK) {
			free(reply);
			return result;
		}
		Notice("received connection on remote host");
		    /* TODO: maybe output nicer debug, like including address */
		Info3("received second SOCKS5 reply version=%d reply=%d address_type=%d",
		      reply->version, reply->reply, reply->address_type);
	}

	switch (reply->reply) {
	case SOCKS5_STATUS_SUCCESS:
		break;
	default:
		Msg2(level, "SOCKS5 server error %d: %s",
		     reply->reply,
		     _xioopen_socks5_strerror(reply->reply));
		if (Close(sfd->fd) < 0) {
			Info2("close(%d): %s", sfd->fd, strerror(errno));
		}
		free(reply);
		return STAT_RETRYLATER;
	}

	free(reply);
	return STAT_OK;
}

/* Same function for all socks5-modes, determined by argv[0] */
static int xioopen_socks5(
	int argc,
	const char *argv[],
	struct opt *opts,
	int xioflags,
	xiofile_t *xxfd,
	const struct addrdesc *addrdesc)
{
	int socks_command = addrdesc->arg1;
	bool dofork = false;
	int maxchildren = 0;
	int socktype = SOCK_STREAM;
	int pf = PF_UNSPEC;
	int ipproto = IPPROTO_TCP;
	char *socksuser = NULL;
	char *sockspass = NULL;
	int level, result;
	struct opt *opts0 = NULL;
	struct single *sfd = &xxfd->stream;
	const char *socks_server, *target_name, *target_port, *socks_port;
	struct addrinfo **bindarr = NULL;
	struct addrinfo **themarr = NULL;
	uint16_t bindport = 0;
	bool needbind = false;
	bool lowport = false;

	if (argc < 4 || argc > 5) {
		xio_syntax(argv[0], 4, argc-1, addrdesc->syntax);
		return STAT_NORETRY;
	}

	socks_server = argv[1];
	if (argc == 5) {
		socks_port = argv[2];
		target_name = argv[3];
		target_port = argv[4];
	} else {
		socks_port = NULL;
		target_name = argv[2];
		target_port = argv[3];
	}

	/* Apply and retrieve some options */
	result = _xioopen_ipapp_init(sfd, xioflags, opts,
				     &dofork, &maxchildren,
				     &pf, &socktype, &ipproto, -1);
	if (result != STAT_OK)
		return result;

	if (_xioopen_opt_socksport(opts, (char **)&socks_port) < 0) {
		return STAT_NORETRY;
	}
	/*! possible memory leak */

	retropt_string(opts, OPT_SOCKSUSER, &socksuser);
	retropt_string(opts, OPT_SOCKSPASS, &sockspass);

	if (socksuser != NULL ||
	    sockspass != NULL) {
		if (socksuser == NULL) {
			const char defuser[] = "anonymous";
			Warn1("SOCKS5 password without username, falling back to \"%s\"",
			      defuser);
			socksuser = strdup(defuser);
		}
		if (sockspass == NULL) {
			Warn("SOCKS5 username without password");
			sockspass = strdup(""); 	/* prevent SIGSEGV */
		}
	}

	opts0 = opts;
	opts = NULL;

	Notice4("opening connection to %s:%s vis socks5 server %s:%s",
		target_name, target_port, socks_server, socks_port);

	do {	/* loop over retries (failed connect and socks-request attempts)
		   and/or forks */
		int _errno;
#if WITH_RETRY
		if (sfd->forever || sfd->retry) {
			level = E_NOTICE;
		} else
#endif
			level = E_WARN;

		opts = copyopts(opts0, GROUP_ALL);

		result =
			_xioopen_ipapp_prepare(&opts, opts0, socks_server, socks_port,
					       pf, socktype, ipproto,
					       sfd->para.socket.ip.ai_flags,
					       &themarr, &bindarr, &bindport, &needbind,
					       &lowport);
		switch (result) {
		case STAT_OK: break;
#if WITH_RETRY
		case STAT_RETRYLATER:
		case STAT_RETRYNOW:
			if (sfd->forever || sfd->retry--) {
				if (result == STAT_RETRYLATER)
					Nanosleep(&sfd->intervall, NULL);
				if (bindarr != NULL)  xiofreeaddrinfo(bindarr);
				xiofreeaddrinfo(themarr);
				freeopts(opts);
				continue;
			}
#endif /* WITH_RETRY */
			/* FALLTHROUGH */
		case STAT_NORETRY:
			if (bindarr != NULL)  xiofreeaddrinfo(bindarr);
			xiofreeaddrinfo(themarr);
			freeopts(opts);
			freeopts(opts0);
			return result;
		}

		Notice2("opening connection to socks5 server %s:%s",
			socks_server, socks_port);
		result =
			_xioopen_ipapp_connect(sfd, socks_server, opts, themarr,
					       needbind, bindarr, bindport, lowport, level);
		_errno = errno;
		if (bindarr != NULL)  xiofreeaddrinfo(bindarr);
		xiofreeaddrinfo(themarr);
		switch (result) {
		case STAT_OK: break;
#if WITH_RETRY
		case STAT_RETRYLATER:
		case STAT_RETRYNOW:
			if (sfd->forever || sfd->retry--) {
				if (result == STAT_RETRYLATER) {
					Nanosleep(&sfd->intervall, NULL);
				}
				freeopts(opts);
				continue;
			}
#endif /* WITH_RETRY */
			/* FALLTHROUGH */
		default:
			Error4("%s:%s:%s:...: %s",
			       argv[0], socks_server, socks_port,
			       _errno?strerror(_errno):"(See above)");
			freeopts(opts0);
			freeopts(opts);
			return result;
		}

		result = _xioopen_socks5_handshake(sfd, socksuser, sockspass,
						   level);
		switch (result) {
		case STAT_OK: break;
#if WITH_RETRY
		case STAT_RETRYLATER:
		case STAT_RETRYNOW:
			if (sfd->forever || sfd->retry--) {
				if (result == STAT_RETRYLATER) {
					Nanosleep(&sfd->intervall, NULL);
				}
				freeopts(opts);
				continue;
			}
#endif /* WITH_RETRY */
			/* FALLTHROUGH */
		default:
			Error3("%s:%s:%s: handshake failed", argv[0], socks_server, socks_port);
			freeopts(opts0);
			freeopts(opts);
			return result;
		}

		result = _xioopen_socks5_request(sfd, target_name, target_port, socks_command, level);
		switch (result) {
		case STAT_OK:
			break;
#if WITH_RETRY
		case STAT_RETRYLATER:
		case STAT_RETRYNOW:
			if ( sfd->forever || sfd->retry-- ) {
				if (result == STAT_RETRYLATER)
					Nanosleep(&sfd->intervall, NULL);
				freeopts(opts);
				continue;
			}
#endif /* WITH_RETRY */
			/* FALLTHROUGH */
		default:
			freeopts(opts);
			freeopts(opts0);
			return result;
		}

		if (dofork) {
			xiosetchilddied();
		}

#if WITH_RETRY
		if (dofork) {
			pid_t pid;
			int level = E_ERROR;
			if (sfd->forever || sfd->retry) {
				level = E_WARN;
			}
			while ((pid = xio_fork(false, level, sfd->shutup)) < 0) {
				if (sfd->forever || sfd->retry) {
					if (sfd->retry > 0)
						--sfd->retry;
					Nanosleep(&sfd->intervall, NULL);
					freeopts(opts);
					continue;
				}
				freeopts(opts);
				freeopts(opts0);
				return STAT_RETRYLATER;
			}

			if ( pid == 0 ) {
				sfd->forever = false;
				sfd->retry = 0;
				break;
			}

			Close(sfd->fd);
			Nanosleep(&sfd->intervall, NULL);
			while (maxchildren > 0 && num_child >= maxchildren) {
				Info1("all %d allowed children are active, waiting", maxchildren);
				Nanosleep(&sfd->intervall, NULL);
			}
			freeopts(opts);
			continue;
		} else
#endif /* WITH_RETRY */
		{
			break;
		}
	} while (true);

	Notice4("successfully connected to %s:%s via socks5 server %s:%s",
		target_name, target_port, socks_server, socks_port);

	result = _xio_openlate(sfd, opts);
	freeopts(opts);
	freeopts(opts0);
	return STAT_OK;
}

#endif /* WITH_SOCKS5 */
