/*
  eXosip - This is the eXtended osip library.
  Copyright (C) 2002,2003,2004,2005,2006,2007  Aymeric MOIZARD  - jack@atosc.org
  Socks support Copyright (C) 2011  MBDSYS  - (vadim@mbdsys.com)
  
  eXosip 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.
  
  eXosip 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/


#ifdef ENABLE_MPATROL
#include <mpatrol.h>
#endif

#include "eXosip2.h"
#include "eXtransport.h"

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifdef WIN32
#include <Mstcpip.h>
#else
#define closesocket close
#endif

#if defined(_WIN32_WCE) || defined(WIN32)
#define strerror(X) "-1"
#define ex_errno WSAGetLastError()
#else
#define ex_errno errno
#endif

#ifndef EAGAIN
#define EAGAIN WSAEWOULDBLOCK
#endif
#ifndef EWOULDBLOCK
#define EWOULDBLOCK WSAEWOULDBLOCK
#endif

//static int socks_socket;
static struct sockaddr_storage ai_addr;

static char socks_firewall_ip[64];
static char socks_firewall_port[10];

#define SOCKST_UDPMASK 8
enum _socktypes { SOCKST_TCP, SOCKST_HTTP, SOCKST_TCP2UDP=SOCKST_UDPMASK, SOCKST_HTTP2UDP };

struct socks_udp_prefix {
	uint32_t len;
};


/* persistent connection */
struct _socks_socket {
	int socket;
	int socktype;
	int nstate;
	char remote_ip[64];
	int remote_port;
	char public_ip[64];
	int  public_port;
	char *pbuf;
	int pbuflen;
	int udplen;
	int prefixlen;
	union {
		char buf[4];
		struct socks_udp_prefix data;
	} prefix;
};

#define SOCKET_TIMEOUT 0

#ifndef EXOSIP_MAX_SOCKETS
#define EXOSIP_MAX_SOCKETS 100
#endif

extern struct eXtl_protocol eXtl_socks_tcp;

static struct _socks_socket socks_socket_tab[EXOSIP_MAX_SOCKETS];

static int socks_tl_init(void)
{
	memset(&ai_addr, 0, sizeof(struct sockaddr_storage));
	memset(&socks_socket_tab, 0, sizeof(struct _socks_socket) * EXOSIP_MAX_SOCKETS);
	memset(socks_firewall_ip, 0, sizeof(socks_firewall_ip));
	memset(socks_firewall_port, 0, sizeof(socks_firewall_port));
	return OSIP_SUCCESS;
}

static int socks_tl_free(void)
{
	int pos;
	memset(socks_firewall_ip, 0, sizeof(socks_firewall_ip));
	memset(socks_firewall_port, 0, sizeof(socks_firewall_port));
	memset(&ai_addr, 0, sizeof(struct sockaddr_storage));

	for (pos = 0; pos < EXOSIP_MAX_SOCKETS; pos++) {
		struct _socks_socket *sk = &socks_socket_tab[pos];
		if (sk->socket > 0) {
			closesocket(sk->socket);
			if (sk->pbuf)
				osip_free(sk->pbuf);

		}
	}
	memset(&socks_socket_tab, 0, sizeof(struct _socks_socket) * EXOSIP_MAX_SOCKETS);
	return OSIP_SUCCESS;
}

// return current time in milliseconds
static unsigned nowms()
{
	struct timeval tv;

	osip_gettimeofday(&tv, 0);

	return tv.tv_sec*1000+tv.tv_usec/1000;

}
static int
waitreaddata(int sock, unsigned millisecs)
{
	struct timeval tv = { millisecs/1000, (millisecs % 1000) * 1000 };
	fd_set rdset;
	int n;

	FD_ZERO(&rdset);
	FD_SET(sock, &rdset);

	n = select(sock+1, &rdset, 0, 0, &tv);
	if (n > 0)
		return 0;

	return 1;


}

