/* sockd */
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <syslog.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#if (defined(sun) && !defined(SOLARIS)) || defined(sgi)
#include <strings.h>
#else
#include <string.h>
#endif
#include "socks.h"
#include <fcntl.h>
#if defined(AIX)
#include <sys/mode.h>
#endif

#include "ident.h"
#define IDENTD_TIMEOUT 15 /* 15 seconds */
static int use_identd = 0;
static int useSyslog = 1;

#if defined(SOCKSIFIED_SOCKD)
extern int socks_useSyslog;
struct hostent *Rgethostbyname();
#else
int socks_useSyslog;
#endif /* #if defined(SOCKSIFIED_SOCKD) */


static char server_version[] = "4.3";
extern char	*socks_porttoserv();
extern void sockd_fail();
extern int DoConnect();
extern int DoNewBind();


u_int32 myaddr;
struct sockshost_s srcsh, dstsh;
#define src_user srcsh.user
#define real_user srcsh.ruser
#define src_name srcsh.dmname[0]
#define dst_serv dstsh.portname
#define dst_name dstsh.dmname[0]
#define socks_cmd dstsh.user

char log_msg[1024];

#ifdef DEBUG
static char buf[1024];
#endif

static struct config *cfAddr = NULL;
static int Ncf = 0;
static char *cfStrings = NULL;
/* rtAddr, Nrt, and rtStrings MUST be declared even for non-multi-homed one */
static struct config *rtAddr = NULL;
static int Nrt = 0;
static char *rtStrings = NULL;

static char *bad_id_cmd = NULL;
static char *no_identd_cmd = NULL;


#if !defined(NO_RBIND) && defined(MULTIHOMED_SERVER)
extern u_int32 sockd_ckrt();
#endif /* #if !defined(NO_RBIND) && defined(MULTIHOMED_SERVER) */

void readConfig()
{
	struct stat statfc, statcf;

	if (stat(SOCKD_FC, &statfc) == 0) {
		socks_rdfz(SOCKD_FC, &cfAddr, &Ncf, &cfStrings, useSyslog);
		sockd_getspcmd(cfAddr, Ncf, &no_identd_cmd, &bad_id_cmd);
		/* syslog(LOG_LOW, "Effective sockd.fc lines: %d\n", Ncf); */
	} else if (stat(SOCKD_CONF, &statcf) == 0) {
		sockd_rdconf(SOCKD_CONF, &cfAddr, &Ncf, &no_identd_cmd, &bad_id_cmd, useSyslog);
		/* syslog(LOG_LOW, "Effective sockd.conf lines: %d\n", Ncf); */
	} else {
		if (useSyslog)
			syslog(LOG_HIGH, "Configuration file not found\n");
		else
			fprintf(stderr, "Configuration file not found\n");
		exit(1);
	}

#if !defined(NO_RBIND) && defined(MULTIHOMED_SERVER)
	/* read the route cofiguration */
	if (stat(SOCKD_FROUTE_FILE, &statfc) == 0) {
	        socks_rdfz(SOCKD_FROUTE_FILE, &rtAddr, &Nrt, &rtStrings, useSyslog);

		/* syslog(LOG_LOW, "Effective sockd.fr lines: %d\n", Nrt); */
	} else if (stat(SOCKD_ROUTE_FILE, &statcf) == 0 ) {
        	sockd_rdroute(SOCKD_ROUTE_FILE, &rtAddr, &Nrt, useSyslog);
		/* syslog(LOG_LOW, "Effective sockd.route lines: %d\n", Nrt); */
	} else {
		if (useSyslog)
			syslog(LOG_HIGH, "Route file not found\n");
		else
			fprintf(stderr, "Route file not found\n");
		exit(1);
	}
#endif /* #if !defined(NO_RBIND) && defined(MULTIHOMED_SERVER) */

}



#ifdef STAND_ALONE_SERVER

