/*
 * Copyright (c) 1998-2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 */

/*
 * rexec-gw.c, a Remote Exec proxy designed to work with TIS fwtk
 *
 * (C) Copyright 1998-2000 by ArkanoiD <ark@eltex.ru>
 *
 */

#include <stdio.h>
#include <stdlib.h>  
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include <signal.h>
#include <unistd.h> 
#include <time.h> 
#include <sys/types.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <net/if.h>
#ifndef	 SIOCATMARK
#include <sys/sockio.h>
#endif
#include "firewall.h"
#include "fwfunc.h"
#include "firewall2.h"
#include "auth.h"

static	char*	moduleId ATTR_UNUSED = "$Id: rexec-gw.c,v 1.11 2007/09/10 02:49:13 arkenoi Exp $";
extern 	int 	h_errno;

#define OP_DENY 001
#define OP_LOG  002
#define OP_AUTH 004		/* never used here */
#define OP_XTND 010

static	time_t	offtime;
static	int	transparent 	= 0;
static	int	extendperm 	= 0;
static	int	log 		= 0;

				/* client stdout fd is our fd 0 */
static	int	c_stderr = -1;	/* client stderr fd 		*/	
static	int	s_stdout = -1;	/* server stdout fd 		*/
static	int	s_stderr = -1;	/* server stderr fd 		*/

static	Cfg	*confp;
static	char	*rhost;
static	char	**validests = 	(char **)0;
static	char	**validusers = 	(char **)0;
static	char	**authusers = 	(char **)0;
static	char	uname[512];
static	char	upass[512];
static  int     cdscp = 0;
static  int     sdscp = 0;
static	int	user_limit = AUTH_USIZ;
static	int	pass_limit = AUTH_PWSIZ;
static	int	cmd_limit  = 255;
static	char	separator[2] = "@";

static	char	cmd[8192];

static	fwparm	options[] = {
	{	FWPARM_LIST,	"-dest",	(char*) &validests	},
	{       FWPARM_LIST,    "-user",        (char*) &validusers     },
	{       FWPARM_LIST,    "-ausers",      (char*) &authusers	},
	{	FWPARM_STRING,  "-plug-to",     proxy_stats.dst         },
	{	FWPARM_PORT  ,  "-port",        (char*)&proxy_stats.port},
	{	FWPARM_STRING,  "-authuser",    proxy_stats.authuser    },
	{	FWPARM_BOOL,    "-transparent", (char*) &transparent    },
	{	FWPARM_BOOL,    "-extnd",       (char*) &extendperm     },
	{	FWPARM_CHAR,    "-separator",           separator       },
	{	FWPARM_BOOL,    "-log",       	(char*) &log		},
	{	FWPARM_DSCP,    "-client-dscp", (char*) &cdscp          },
	{	FWPARM_DSCP,    "-server-dscp", (char*) &sdscp          },
	{       0,              0,              0                       } };

extern	int auth_perm(Cfg*,char*,char*,char*,char**);

#if !defined(linux) && !defined(SOLARIS) && !defined(HPUX)
/*
 *      Assume we are running on BSD-like system, otherwise rexec() is
 *	declared in <netdb.h>
 */
extern 	int rexec(char**,int,char*,char*,char*,int*);
#endif

#ifndef SCO5
static 	void 
trap_sigurg(int dummy)
{
	int	atmark;
	char	buf[512];
	char	c;

	while(1) {
		if(ioctl(s_stdout,SIOCATMARK,(char *)&atmark) < 0)
			return;
		if(atmark)
			break;
		read(s_stdout,buf,sizeof(buf));
	}
	if(recv(s_stdout,(char *)&c,sizeof(c),MSG_OOB) < 0)
		return;
	send(0,(char *)&c,sizeof(c),MSG_OOB);
	signal(SIGURG,trap_sigurg);
}
#endif

void rsh_error(dest,text)
char	*dest;
char	*text;
{
	write(0,"\0x01",1);
	write(0,text,strlen(text));
	proxy_exit();
}

void
get_asciiz(buf,maxbytes)
char 	*buf;
int	maxbytes;
{
	while(1) {
		maxbytes--;
		if (soread(0,buf,1) != 1) {
			syslog(LLEV,"protocol failure in circuit setup; error reading client data");
			proxy_exit();
		}
		if (maxbytes < 0) {
			syslog(LLEV,"fwtksyserr: buffer overrun on client data");
			rsh_error("none","protocol entry too long\r\n");
		}
		if (*buf == '\0')
			break;
		buf++;
	}
	return;
}