/* read a response to a http request (terminated by \r\n\r\n) */
static int
socks_read_http_response(struct _socks_socket* sk, char* buf,  size_t bsize)
{
	unsigned  start = nowms();
	size_t filled = 0;
	int s;

	while((nowms() - start) < 2000) {
		if (!waitreaddata(sk->socket, 2000 - (nowms() - start) )) {
			s = recv(sk->socket, buf+filled, bsize - filled, MSG_DONTWAIT);
			if ( s > 0) {
				filled += s;
				buf[filled] = 0;
				if (strstr(buf, "\r\n\r\n")) {
					return 0;
				}
				if (filled >= bsize)
					break;
			}
		}

	}
	return -1;





}

static socks_http_negotiate(struct _socks_socket *sk)
{
	char buf[4*1024];

	int ret;

	snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.1\r\n%s:%s\r\n\r\n",
		sk->remote_ip, sk->remote_port, (sk->socktype & SOCKST_UDPMASK) ? "UdpHost" : "Host", sk->remote_ip);

	ret = send(sk->socket, buf, strlen(buf), 0);
	if (ret != strlen(buf))
		return -1;

	if (!socks_read_http_response(sk, buf, sizeof(buf))) {
		if (strstr(buf, "HTTP/1.0") || strstr(buf, "HTTP/1.1")) {
			const char *space = strstr(buf, " ");
			char *nextline;
			int status;

			if (!space)
				return 0;

			status = atoi(space+1);
			if (status != 200)
				return -1;

			nextline = strstr(buf, "\r\n");
			nextline += 2;
			if (!osip_strcasecmp(nextline, "X-PublicAddress:")) {
				char *token = strtok(nextline, ":");
				if (token) {
					token = strtok(NULL, ":");
					if (token) {
						osip_strncpy(sk->public_ip, token, sizeof(sk->public_ip)-1);
						token = strtok(NULL, ":");
						if (token) {
							sk->public_port = atoi(token);
							return 0;
						}
					}
				}

			}
			return -1;
		}


	}
	return -1;
}

struct socks5_req {
	char    ver;          /* Version number */
	char    cmd;          /* Command */
	char    _nop;         /* Reserved */
	char    atp ;        /* Address type */
	uint32_t destip;    /* Dest address */
	uint16_t dport;    /* Dest port */
};

static int
socks5_negotiate(struct _socks_socket *sk)
{
	int ret;
	char msg1[] = { 0x05, 0x01, 0x00 };
	char auth[2];
	struct socks5_req req, resp;
	struct in_addr ina;

	ret = send(sk->socket, msg1, 3, 0);
	if (ret != 3)
		return -1;

	ret = recv(sk->socket, auth, 2, 0);
	if (ret != 2)
		return -1;

	if (auth[0] != 5) /* check protocol version */
		return -1;
	if (auth[1] != 0) /* check auth type */
		return -1;

	req.ver = 5;
	req.cmd = (sk->socktype & SOCKST_UDPMASK) ? 4 : 1;
	req._nop = 0;
	req.atp = 1;
	req.destip = inet_addr(sk->remote_ip);
	req.dport  = htons(sk->remote_port);
	ret = send(sk->socket, &req, sizeof(req), 0);
	if (ret != sizeof(req))
		return -1;

	ret = recv(sk->socket, &resp, 4, 0);
	if (ret != 4)
		return -1;
	if (resp.ver != 5)
		return -1;

	if (resp.cmd != 0)
		return -1;

	if (resp.atp != 0)
		return -1;

	ret = recv(sk->socket, &resp.destip, 6, 0);
	if (ret != 6)
		return 0;

	ina.s_addr = resp.destip;
	osip_strncpy(sk->public_ip, inet_ntoa(ina), sizeof(sk->public_ip)-1);
	sk->public_port = ntohl(resp.dport);

	return 0;
}