extern void sockd_dumpcf();
#if !defined(NO_RBIND) && defined(MULTIHOMED_SERVER)
extern void sockd_dumprt();
#endif /* #if !defined(NO_RBIND) && defined(MULTIHOMED_SERVER) */

void rereadConfig()
{
	syslog(LOG_LOW, "Received SIGHUP, rereading config file");
	readConfig();
}

/* system V machines may have to reset the signal here */
void reapChild()
{
	int status;

#if defined(NO_WAITPID)
	while (wait3(&status, WNOHANG, NULL) > 0) ;
#else
	while (waitpid(-1, &status, WNOHANG) > 0) ;
#endif
	signal(SIGCHLD, reapChild);
}

void dumpconf()
{
	syslog(LOG_HIGH, "Received SIGUSR1\n");
	sockd_dumpcf(cfAddr, Ncf, useSyslog);
#if !defined(NO_RBIND) && defined(MULTIHOMED_SERVER)
	sockd_dumprt(rtAddr, Nrt, useSyslog);
#endif /* #if !defined(NO_RBIND) && defined(MULTIHOMED_SERVER) */
}

#endif /* #ifdef STAND_ALONE_SERVER */




void die()
{
	syslog(LOG_HIGH, "timed-out -- %s", log_msg);
	exit(1);
}



#if defined(FOR_PS) && !defined(SYSV)


main(argc, argv, envp)
int	argc;
char	*argv[];
char	*envp[];

#else /* FOR_PS not defined */

main(argc, argv)
int	argc;
char	*argv[];
#endif /* #if defined(FOR_PS) && !defined(SYSV) */