int
main(int argc, char *argv[])
{
	char			buffer[1024 *4];
	uint16_t		x;
	socklen_t		l;
	struct	sockaddr_in	c_addr;
	char			*p;
	struct	timeval		timo;
	fd_set			rset;
	static	char		tokbuf[1024];
	static	char		*tokav[56];
	int			tokac;
	int			opt = 1;

	proxy_init(argc,argv);
	proxy_stats.port = 512;

	user_limit = proxy_conf_int(proxy_confp,"user-limit",1,
				    sizeof(uname)-1,user_limit);

	pass_limit  = proxy_conf_int(proxy_confp,"password-limit",1,
				    sizeof(upass)-1,pass_limit);

	cmd_limit  = proxy_conf_int(proxy_confp,"command-limit",1,
				    sizeof(cmd)-1,user_limit);

	if (cdscp)
		 proxy_set_dscp(0,cdscp);

	if ((confp=proxy_conf_hosts(proxy_confp,proxy_stats.rladdr,
				    proxy_stats.riaddr)))
		proxy_parse_options(confp,options);
	else
		proxy_exit();

	memset(buffer,0,sizeof(buffer));

	l = sizeof(c_addr);
	if (getpeername(0, (struct sockaddr *)&c_addr, &l) < 0) {
		syslog(LOG_ERR, "fwtksyserr: getpeername(): %s", strerror(errno));
		exit(1);
	}
	if ((x=ntohs(c_addr.sin_port)) < 1024) {
		syslog(LLEV,"deny host=%.512s/%.20s connect from privileged port %d",proxy_stats.rladdr,proxy_stats.riaddr,x);
		exit(1);
	}
/*
	Get remote stderr and dup it to ours, we will send all diagnostic
	messages there.
	If there is none, keep our old fd 2 (should be the same as 0)
*/

#ifdef	SO_KEEPALIVE
	setsockopt(0,SOL_SOCKET,SO_KEEPALIVE,(char *)&opt,sizeof(opt));
#endif

	get_asciiz(buffer,16);
	if (buffer[0] != '\0') {
		x = atoi(buffer);
		if (x < 1024) {
			syslog(LLEV,"securityalert: back connection requested to privileged port %d",x);
			proxy_exit();
		}
		if (x < 512) {
			syslog(LLEV,"securityalert: back connection requested to reserved port %d",x);
			proxy_exit();
		}
		proxy_update_operation("CALLBACK");
		if ((c_stderr = conn_server(proxy_stats.riaddr,x,0,(char *)0)) <0) {
			syslog(LLEV,"protocol failure in circuit setup: cannot estabilish back connection to client");
			proxy_exit();
		}
		if (cdscp)
			proxy_set_dscp(c_stderr,cdscp);
		close(2);
		dup(c_stderr);
	}

	proxy_update_operation("AUTHENTICATION");
	get_asciiz(uname,user_limit);
	get_asciiz(upass,pass_limit);
	get_asciiz(cmd,cmd_limit);

	tokac = enargv(cmd,tokav,56,tokbuf,sizeof(tokbuf));
	if (strrchr(tokav[0],'/'))
		strlcpy(buffer,strrchr(tokav[0],'/')+1,sizeof(buffer));
	else
		strlcpy(buffer,tokav[0],sizeof(buffer));

	if (!*proxy_stats.dst) {
		if (transparent)
			proxy_get_transparent_dst(proxy_stats.dst,&proxy_stats.port);
		else if ((p = strrchr(uname,*separator))) {
			*p++ = '\0';
			if (*p == '\0') {
				syslog(LLEV,"NULL destination");
				rsh_error("none","NULL destination\r\n");
			} else
				strlcpy(proxy_stats.dst,p,sizeof(proxy_stats.dst));
		} else {
			syslog(LLEV,"permission denied to run command on firewall");
			rsh_error("none","Permission denied.\r\n");
		}
	}

	if (searchlist(uname,validusers)) {
		syslog(LLEV,"deny host=%.512s/%.20s access for user %s",
		       proxy_stats.rladdr,proxy_stats.riaddr,uname);
		rsh_error("none","Permission denied.\r\n");
	}

	if (proxy_check_dest(validests,extendperm)) 
		rsh_error(proxy_stats.dst,"Permission denied.\r\n");

	rhost=proxy_stats.dst; 

	if (extendperm) {
		char *nav[3] = { uname, cmd, NULL };

		switch (auth_perm(confp, proxy_stats.authuser, "rexec-gw", 
				  proxy_stats.dst, nav )) {
		case -1:
			syslog(LLEV,"no match for rexec-gw user=%.100s in netperm-table",proxy_stats.authuser);
			proxy_exit();
		case 1: 
			syslog(LLEV,"deny host=%.256s/%.20s connect to %.256s user=%.64s cmd=\"%.256s\"",proxy_stats.rladdr,proxy_stats.riaddr,proxy_stats.dst,proxy_stats.authuser,cmd);
			exit(1);
		}
	}
	if (log)
		syslog(LLEV,"%.256s/%.20s: %.64s@%.64s: %.64s",
	       	       proxy_stats.rladdr,proxy_stats.riaddr,
		       uname, proxy_stats.dst,cmd);

	proxy_update_operation(cmd);
	if ((s_stdout = rexec(&rhost,proxy_stats.port,uname,upass,cmd,
			     c_stderr!=-1 ? &s_stderr : 0)) < 0) {
		syslog(LLEV,"rexec failed");
		proxy_exit();
	}
	if (sdscp) {
		proxy_set_dscp(s_stdout,sdscp);
		if (s_stderr != -1)
			proxy_set_dscp(s_stderr,sdscp);
	}
	proxy_chroot_setugid();
	write(0,"\0",1);

	setsockopt(s_stdout,SOL_SOCKET,SO_OOBINLINE,(char *) &opt,sizeof(opt));
		   

#ifndef SCO5
	signal(SIGURG,trap_sigurg);
#endif

	set_oob_notification(s_stdout);

	while (1) {
		int	n;
		FD_ZERO(&rset);
		FD_SET(0,&rset);
		FD_SET(s_stdout,&rset);
		if (s_stderr != -1)
			FD_SET(s_stderr,&rset);
		if (c_stderr != -1)
			FD_SET(c_stderr,&rset);

		timo.tv_sec =  proxy_timeout;
		timo.tv_usec = 0;

		if ((n=select(32,&rset,NULL,NULL,&timo)) < 0) {
			if (errno != EBADF && errno != EINTR)
				syslog(LLEV,"fwtksyserr: select: %s", strerror(errno));
			exit(1);
		} else {
			if(n == 0) {
				syslog(LLEV,"Network timeout signal after %d seconds",proxy_timeout);
				proxy_exit();
			}
		}

		if (FD_ISSET(0,&rset)) {
/*
 * 		we are going to be truly bidirectional here? 
 *		linux manual says it does not work, but it does..
 */
			if((n = read(0,buffer,sizeof(buffer))) <= 0)
				break;
			if(write(s_stdout,buffer,n) != n) 
				break;
			proxy_stats.outbytes += n;
		}

		if (FD_ISSET(s_stdout,&rset)) {
			if((n = read(s_stdout,buffer,sizeof(buffer))) <= 0)
				break;
			if(write(0,buffer,n) != n)
				break;
			proxy_stats.inbytes += n;
		}

		if (FD_ISSET(c_stderr,&rset)) {
/*
 *		said to be useful
 */
			if((n = read(c_stderr,buffer,sizeof(buffer))) <= 0)
				break;
			if(write(s_stderr,buffer,n) != n)
				break;

			proxy_stats.outbytes += n;
		}

		if (FD_ISSET(s_stderr,&rset)) {
/*
 *		we do not terminate if server closes stderr connection;
 *		there could still be some data on the master socket
 */
			if((n = read(s_stderr,buffer,sizeof(buffer))) > 0) {
				if(write(c_stderr,buffer,n) != n)
					break;
				proxy_stats.inbytes += n;
			}
		}
	}
	time(&offtime);
	syslog(LLEV,"exit host=%.512s/%.20s dest=%.128s in=%d out=%d user=%.64s duration=%u cmd=\"%.256s\"",
                     proxy_stats.rladdr,proxy_stats.riaddr,proxy_stats.dst,proxy_stats.inbytes,proxy_stats.outbytes,proxy_stats.authuser,
		     (int) (offtime - proxy_stats.start_time),cmd);
	exit(0);
}