int socks_negotiate(struct _socks_socket *sk)
{


	if (sk->socktype== SOCKST_HTTP || sk->socktype == SOCKST_HTTP2UDP) {
		return socks_http_negotiate(sk);
	}
	return socks5_negotiate(sk);



}
static socks_connect(struct _socks_socket *sk, const char *addr, int port)
{
	struct addrinfo *addrinfo = NULL;
	struct addrinfo *curinfo;
	int sock;
	int res;

	res = eXosip_get_addrinfo(&addrinfo,
							  socks_firewall_ip,
							  atoi(socks_firewall_port), eXtl_socks_tcp.proto_num);
	if (res)
		return -1;

	for (curinfo = addrinfo; curinfo; curinfo = curinfo->ai_next) {
		if (curinfo->ai_protocol && curinfo->ai_protocol != IPPROTO_TCP) {
			OSIP_TRACE(osip_trace
					   (__FILE__, __LINE__, OSIP_INFO2, NULL,
						"Skipping protocol %d\n", curinfo->ai_protocol));
			continue;
		}

		sock = (int) socket(curinfo->ai_family, curinfo->ai_socktype,
				curinfo->ai_protocol);


		if (sock < 0) {
			OSIP_TRACE(osip_trace
					(__FILE__, __LINE__, OSIP_ERROR, NULL,
							"Cannot create socket %s!\n", strerror(ex_errno)));
			continue;
		}

	}


	if ((sock >= 0) && !connect(sock, curinfo->ai_addr, curinfo->ai_addrlen)) {
		sk->socket = sock;
		sk->remote_port = port;
		osip_strncpy(sk->remote_ip, addr, sizeof(sk->remote_ip));
		if (!socks_negotiate(sk)) {
			eXosip_freeaddrinfo(addrinfo);
			return 0;
		}
	}

	eXosip_freeaddrinfo(addrinfo);
	closesocket(sock);
	return -1;


}

static struct _socks_socket *
socks_connect_to(const char *host, int port, int mode)
{
	int pos;

	for (pos = 0; pos < EXOSIP_MAX_SOCKETS; pos++) {
		struct _socks_socket *sk = &socks_socket_tab[pos];

		if (sk->socket == 0) {
			sk->socktype = mode;
			if (!socks_connect(sk, host, port)) {
				if (mode & SOCKST_UDPMASK) {
					sk->pbuf = osip_malloc(SIP_MESSAGE_MAX_LENGTH);
					if (sk->pbuf) {
						closesocket(sk->socket);
						return 0;
					}
				}
				return sk;
			}


		}
	}

}

static int
socks_get_udp_prefix(struct _socks_socket *sk)
{
	int t;
	int need_more = sizeof(sk->prefix.data) - sk->prefixlen;

	t = recv(sk->socket, sk->prefix.buf+sk->prefixlen, need_more, 0);
	if (t < 0)
		return -1;
	sk->prefixlen += t;
	if (sk->prefixlen == sizeof(sk->prefix.data)) {
		sk->udplen = ntohl(sk->prefix.data.len);
		sk->prefixlen = 0;
		return 0;
	}
	return 1;

}


static int socks_tl_open(void)
{

	return OSIP_SUCCESS;
}

static int socks_tl_set_fdset(fd_set * osip_fdset, int *fd_max)
{
	int pos;


	for (pos = 0; pos < EXOSIP_MAX_SOCKETS; pos++) {
		if (socks_socket_tab[pos].socket > 0) {
			eXFD_SET(socks_socket_tab[pos].socket, osip_fdset);
			if (socks_socket_tab[pos].socket > *fd_max)
				*fd_max = socks_socket_tab[pos].socket;
		}
	}

	return OSIP_SUCCESS;
}

