/* Rbind.c */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#if defined(ISC)
#include <net/errno.h>
#endif /* #if defined(ISC) */
#include <stdio.h>
#include <netdb.h>
#include <sys/time.h>
#include <syslog.h>
#if (defined(sun) && !defined(SOLARIS)) || defined(sgi)
#include <strings.h>
#else
#include <string.h>
#endif
#include "socks.h"

extern int socksC_proto();
extern int socks_check_result();
extern int socks_connect_sockd();
extern int SOCKSinit();
extern int socks_ckcf();

extern struct sockshost_s socks_srcsh, socks_dstsh;
#define socks_cmd socks_dstsh.user
#define dst_name socks_dstsh.dmname[0]
#define dst_serv socks_dstsh.portname
#define src_user socks_srcsh.user
#define real_user socks_srcsh.ruser
#define src_name socks_srcsh.dmname[0]

extern int socks_init_done;
extern u_int32 socks_last_conn_host;
extern unsigned short socks_last_conn_port;
extern struct sockaddr_in socks_nsin;
extern struct sockaddr_in socks_cursin;
extern int socks_no_conf;
extern struct config *scfAddr;
extern int Nscf;
extern int socks_useSyslog;
extern char *socks_server;

static int socks_direct;

Rbind(sock, sin, size)
int			sock;
struct sockaddr_in	*sin;
int			size;
{
	Socks_t			dst;
	struct sockaddr_in	psin;
	int	i;
	int	con_ret;
	struct timeval tmo;
	fd_set fds;
	int	s;

	if (socks_init_done == 0)
		SOCKSinit("SOCKSclient");
	bzero((char *)&psin, sizeof(psin));
	strcpy(socks_cmd, "bind");

	psin.sin_addr.s_addr = socks_last_conn_host;
	psin.sin_port = socks_last_conn_port;
	if (socks_no_conf)
		socks_direct = SOCKS_DIRECT;
	else
		socks_direct = socks_ckcf(&socks_srcsh, &socks_dstsh, scfAddr, Nscf, socks_useSyslog);

	if (socks_direct == SOCKS_DENY) {
		syslog(LOG_LOW, "Refused -- bind() from %s(%s) for %s (%s)",
			src_user, real_user, dst_name, dst_serv);
		errno = ECONNREFUSED;
		return -1;
	}

	if (socks_direct == SOCKS_DIRECT) {
		syslog(LOG_LOW, "bind() directly from %s(%s) for %s (%s)",
			src_user, real_user, dst_name, dst_serv);
		return (bind(sock, (struct sockaddr *)sin, size));
	}
	
	con_ret = socks_connect_sockd(sock);
	if (con_ret == 0) {
		;
#ifdef SVR4
	} else if ((errno == EINPROGRESS) || (errno == EAGAIN)) {
#else
	} else if (errno == EINPROGRESS) {
#endif
		while (1) {
			tmo.tv_sec = 0;
			tmo.tv_usec = 100000;
			FD_ZERO(&fds);
			FD_SET(sock, &fds);
			s = select(sock+1, NULL, &fds, NULL, &tmo);
			if ((s == 0) || ((s == -1) && (errno == EINTR)))
				continue;
			if (s < 0) {
				syslog(LOG_LOW, "select() in Rbind(): %m");
				errno = ECONNREFUSED;
				return -1;
			}
			con_ret = connect(sock, (struct sockaddr *)&socks_nsin, sizeof(struct sockaddr_in));
			if ((con_ret < 0) && (errno == EISCONN)) {
				con_ret = 0;
				break;
#if defined(SVR4)
			} else if ((con_ret < 0) && ((errno == EALREADY) || 
				(errno == EAGAIN))) {
#else /* !defined(SVR4) */
			} else if ((con_ret < 0) && (errno == EALREADY)) {
#endif /* #if defined(SVR4) */
				continue;
			} else
				break;
		}
	}
	if (con_ret < 0) {
		syslog(LOG_LOW, "Failed -- bind() from %s(%s) for %s (%s)",
			src_user, real_user, dst_name, dst_serv);
		errno = ECONNREFUSED;
		return -1;
	}
	syslog(LOG_LOW, "bind() from %s(%s) for %s (%s) using sockd at %s",
		src_user, real_user, dst_name, dst_serv, socks_server);

	dst.version = SOCKS_VERSION;
	dst.cmd     = SOCKS_BIND;
	dst.port    = socks_last_conn_port;
	dst.host    = socks_last_conn_host;
	if (socksC_proto(sock, &dst) < 0)
		return(-1);
	socks_cursin.sin_family = AF_INET;
	socks_cursin.sin_port = dst.port;
	if (ntohl(dst.host) == INADDR_ANY)
		socks_cursin.sin_addr.s_addr = socks_nsin.sin_addr.s_addr;
	else
		socks_cursin.sin_addr.s_addr = dst.host;

	return (socks_check_result(dst.cmd));
}

/*
**  Stub routine since the listen will have alread succeded on the
**   server.
*/
Rlisten(s, n)
int	s, n;
{
	if (socks_direct)
		return (listen(s, n));

	return 0;
}

/*
**  Well we know where we got a connection from.
*/
Rgetsockname(sock, sin, size)
int			sock;
struct sockaddr_in	*sin;
int			*size;
{
	if (socks_direct)
		return (getsockname(sock, (struct sockaddr *)sin, size));

	*size = sizeof(struct sockaddr_in);
	*sin = socks_cursin;

	return 0;
}

/*
**  Do an accept, which is really a select for some data on
**    the present socket.
*/
Raccept(sock, sin, size)
int			sock;
struct sockaddr_in	*sin;
int			*size;
{
	fd_set		fds;
	Socks_t		dst;
	int		fdsbits = sock + 1;

	if (socks_direct)
		return(accept(sock, (struct sockaddr *)sin, size));

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

	if (select(fdsbits, &fds, NULL, NULL, NULL) > 0) {
		if (FD_ISSET(sock, &fds) && (socks_GetDst(sock, &dst) >= 0)) {
			sin->sin_family = AF_INET;
			sin->sin_port = dst.port;
			sin->sin_addr.s_addr = dst.host;
			return(dup(sock));
		}
	}
	if (socks_useSyslog)
		syslog(LOG_LOW, "Connection refused by SOCKS server %s\n", socks_server);
	else
		fprintf(stderr, "Connection refused by SOCKS server %s\n", socks_server);
	return -1;
}