{
	char			c;
	int			inp, in, out, nindex=0;
	int			i, n, len = sizeof(struct sockaddr_in);
	struct sockaddr_in	sin, from, dstsin;
	int			fromlen = sizeof(struct sockaddr_in);
	Socks_t			dst;
	int			one = 1;
	struct servent	*sp;
	int	permit;
	unsigned short socks_port;
	struct hostent *hp;
	char	name[NAMELEN];
	char log_msg[1024];
	char *progname;

	static char		netbuf[4096];
	fd_set			fds;
	int			s, fdsbits;
	static struct timeval	tout = { SOCKS_TIMEOUT, 0 };
/* >>> Andy McFadden fadden@uts.amdahl.com */
 	struct linger ling;	/* for linger */
 	int length;		/* for linger */
	unsigned long from_in = 0L, from_out = 0L;

#ifdef STAND_ALONE_SERVER
	pid_t pid;
	struct passwd *pw;
	sigset_t mask;
	int devnull;
	char pidstr[32];
	int pidf, pidlen;
#endif /* #ifdef STAND_ALONE_SERVER */

#if defined(FOR_PS) && !defined(SYSV)
#define MAXUSERENVIRON 100
	char *UserEnviron[MAXUSERENVIRON+1];    /* saved user environment */
	extern char **environ;
	char ps_buf[1024];
	char **Argv = NULL;   /* pointer to argument vector */
	char *LastArgv = NULL;        /* end of argv */
  
	for (i = 0; i < MAXUSERENVIRON && envp[i] != NULL; i++)
		UserEnviron[i] = strdup(envp[i]);
	UserEnviron[i] = NULL;
	environ = UserEnviron;

	/*
	**  Save start and extent of argv for setproctitle.
	*/

	Argv = argv;
	if (i > 0)
		LastArgv = envp[i - 1] + strlen(envp[i - 1]);
	else
		LastArgv = argv[argc - 1] + strlen(argv[argc - 1]);
#endif /* FOR_PS && !SYSV */

	if ((progname = rindex(argv[0], '/')) == NULL)
		progname = argv[0];
	else
		progname++;
		

/* if started by root, change the uid to nobody. */
#ifdef STAND_ALONE_SERVER
/*
	if (getuid() == 0) {
		if((pw = getpwnam("nobody")) == (struct passwd *)0) {
			fprintf(stderr, "Terminated: User nobody not in password file\n");
			exit(1);
		}
		if (setuid(pw->pw_uid) < 0) {
			fprintf(stderr, "Terminated: Can't change uid to nobody\n");
			exit(1);
		}
	}
*/
#endif /* ifdef STAND_ALONE_SERVER */

	if (argc >= 2) {
		if (strcmp(argv[1],"-ver") == 0) {
			printf(" SOCKS server version %s\n", RELEASE);
			printf("\tSOCKS protocol version: 4A\n");
#if defined(SOCKSIFIED_SOCKD)
			printf("\tSOCKSified: Yes (needs %s or %s)\n", SOCKS_FC, SOCKS_CONF);
#else
			printf("\tSOCKSified: No\n");
#endif /* #if defined(SOCKSIFIED_SOCKD) */
#if defined(STAND_ALONE_SERVER)
			printf("\tStand-alone: Yes\n");
#else
			printf("\tStand-alone: No (must run under inetd)\n");
#endif /* #if defined(STAND_ALONE_SERVER) */
#if defined(NO_RBIND)
			printf("\tSupports RBIND: No\n");
#else
			printf("\tSupports RBIND: Yes\n");
#endif /* #if defined(NO_RBIND) */
# if defined(MULTIHOMED_SERVER)
			printf("\tRuns on Multi-homed server: Yes\n");
#if defined(NO_RBIND)
			printf("\tNeeds route file: No\n");
# else
			printf("\tNeeds route file: Yes\n");
#endif /* #if defined(NO_RBIND) */
#else
			printf("\tRuns on Multi-homed server: No\n");
			printf("\tNeeds route file: No\n");
# endif /* # if defined(MULTIHOMED_SERVER) */
		exit(1);
		}

		if (strcmp(argv[1], "-i") == 0)
			use_identd = 1;
		else if (strcmp(argv[1], "-I") == 0)
			use_identd = 2; /* strict use of identd */
	}

	socks_port = htons(SOCKS_DEF_PORT);
	bzero((char *)&sin, sizeof(sin));
	bzero((char *)&from, sizeof(from));
	bzero((char *)&dstsin, sizeof(dstsin));
	bzero((char *)&srcsh, sizeof(srcsh));
	bzero((char *)&dstsh, sizeof(dstsh));

#if defined(SOCKSIFIED_SOCKD)
	socks_useSyslog = useSyslog;
#endif /* #if defined(SOCKSIFIED_SOCKD) */

	if ((sp = getservbyname("socks", "tcp")) != NULL)
		socks_port = sp->s_port;

#ifdef LOG_DAEMON
	(void) openlog(progname, LOG_PID, SYSLOG_FAC);
#else /* LOG_DAEMON not defined */
	(void) openlog(progname, LOG_PID);
#endif /* #ifdef LOG_DAEMON */

#ifdef STAND_ALONE_SERVER
    if ((pid = fork()) == -1) {
		syslog(LOG_HIGH, "starup could not fork (%m)!");
        exit(1);
	}
    if (pid)
        exit(0);

	/* we are backgrounded now */
	syslog(LOG_LOW, "sockd server starting");
    (void) setsid();
    (void) chdir("/");
	devnull = open("/dev/null", O_RDWR, 0);

	if (devnull != -1) {
		(void) dup2(devnull, 0);
		(void) dup2(devnull, 1);
		(void) dup2(devnull, 2);
		if (devnull > 2)
			(void) close(devnull);
    }
#endif /* #ifdef STAND_ALONE_SERVER */

	readConfig();

	if (gethostname(name, sizeof(name)) < 0) {
		syslog(LOG_HIGH, "Error: gethostname(): %m\n");
		exit(1);
	}
	if ((hp = gethostbyname(name)) == NULL) {
		syslog(LOG_HIGH, "Error: gethostbyname(%s): %m\n", name);
		exit(1);
	}
	bcopy(*(hp->h_addr_list), &myaddr, sizeof(myaddr));

#ifdef STAND_ALONE_SERVER
	/* signal(SIGTERM, killChildren); */
	signal(SIGHUP, rereadConfig);
	signal(SIGCHLD, reapChild);
	signal(SIGUSR1, dumpconf);

	inp  = socket(AF_INET, SOCK_STREAM, 0);
	sin.sin_family = AF_INET;
	sin.sin_port = socks_port;
	sin.sin_addr.s_addr = htonl(INADDR_ANY);

	/* added socket option to allow multiple use of the socks port */
	devnull = 1;
	setsockopt(inp, SOL_SOCKET, SO_REUSEADDR, &devnull, sizeof(devnull));

	if (bind(inp, &sin, sizeof(sin)) < 0) {
		syslog(LOG_HIGH, "error -- main bind() %m");
		exit(1);
	}

	if (listen(inp, MAX_CLIENTS) < 0) {
		syslog(LOG_HIGH, "error -- main listen() %m");
		exit(1);
	}
	/* Put our process id in PID_FILE */
	if ((pidf = open(PID_FILE, O_RDWR|O_CREAT|O_TRUNC,
		S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0) {
		syslog(LOG_HIGH, "Cannot create/access %s", PID_FILE);
	} else {
		sprintf(pidstr, "%d\n", getpid());
		if (write(pidf, pidstr, strlen(pidstr)) < 0)
			syslog(LOG_HIGH, "Cannot write to /%s", PID_FILE);
		close(pidf);
	}
	/* parent stays within this loop */
	for (;;) {
		int pid;
	
		if ((in = accept(inp, &sin, &len)) < 0) {
			if ((errno == EINTR) || (errno == EPROTO)) continue;	/* caught SIGCHLD */
			syslog(LOG_HIGH, "error -- main accept() %m");
			exit(1);
		}
	    
		if ((pid = fork()) == 0) break;	/* child */
		if (pid == -1) syslog(LOG_HIGH, "fork failed - %m");
	    /* parent stays in loop */
		close(in);
	}
	/* in child now */
	signal(SIGHUP, SIG_IGN);
	signal(SIGCHLD, SIG_IGN);
	signal(SIGTERM, SIG_DFL);
	closelog();
	close(inp);
#ifdef LOG_DAEMON
	(void) openlog(progname, LOG_PID, SYSLOG_FAC);
#else /* LOG_DAEMON not defined */
	(void) openlog(progname, LOG_PID);
#endif /* #ifdef LOG_DAEMON */

#else /* STAND_ALONE_SERVER not defined */
	in = dup(0);
#endif /* STAND_ALONE_SERVER */

	if (getpeername(in, (struct sockaddr *)&from, &fromlen) < 0) {
		syslog(LOG_HIGH, "error -- unable to get client address.");
		exit(1);
	}
#ifdef DEBUG
	syslog(LOG_LOW, "socks_client_port=%u", ntohs(from.sin_port));
#endif /* #ifdef DEBUG */

	if (socks_IPtohost(&from.sin_addr, &srcsh) < 0) {
		syslog(LOG_HIGH, "Out of memory\n");
		exit(1);
	}
	strcpy(real_user,"unknown");

	if (socks_GetDst(in, &dst) < 0) {
		syslog(LOG_HIGH, "Error in socks_GetDst: %m; from host %s", src_name);
		exit(1);
	}

	if (dst.version != SOCKS_VERSION) {
		syslog(LOG_HIGH, "error -- wrong version (0x%2x) from host %s.",
			dst.version, src_name);
		exit(1);
	}

	if (dst.cmd == SOCKS_CONNECT) {
		strcpy(socks_cmd, "connect");
#if !defined(NO_RBIND)
	} else  if (dst.cmd == SOCKS_BIND) {
		strcpy(socks_cmd, "bind");
#endif /* #if defined(NO_RBIND) */
	} else {
		syslog(LOG_HIGH, "error -- unsupported command (0x%2x) from host %s",
			dst.cmd, src_name);
		exit(1);
	}

	dstsin.sin_family      = AF_INET;
	dstsin.sin_addr.s_addr = dst.host;
	dstsin.sin_port        = dst.port;

	nindex = 0;
	while (read(in, &c, 1) == 1)
		if (c == '\0')
			break;
		else {
			if (nindex < sizeof(src_user) - 1)
			src_user[nindex++] = c;
		}
	src_user[nindex] = '\0';

#define REALIP	(((ntohl(dst.host)) >> 8) != 0)
	if (REALIP) {
		if (socks_IPtohost(&dstsin.sin_addr, &dstsh) < 0) {
			syslog(LOG_HIGH, "Out of memory\n");
			exit(1);
		}
	} else {
	/* Client sending fake IP, get the domainname now */
		nindex = 0;
		while (read(in, &c, 1) == 1)
			if (c == '\0')
				break;
			else {
				if (nindex < sizeof(name) - 1)
					name[nindex++] = c;
			}
		name[nindex] = '\0';
		if (nindex == 0) {
			syslog(LOG_HIGH, "Received null hostname from client\n");
			exit(1);
		}
#ifdef DEBUG
		syslog(LOG_LOW, "Received domainname >>%s<< from client\n", name);
#endif /* #ifdef DEBUG */
		if(socks_host(name, &dstsh) < 0) {
			syslog(LOG_HIGH, "Out of memory\n");
			exit(1);
		}
		if (dstsh.shipaddr[0].s_addr == 0) {
#if defined(SOCKSIFIED_SOCKD)
			hp = Rgethostbyname(dstsh.dmname[0]);
			bcopy(hp->h_addr_list[0], &(dstsh.shipaddr[0]), IPADDRLENG);
#else /* SOCKSIFIED_SOCKD not defined */
			syslog(LOG_LOW, "Unknown destination host: %s\n", dst_name);
			exit(1);
#endif /* #if defined(SOCKSIFIED_SOCKD) */
		}
	}
	dstsh.port = dstsin.sin_port;
	socks_porttoserv(dstsh.port, dst_serv, sizeof(dst_serv));

	permit = sockd_ckcf(&srcsh, &dstsh, use_identd, in, cfAddr, Ncf, no_identd_cmd, bad_id_cmd, useSyslog);

	if (dst.cmd == SOCKS_CONNECT) {
		sprintf(log_msg, "Connect from %s(%s)@%s to %s (%s)",
			src_user, real_user, src_name, dst_name, dst_serv);
#if defined(FOR_PS) && !defined(SYSV)
		sprintf(ps_buf, "%s: %s(c) to %s",
			src_user, dst_serv, dst_name);
		socks_setproctitle(ps_buf, Argv, LastArgv);
#endif /* #if defined(FOR_PS) && !defined(SYSV) */

#if !defined(NO_RBIND)
	} else {
		sprintf(log_msg, "Bind from %s(%s)@%s for %s",
			src_user, real_user, src_name, dst_name);
#if defined(FOR_PS) && !defined(SYSV)
		sprintf(ps_buf, "%s: %s(b) to %s",
			src_user, dst_serv, dst_name);
		socks_setproctitle(ps_buf, Argv, LastArgv);
#endif /* #if defined(FOR_PS) && !defined(SYSV) */
#endif /* #if !defined(NO_RBIND) */
	}

	if (permit == 1) 
		;
	else if (permit == 0) {
		syslog(LOG_LOW, "refused -- %s", log_msg);
		exit(1);
	} else if (permit == -1) {
		syslog(LOG_LOW, "cannot connect to identd on %s", src_name);
	} else if (permit == -2) {
		syslog(LOG_LOW, "refused -- %s", log_msg);
		syslog(LOG_LOW, "cannot connect to identd on %s", src_name);
		dst.cmd = SOCKS_NO_IDENTD;
		socks_SendDst(in, &dst);
		exit(1);
	} else if (permit == -3) {
		syslog(LOG_LOW, "refused -- %s", log_msg);
		syslog(LOG_LOW, "*Alert*: real user is %s, not %s", real_user, src_user);
		dst.cmd = SOCKS_BAD_ID;
		socks_SendDst(in, &dst);
		exit(1);
	} else {
		syslog(LOG_HIGH, "refused -- %s", log_msg);
		syslog(LOG_HIGH, "Unexpected result from Validate");
		exit(1);
	}


#ifdef DEBUG
	strcpy(buf, inet_ntoa(from.sin_addr));
	syslog(LOG_LOW,"USER:%s,  SRC:%s,  DST:%s, PORT:%u",
		src_user, buf, inet_ntoa(dstsin.sin_addr),
		ntohs(dstsin.sin_port));
#endif /* DEBUG */

	/*
	**  Kill a connecting off if bind or connect takes too
	**    long to complete
	*/
	signal(SIGALRM, die);
	/* alarm(60*5); */ 
	alarm(60*2);

#if defined(NO_RBIND)
#if defined(SOCKSIFIED_SOCKD)
	out = RDoConnect(in, &dstsh, log_msg);
#else
	out = DoConnect(in, &dstsh, log_msg);
#endif /* #if defined(SOCKSIFIED_SOCKD) */
#else
	if (dst.cmd == SOCKS_CONNECT) {
#if defined(SOCKSIFIED_SOCKD)
		out = RDoConnect(in, &dstsh, log_msg);
#else
		out = DoConnect(in, &dstsh, log_msg);
#endif /* #if defined(SOCKSIFIED_SOCKD) */
	}
	if (dst.cmd == SOCKS_BIND) {
#if defined(SOCKSIFIED_SOCKD)
		out = RDoNewBind(in, &srcsh, &dstsh, rtAddr, Nrt, myaddr, log_msg);
#else
		out = DoNewBind(in, &srcsh, &dstsh, rtAddr, Nrt, myaddr, log_msg);
#endif /* #if defined(SOCKSIFIED_SOCKD) */
	}
#endif /* #if defined(NO_RBIND) */

	alarm(0);

 	/*
 	 * ATM: use SO_LINGER so it won't hang up on client
 	 */
 	ling.l_onoff = 1;   /* turn it on */
 	ling.l_linger = /*3*/  10;
 	length = sizeof(ling);
 	if (setsockopt(in,  SOL_SOCKET, SO_LINGER, &ling, length) < 0)
 		syslog(LOG_LOW, "setsockopt (SO_LINGER): %m");
 	if (setsockopt(out, SOL_SOCKET, SO_LINGER, &ling, length) < 0)
 		syslog(LOG_LOW, "setsockopt (SO_LINGER): %m");
/* <<< Andy McFadden fadden@uts.amdahl.com */

	FD_ZERO(&fds);
	if (in > out)
		fdsbits = in + 1;
	else
		fdsbits = out +1;

	while (1) {
		tout.tv_sec = SOCKS_TIMEOUT;
		tout.tv_usec = 0;
		FD_SET(in, &fds);
		FD_SET(out, &fds);
		if ((s = select(fdsbits, &fds, NULL,NULL, &tout)) > 0) {
			if (FD_ISSET(in, &fds)) {
				if ((n = read(in, netbuf, sizeof(netbuf))) > 0) {
					from_in += n;
					if (write(out, netbuf, n) < 0) {
						goto done;
					}
				} else {
					goto done;
				}
			}
			if (FD_ISSET(out, &fds)) {
				if ((n = read(out, netbuf, sizeof(netbuf))) > 0) {
					from_out += n;
					if (write(in, netbuf, n) < 0) {
						goto done;
					}
				} else {
					goto done;
				}
			}
		} else if ((s == 0) || ((s < 0) && (errno == EINTR))) {
				continue;
		} else {
			syslog(LOG_LOW, "select %m\n");
			goto done;
		}
	}

done:
	syslog(LOG_LOW, "terminated -- %s.", log_msg);
	syslog(LOG_LOW, "%lu bytes from %s, %lu bytes from %s", from_in, src_name, from_out, dst_name);

}