static int
socks_tl_read_message(fd_set * osip_fdset)
{
	int pos = 0;
	char *buf;



	buf = NULL;

	for (pos = 0; pos < EXOSIP_MAX_SOCKETS; pos++) {
		struct _socks_socket *sk = &socks_socket_tab[pos];

		if (sk->socket > 0
			&& FD_ISSET(sk->socket, osip_fdset)) {
			int i;


			if (sk->socktype & SOCKST_UDPMASK) { // We expect 4 bytes of prefix containing packet length
				if (!socks_get_udp_prefix(sk)) {
					int need_more = sk->udplen - sk->pbuflen;

					i = recv(sk->socket, sk->pbuf, need_more, 0);
					if (i > 0) {
						sk->pbuflen += i;
						if (sk->pbuflen == sk->udplen) {
							sk->pbuf[sk->udplen] = 0;
							_eXosip_handle_incoming_message(sk->pbuf,
															sk->udplen,
															sk->socket,
															sk->remote_ip,
															sk->remote_port);
							sk->udplen = 0;

						}
					}
				}
				continue;
			}

			if (buf == NULL)
				buf = (char *) osip_malloc(SIP_MESSAGE_MAX_LENGTH * sizeof(char) + 1);
			if (buf == NULL)
				return OSIP_NOMEM;

			i = recv(sk->socket, buf, SIP_MESSAGE_MAX_LENGTH, 0);

#define TEST_CODE_FOR_FRAGMENTATION
#ifdef TEST_CODE_FOR_FRAGMENTATION
			if (i > 0) {
				char *end_sip;
				char *cl_header;
				int cl_size;
				osip_strncpy(buf + i, "\0", 1);
				if (sk->pbuf != NULL) {
					/* concat old data with new data */
					sk->pbuf =
						(char *) osip_realloc(sk->pbuf,
											  sk->pbuflen + i + 1);
					if (sk->pbuf == NULL) {
						OSIP_TRACE(osip_trace
								   (__FILE__, __LINE__, OSIP_ERROR, NULL,
									"Reallocation error: (len=%i)",
									sk->pbuflen + i + 1));
						sk->pbuflen = 0;
						continue;	/* give up: realloc issue */
					}
					osip_strncpy(sk->pbuf +  sk->pbuflen, buf, i);
					sk->pbuflen += i;

				}

				if (sk->pbuf == NULL) {
					sk->pbuf = (char *) osip_malloc(i + 1);
					osip_strncpy(sk->pbuf, buf, i);
					sk->pbuflen = i;
				}

				end_sip = strstr(sk->pbuf, "\r\n\r\n");
				/* end_sip might be end of SIP headers */
				while (end_sip != NULL) {
					/* a content-legnth MUST exist before the CRLFCRLF */
					cl_header =
						osip_strcasestr(sk->pbuf,
										"\ncontent-length ");
					if (cl_header == NULL || cl_header > end_sip)
						cl_header = osip_strcasestr(sk->pbuf, "\ncontent-length:");
					if (cl_header == NULL || cl_header > end_sip)
						cl_header = osip_strcasestr(sk->pbuf,"\r\nl ");
					if (cl_header == NULL || cl_header > end_sip)
						cl_header = osip_strcasestr(sk->pbuf, "\r\nl:");

					if (cl_header != NULL && cl_header < end_sip)
						cl_header = strchr(cl_header, ':');
					/* broken data */
					if (cl_header == NULL || cl_header >= end_sip) {
						/* remove data up to crlfcrlf and restart */
						memmove(sk->pbuf, end_sip+4, sk->pbuflen - (end_sip + 4 - sk->pbuf) + 1);

						sk->pbuflen -=  (end_sip + 4 - sk->pbuf);

						sk->pbuf = (char *) osip_realloc(sk->pbuf,  sk->pbuflen + 1);
						if (sk->pbuf == NULL) {
							OSIP_TRACE(osip_trace
									   (__FILE__, __LINE__, OSIP_ERROR, NULL,
										"Reallocation error: (len=%i)", sk->pbuflen + 1));
							sk->pbuflen = 0;
							break;
						}
						end_sip = strstr(sk->pbuf, "\r\n\r\n");
						continue;	/* and restart from new CRLFCRLF */
					}

					/* header content-length was found before CRLFCRLF -> all headers are available */
					cl_header++;	/* after ':' char */
					cl_size = osip_atoi(cl_header);

					if (cl_size == 0
						|| (cl_size > 0 && end_sip + 4 + cl_size <= sk->pbuf + sk->pbuflen)) {
						/* we have beg_sip & end_sip */
						_eXosip_handle_incoming_message(sk->pbuf,
														end_sip + 4 + cl_size - sk->pbuf,
														sk->socket,
														sk->remote_ip,
														sk->remote_port);

						if (sk->pbuflen - (end_sip + 4 + cl_size -  sk->pbuf) == 0) {
							end_sip = NULL;
							OSIP_TRACE(osip_trace
									   (__FILE__, __LINE__, OSIP_INFO2, NULL,
										"All TCP data consumed\n"));
							sk->pbuflen = 0;
							osip_free(sk->pbuf);
							sk->pbuf = NULL;
							continue;
						}

						/* any more content? */
						memmove(sk->pbuf,
								end_sip + 4 + cl_size,
								sk->pbuflen - (end_sip + 4 + cl_size -  sk->pbuf) + 1);

						sk->pbuflen -=(end_sip +4 + cl_size - sk->pbuf);

						sk->pbuf = (char *) osip_realloc(sk->pbuf, sk->pbuflen + 1);
						if (sk->pbuf == NULL) {
							OSIP_TRACE(osip_trace
									   (__FILE__, __LINE__, OSIP_ERROR, NULL,
										"Reallocation error: (len=%i)",
										sk->pbuflen + 1));
							sk->pbuflen = 0;
							break;
						}
						end_sip = strstr(sk->pbuf,  "\r\n\r\n");
						continue;	/* and restart from new CRLFCRLF */
					}

					/* uncomplete SIP message */
					end_sip = NULL;
					OSIP_TRACE(osip_trace
							   (__FILE__, __LINE__, OSIP_INFO2, NULL,
								"Uncomplete TCP data (%s)\n", buf));
					continue;
				}

				if (sk->pbuflen == 0) {
					/* all data consumed are reallocation error ? */
					continue;
				}
#else
			if (i > 5) {
				osip_strncpy(buf + i, "\0", 1);
				OSIP_TRACE(osip_trace
						   (__FILE__, __LINE__, OSIP_INFO2, NULL,
							"Received TCP message: \n%s\n", buf));
				_eXosip_handle_incoming_message(buf, i,
												sk->socket,
												sk->remote_ip,
												sk->remote_port);
#endif
			} else if (i < 0) {
				int status = ex_errno;
				if (status != EAGAIN) {
					OSIP_TRACE(osip_trace
							   (__FILE__, __LINE__, OSIP_ERROR, NULL,
								"Could not read socket (%s)- close it\n",
								strerror(status)));
					close(sk->socket);
					if (sk->pbuf)
						osip_free(sk->pbuf);
					memset(sk, 0, sizeof(socks_socket_tab[pos]));
				}
			} else if (i == 0) {
				OSIP_TRACE(osip_trace
						   (__FILE__, __LINE__, OSIP_INFO1, NULL,
							"End of stream (read 0 byte from %s:%i)\n",
							sk->remote_ip,
							sk->remote_port));
				close(sk->socket);		struct socks_udp_prefix {
					uint32_t len;
				};

				if (sk->pbuf)
					osip_free(sk->pbuf);
				memset(sk, 0, sizeof(socks_socket_tab[pos]));
			}
#ifndef MINISIZE
			else {
				/* we expect at least one byte, otherwise there's no doubt that it is not a sip message ! */
				OSIP_TRACE(osip_trace
						   (__FILE__, __LINE__, OSIP_INFO1, NULL,
							"Dummy SIP message received (size=%i)\n", i));
			}
#endif
		}
	}

	if (buf != NULL)
		osip_free(buf);

	return OSIP_SUCCESS;
}

static int _socks_tl_find_socket(char *host, int port)
{
	int pos;

	for (pos = 0; pos < EXOSIP_MAX_SOCKETS; pos++) {
		struct _socks_socket *sk = &socks_socket_tab[pos];

		if (sk->socket != 0) {
			if (0 == osip_strcasecmp(sk->remote_ip, host)
				&& port == sk->remote_port)
				return sk->socket;
		}
	}
	return -1;
}

static int _socks_tl_is_connected(int sock)
{
	int res;
	struct timeval tv;
	fd_set wrset;
	int valopt;
	socklen_t sock_len;
	tv.tv_sec = SOCKET_TIMEOUT / 1000;
	tv.tv_usec = (SOCKET_TIMEOUT % 1000) * 1000;

	FD_ZERO(&wrset);
	FD_SET(sock, &wrset);

	res = select(sock + 1, NULL, &wrset, NULL, &tv);
	if (res > 0) {
		sock_len = sizeof(int);
		if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) (&valopt), &sock_len)
			== 0) {
			if (valopt) {
				OSIP_TRACE(osip_trace
						   (__FILE__, __LINE__, OSIP_INFO2, NULL,
							"Cannot connect socket node / %s[%d]\n",
							strerror(ex_errno), ex_errno));
				return -1;
			} else {
				return 0;
			}
		} else {
			OSIP_TRACE(osip_trace
					   (__FILE__, __LINE__, OSIP_INFO2, NULL,
						"Cannot connect socket node / error in getsockopt %s[%d]\n",
						strerror(ex_errno), ex_errno));
			return -1;
		}
	} else if (res < 0) {
		OSIP_TRACE(osip_trace
				   (__FILE__, __LINE__, OSIP_INFO2, NULL,
					"Cannot connect socket node / error in select %s[%d]\n",
					strerror(ex_errno), ex_errno));
		return -1;
	} else {
		OSIP_TRACE(osip_trace
				   (__FILE__, __LINE__, OSIP_INFO2, NULL,
					"Cannot connect socket node / select timeout (%d ms)\n",
					SOCKET_TIMEOUT));
		return 1;
	}
}

