/* DoNewBind */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <syslog.h>
#include "socks.h"

extern void sockd_fail();
extern u_int32 sockd_ckrt();
extern char *socks_porttoserv();

#if defined(SOCKSIFIED_SOCKD)
extern int SOCKSinit();
extern u_int32 socks_last_conn_host;
extern unsigned short socks_last_conn_port;
extern struct sockshost_s socks_srcsh, socks_dstsh;
#endif /* #if defined(SOCKSIFIED_SOCKD) */


/*
**  Set up a socket to be connected to from the outside world.
**   diffrence between this an the Version1 protocal is that
**   the socket has to be bound from a specific host that
**   is passed.
*/
#if defined(SOCKSIFIED_SOCKD)
int RDoNewBind(in, srcshp, dstshp, rtAddr, Nrt, myaddr, log_msg)
#else
int DoNewBind(in, srcshp, dstshp, rtAddr, Nrt, myaddr, log_msg)
#endif /* #if defined(SOCKSIFIED_SOCKD) */
int	in;
struct sockshost_s *srcshp, *dstshp;
struct config *rtAddr;
int Nrt;
u_int32 myaddr;
char *log_msg;
/* returns descriptor of socket connection to destination */
{
	int			new, out, len = sizeof(struct sockaddr_in);
	struct sockaddr_in	sin;
	Socks_t			ndst;
	int	turnon = 1;
	struct in_addr *pIP;
	int i;

#define dst_name dstshp->dmname[0]
#define dst_serv dstshp->portname

	bzero((char *)&sin, sizeof(sin));
#if defined(SO_OOBINLINE)
	setsockopt(in, SOL_SOCKET, SO_OOBINLINE, &turnon, sizeof(turnon));
#endif

	sin.sin_family = AF_INET;
	ndst.version = SOCKS_REPLY_VERSION;
	ndst.cmd  = SOCKS_RESULT;
	sin.sin_port = htons(0);

	if ((out = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		sockd_fail("socket()", in, &ndst,log_msg);

#if defined(SOCKSIFIED_SOCKD)
	sin.sin_addr.s_addr = htonl(INADDR_ANY);
	SOCKSinit("rsockd");
	socks_last_conn_host = dstshp->shipaddr[0].s_addr;
	socks_last_conn_port = dstshp->port;
	for (i= 0; i < MAXNAMESPERHOST; i++) {
		socks_srcsh.dmname[i] = srcshp->dmname[i];
		socks_dstsh.dmname[i] = dstshp->dmname[i];
	}
	for (i = 0; i < MAXIPPERHOST; i++) {
		socks_srcsh.shipaddr[i].s_addr = srcshp->shipaddr[i].s_addr;
		socks_dstsh.shipaddr[i].s_addr = dstshp->shipaddr[i].s_addr;
	}
	strcpy(socks_srcsh.user, srcshp->user);
	strcpy(socks_srcsh.ruser, srcshp->ruser);
	socks_dstsh.port = srcshp->port;
	strcpy(socks_dstsh.portname, dstshp->portname);
	if (Rbind(out, (struct sockaddr *)&sin, sizeof(sin)) < 0)
		sockd_fail("bind()", in, &ndst,log_msg);
	if (Rgetsockname(out, (struct sockaddr *)&sin, &len) < 0)
		sockd_fail("getsockname()", in, &ndst,log_msg);
#else
#if defined(MULTIHOMED_SERVER)
	sin.sin_addr.s_addr = sockd_ckrt(dstshp, rtAddr, Nrt);
#else
	sin.sin_addr.s_addr = myaddr;
#endif /* #if defined(MULTIHOMED_SERVER) */
	if (bind(out, (struct sockaddr *)&sin, sizeof(sin)) < 0)
		sockd_fail("bind()", in, &ndst,log_msg);
	if (getsockname(out, (struct sockaddr *)&sin, &len) < 0)
		sockd_fail("getsockname()", in, &ndst,log_msg);
#endif /* #if defined(SOCKSIFIED_SOCKD) */


	ndst.port = sin.sin_port;
	ndst.host = sin.sin_addr.s_addr;

#if defined(SOCKSIFIED_SOCKD)
	if (Rlisten(out, 1) < 0)
		sockd_fail("listen()", in, &ndst,log_msg);
#else
	if (listen(out, 1) < 0)
		sockd_fail("listen()", in, &ndst,log_msg);
#endif /* #if defined(SOCKSIFIED_SOCKD) */

	socks_SendDst(in, &ndst);

	len = sizeof(struct sockaddr_in);
#if defined(SOCKSIFIED_SOCKD)
	if ((new = Raccept(out, (struct sockaddr *)&sin, &len)) < 0)
		sockd_fail("accept()", in, &ndst, log_msg);
#else
	if ((new = accept(out, (struct sockaddr *)&sin, &len)) < 0)
		sockd_fail("accept()", in, &ndst, log_msg);
#endif /* #if defined(SOCKSIFIED_SOCKD) */

#if defined(SO_OOBINLINE)
	setsockopt(new, SOL_SOCKET, SO_OOBINLINE, &turnon, sizeof(turnon));
#endif
	close(out);

	(void)socks_porttoserv(sin.sin_port, dst_serv, sizeof(dst_serv));

#if !defined(SOCKSIFIED_SOCKD)
	if (dstshp->shipaddr[0].s_addr != -1) {
		for (pIP = dstshp->shipaddr; (*pIP).s_addr != 0; pIP++) {
			if (sin.sin_addr.s_addr = (*pIP).s_addr)
				break;
		}
		if ((*pIP).s_addr == 0) {
			socks_IPtohost(&sin.sin_addr, dstshp);
			socks_porttoserv(sin.sin_port, dst_serv, sizeof(dst_serv));
			syslog(LOG_LOW, "failed -- %s. Error: connected to wrong host %s (%s)",
				log_msg, dst_name, dst_serv);
			ndst.cmd = SOCKS_FAIL;
			socks_SendDst(in, &ndst);
			exit(1);
		}
	}
#endif /* #if !defined(SOCKSIFIED_SOCKD) */

	syslog(LOG_LOW, "connected -- %s (%s)", log_msg, dst_serv);
	ndst.port = sin.sin_port;
	ndst.host = sin.sin_addr.s_addr;
	socks_SendDst(in, &ndst);
	return new;
}