static int _socks_tl_connect_socket2(char *host, int port, int mode)
{
	int res;
	int sock = -1;
	struct _socks_socket *sk = 0;
	int i = _socks_tl_find_socket(host, port);
	if (i >= 0) {
		return i;
	}

	OSIP_TRACE(osip_trace
			   (__FILE__, __LINE__, OSIP_INFO2, NULL,
				"New binding with %s\n", host));


	sk = socks_connect_to(host, port, mode);
	if (!sk)
		return -1;

	sock = sk->socket;


	if (sock > 0) {
		if (mode & SOCKST_UDPMASK) {
			sk->pbuf = (char *) osip_malloc(SIP_MESSAGE_MAX_LENGTH);
			if (!sk->pbuf) {
				closesocket(sk->socket);
				memset(sk, 0, sizeof(*sk));
				return -1;
			}

		}

		return sock;
	}

	return -1;
}


static int
socks_tl_send_message2(osip_transaction_t * tr, osip_message_t * sip, char *host,
					int port, int out_socket, int mode)
{
	size_t length = 0;
	char *message;
	int i;

	if (host == NULL) {
		host = sip->req_uri->host;
		if (sip->req_uri->port != NULL)
			port = osip_atoi(sip->req_uri->port);
		else
			port = 5060;
	}

	/* remove preloaded route if there is no tag in the To header
	 */
	{
		osip_route_t *route = NULL;
		osip_generic_param_t *tag = NULL;
		osip_message_get_route(sip, 0, &route);

		osip_to_get_tag(sip->to, &tag);
		if (tag == NULL && route != NULL && route->url != NULL) {
			osip_list_remove(&sip->routes, 0);
		}
		i = osip_message_to_str(sip, &message, &length);
		if (tag == NULL && route != NULL && route->url != NULL) {
			osip_list_add(&sip->routes, route, 0);
		}
	}

	if (i != 0 || length <= 0) {
		return -1;
	}

	/* Step 1: find existing socket to send message */
	if (out_socket <= 0) {
		out_socket = _socks_tl_find_socket(host, port);

		/* Step 2: create new socket with host:port */
		if (out_socket <= 0) {
			out_socket = _socks_tl_connect_socket2(host, port, mode);
		}
	} else {
		OSIP_TRACE(osip_trace(__FILE__, __LINE__, OSIP_INFO1, NULL,
							  "reusing REQUEST connection (to dest=%s:%i)\n",
							  host, port));
	}

	if (out_socket <= 0) {
		osip_free(message);
		return -1;
	}

	i = _socks_tl_is_connected(out_socket);
	if (i > 0) {
		time_t now;
		now = time(NULL);
		OSIP_TRACE(osip_trace
				   (__FILE__, __LINE__, OSIP_INFO2, NULL,
					"socket node:%s, socket %d [pos=%d], in progress\n",
					host, out_socket, -1));
		osip_free(message);
		if (tr != NULL && now - tr->birth_time > 10)
			return -1;
		return 1;
	} else if (i == 0) {
		OSIP_TRACE(osip_trace
				   (__FILE__, __LINE__, OSIP_INFO2, NULL,
					"socket node:%s , socket %d [pos=%d], connected\n",
					host, out_socket, -1));
	} else {
		OSIP_TRACE(osip_trace
				   (__FILE__, __LINE__, OSIP_ERROR, NULL,
					"socket node:%s, socket %d [pos=%d], socket error\n",
					host, out_socket, -1));
		osip_free(message);
		return -1;
	}

	OSIP_TRACE(osip_trace(__FILE__, __LINE__, OSIP_INFO1, NULL,
						  "Message sent: (to dest=%s:%i) \n%s\n",
						  host, port, message));

	if (mode & SOCKST_UDPMASK) {
		struct socks_udp_prefix *pfx;

		pfx = (struct socks_udp_prefix *)osip_malloc(length + sizeof(*pfx));
		if (!pfx)
			return -1;
		memcpy(pfx+1, message, length);
		length += sizeof(*pfx);
		osip_free(message);
		message = (char *)pfx;
	}

	while (1) {
		i = send(out_socket, (const void *) message, length, 0);
		if (i < 0) {
			int status = ex_errno;
			if (EAGAIN == status || EWOULDBLOCK == status) {
				struct timeval tv;
				fd_set wrset;
				tv.tv_sec = SOCKET_TIMEOUT / 1000;
				tv.tv_usec = (SOCKET_TIMEOUT % 1000) * 1000;

				FD_ZERO(&wrset);
				FD_SET(out_socket, &wrset);

				i = select(out_socket + 1, NULL, &wrset, NULL, &tv);
				if (i > 0) {
					continue;
				} else if (i < 0) {
					OSIP_TRACE(osip_trace(__FILE__, __LINE__, OSIP_ERROR, NULL,
										  "TCP select error: %s\n",
										  strerror(ex_errno)));
					osip_free(message);
					return -1;
				} else {
					OSIP_TRACE(osip_trace(__FILE__, __LINE__, OSIP_ERROR, NULL,
										  "TCP timeout: %d ms\n", SOCKET_TIMEOUT));
					osip_free(message);
					return -1;
				}
			} else {
				/* SIP_NETWORK_ERROR; */
				OSIP_TRACE(osip_trace(__FILE__, __LINE__, OSIP_ERROR, NULL,
									  "TCP error: %s\n", strerror(status)));
				osip_free(message);
				return -1;
			}
		}
		break;
	}

	osip_free(message);
	return OSIP_SUCCESS;
}

static int
socks_tcp_tl_send_message(osip_transaction_t * tr, osip_message_t * sip, char *host,
					int port, int out_socket)
{
	return socks_tl_send_message2(tr, sip, host, port, out_socket, SOCKST_TCP);
}

static int
http_tl_send_message(osip_transaction_t * tr, osip_message_t * sip, char *host,
					int port, int out_socket)
{
	return socks_tl_send_message2(tr, sip, host, port, out_socket, SOCKST_HTTP);
}


static int
socks_udp_tl_send_message(osip_transaction_t * tr, osip_message_t * sip, char *host,
					int port, int out_socket)
{
	return socks_tl_send_message2(tr, sip, host, port, out_socket, SOCKST_TCP2UDP);
}

static int
http_udp_tl_send_message(osip_transaction_t * tr, osip_message_t * sip, char *host,
					int port, int out_socket)
{
	return socks_tl_send_message2(tr, sip, host, port, out_socket, SOCKST_HTTP2UDP);
}



static int socks_tl_keepalive(void)
{
	return 1;
}

static int socks_tl_set_socket(int socket)
{
//	socks_socket = socket;

	return OSIP_SUCCESS;
}


static int socks_tl_masquerade_contact(const char *public_address, int port)
{
	if (public_address == NULL || public_address[0] == '\0') {
		memset(socks_firewall_ip, '\0', sizeof(socks_firewall_ip));
		memset(socks_firewall_port, '\0', sizeof(socks_firewall_port));
		if (eXtl_socks_tcp.proto_port > 0)
			snprintf(socks_firewall_port, sizeof(socks_firewall_port), "%i",
					 eXtl_socks_tcp.proto_port);
		return OSIP_SUCCESS;
	}
	snprintf(socks_firewall_ip, sizeof(socks_firewall_ip), "%s", public_address);
	if (port > 0) {
		snprintf(socks_firewall_port, sizeof(socks_firewall_port), "%i", port);
	}
	return OSIP_SUCCESS;
}

static int
socks_tl_get_masquerade_contact(char *ip, int ip_size, char *port, int port_size)
{
    int pos;
    struct _socks_socket *sk = 0;

	memset(ip, 0, ip_size);
	memset(port, 0, port_size);
	for (pos = 0; pos < EXOSIP_MAX_SOCKETS; pos++) {
		sk = &socks_socket_tab[pos];
		if (sk->socket > 0)
			break;
	}

	if (!sk)
		return -1;

	if (socks_firewall_ip[0] != '\0')
		snprintf(ip, ip_size, "%s", sk->public_ip);

	if (socks_firewall_port[0] != '\0')
		snprintf(port, port_size, "%d", sk->public_port);
	return OSIP_SUCCESS;
}

struct eXtl_protocol eXtl_socks_tcp = {
	1,
	7070,
	"SOCKS+TCP",
	"0.0.0.0",
	IPPROTO_TCP,
	AF_INET,
	0,
	0,

	&socks_tl_init,
	&socks_tl_free,
	&socks_tl_open,
	&socks_tl_set_fdset,
	&socks_tl_read_message,
	&socks_tcp_tl_send_message,
	&socks_tl_keepalive,
	&socks_tl_set_socket,
	&socks_tl_masquerade_contact,
	&socks_tl_get_masquerade_contact
};

struct eXtl_protocol eXtl_socks_http = {
	1,
	7070,
	"HTTP+TCP",
	"0.0.0.0",
	IPPROTO_TCP,
	AF_INET,
	0,
	0,

	&socks_tl_init,
	&socks_tl_free,
	&socks_tl_open,
	&socks_tl_set_fdset,
	&socks_tl_read_message,
	&http_tl_send_message,
	&socks_tl_keepalive,
	&socks_tl_set_socket,
	&socks_tl_masquerade_contact,
	&socks_tl_get_masquerade_contact
};

struct eXtl_protocol eXtl_socks_tcp2udp = {
	1,
	7070,
	"SOCKS+UDP",
	"0.0.0.0",
	IPPROTO_TCP,
	AF_INET,
	0,
	0,

	&socks_tl_init,
	&socks_tl_free,
	&socks_tl_open,
	&socks_tl_set_fdset,
	&socks_tl_read_message,
	&socks_udp_tl_send_message,
	&socks_tl_keepalive,
	&socks_tl_set_socket,
	&socks_tl_masquerade_contact,
	&socks_tl_get_masquerade_contact
};

struct eXtl_protocol eXtl_socks_http2udp = {
	1,
	7070,
	"HTTP+UDP",
	"0.0.0.0",
	IPPROTO_TCP,
	AF_INET,
	0,
	0,

	&socks_tl_init,
	&socks_tl_free,
	&socks_tl_open,
	&socks_tl_set_fdset,
	&socks_tl_read_message,
	&http_udp_tl_send_message,
	&socks_tl_keepalive,
	&socks_tl_set_socket,
	&socks_tl_masquerade_contact,
	&socks_tl_get_masquerade_contact
};


