/*
in.rshd.c
*/

/*
	main channel:

	back channel\0
	remuser\0
	locuser\0
	command\0
	data

	back channel:
	signal\0

*/

#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pwd.h>
#include <grp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <net/gen/in.h>
#include <net/gen/inet.h>
#include <net/gen/netdb.h>
#include <net/gen/socket.h>
#include <net/gen/tcp.h>
#include <net/gen/tcp_io.h>
#include <net/hton.h>
#include <net/netlib.h>

#define DEBUG 0

#if DEBUG
#define where() fprintf(stderr, "%s, %d: ", __FILE__, __LINE__)
#endif

char cmdbuf[_POSIX_ARG_MAX+1], locuser[16], remuser[16];
extern char **environ;
char username[20]="USER=";
char homedir[64]="HOME=";
char shell[64]="SHELL=";
char tz[1024]="TZ=";
char *envinit[]= {homedir, shell, username, tz, "PATH=:/bin:/usr/bin", 0};
char *prog_name;
char buffer[PIPE_BUF];

#if __STDC__
#define PROTO(func, args) func args
#else
#define PROTO(func, args) func ()
#endif

PROTO (int main, (int argc, char *argv[]));
PROTO (void getstr, (char*buf, int cnt, char *err));
PROTO (void close_on_exec, (int fd));

int main(argc, argv)
int argc;
char *argv[];
{
	int result, result1;
	nwio_tcpconf_t tcpconf, err_tcpconf;
	nwio_tcpcl_t tcpconnopt;
	nwio_tcpatt_t tcpattachopt;
	tcpport_t tcpport;
	tcpport_t err_port;
	int err_fd, pds[2];
	pid_t pid, pid1, new_pg;
#if USEATTACH
	int err2_fd;
#endif
	struct passwd *pwent;
	char *cp, *buff_ptr, *TZ;
	char sig;

	prog_name= argv[0];
	if (argc != 1)
	{
		fprintf(stderr, "%s: wrong number of arguments (%d)\n",
			prog_name, argc);
		exit(1);
	}

	signal(SIGINT, SIG_DFL);
	signal(SIGQUIT, SIG_DFL);
	signal(SIGTERM, SIG_DFL);

#if DEBUG
 { where(); fprintf(stderr, "\n"); }
#endif
	result= ioctl (0, NWIOGTCPCONF, &tcpconf);
	if (result<0)
	{
		fprintf(stderr, "%s: ioctl(NWIOGTCPCONF)= %d : %s\n", 
			prog_name, errno, strerror(errno));
		exit(1);
	}
#if DEBUG
 { where(); fprintf(stderr, "\n"); }
#endif

	tcpport= ntohs(tcpconf.nwtc_remport);
	if (tcpport >= TCPPORT_RESERVED || tcpport < TCPPORT_RESERVED/2)
	{
		printf("\1%s: unprotected port (%d)\n", prog_name, tcpport);
		exit(1);
	}
	alarm(60);
	err_port= 0;
	for (;;)
	{
		char c;
		result= read(0, &c, 1);
		if (result <0)
		{
			fprintf(stderr, "%s: read= %d : %s\n", prog_name, 
				errno, strerror(errno));
		}
		if (result<1)
			exit(1);
		if (c == 0)
			break;
		err_port= err_port*10 + c - '0';
	}
	alarm(0);
	if (err_port != 0)
	{
		int n, pid, lport;

		pid= getpid();
		lport= 1;
		do {
			lport= (lport << 1) | (pid & 1);
			pid >>= 1;
		} while (lport < TCPPORT_RESERVED/2);

		n= TCPPORT_RESERVED/2;
		do
		{
			if (--lport < TCPPORT_RESERVED/2)
				lport= TCPPORT_RESERVED-1;
			err_fd= open ("/dev/tcp", O_RDWR);
			if (err_fd<0)
			{
				fprintf(stderr, "%s: open= %d : %s\n", 
					prog_name, errno, strerror(errno));
				exit(1);
			}
			close_on_exec(err_fd);
			err_tcpconf.nwtc_flags= NWTC_LP_SET | NWTC_SET_RA |
				NWTC_SET_RP | NWTC_EXCL;
			err_tcpconf.nwtc_locport= htons(lport);
			err_tcpconf.nwtc_remport= htons(err_port);
			err_tcpconf.nwtc_remaddr= tcpconf.nwtc_remaddr;

#if DEBUG
 { where(); fprintf(stderr, "\n"); }
#endif
			result= ioctl (err_fd, NWIOSTCPCONF, &err_tcpconf);
			if (result == 0) break;
			if (errno != EADDRINUSE)
			{
				fprintf(stderr, 
					"%s: ioctl(NWIOSTCPCONF)= %d : %s\n",
					prog_name, errno, strerror(errno));
				exit(1);
			}
			close(err_fd);
		} while (--n > 0);
		if (n == 0)
		{
			printf("\1can't get stderr port\n");
			exit(1);
		}

		err_tcpconf.nwtc_flags= NWTC_SHARED;
#if DEBUG
{ where(); fprintf(stderr, "\n"); }
#endif
		result= ioctl (err_fd, NWIOSTCPCONF, &err_tcpconf);
		if (result<0)
		{
			fprintf(stderr, 
				"%s: ioctl(NWIOSTCPCONF)= %d : %s\n",
				prog_name, errno, strerror(errno));
			exit(1);
		}
#if DEBUG
{ where(); fprintf(stderr, "\n"); }
#endif
		tcpconnopt.nwtcl_flags= 0;

		n= 20;
		for (;;)
		{
#if DEBUG
{ where(); fprintf(stderr, "\n"); }
#endif
			result= ioctl (err_fd, NWIOTCPCONN, &tcpconnopt);
			if (result == 0) break;
			if (errno != EAGAIN && errno != ECONNREFUSED)
			{
				fprintf(stderr,
					"%s: ioctl(NWIOTCPCONN)= %d : %s\n",
					prog_name, errno, strerror(errno));
				exit(1);
			}
			if (--n == 0) break;
			sleep(1);
#if DEBUG
{ where(); fprintf(stderr, "\n"); }
#endif
		}
#if USEATTACH
		err2_fd= open ("/dev/tcp", O_RDWR);
		close_on_exec(err2_fd);
		if (err2_fd<0)
		{
			fprintf(stderr, "%s: open= %d : %s\n", errno,
				prog_name, strerror(errno));
			exit(1);
		}
#if DEBUG
 { where(); fprintf(stderr, "\n"); }
#endif
		result= ioctl (err2_fd, NWIOSTCPCONF, &err_tcpconf);
		if (result<0)
		{
			fprintf(stderr, "%s: ioctl(NWIOSTCPCONF)= %d : %s\n",
				prog_name, errno, strerror(errno));
			exit(1);
		}
#if DEBUG
 { where(); fprintf(stderr, "\n"); }
#endif
		tcpattachopt.nwta_flags= 0;
#if DEBUG
 { where(); fprintf(stderr, "\n"); }
#endif
		result= ioctl (err2_fd, NWIOTCPATTACH, &tcpattachopt);
		if (result<0)
		{
			fprintf(stderr, "%s: ioctl(NWIOTCPATTACH)= %d : %s\n",
				prog_name, errno, strerror(errno));
			exit(1);
		}
#if DEBUG
 { where(); fprintf(stderr, "\n"); }
#endif
#endif
	}
	getstr(remuser, sizeof(remuser), "remuser");
	getstr(locuser, sizeof(locuser), "locuser");
	getstr(cmdbuf, sizeof(cmdbuf), "cmdbuf");
	setpwent();
	pwent= getpwnam(locuser);
	if (!pwent)
	{
		printf("\1Login incorrect.\n");
		exit(1);
	}
	endpwent();
	if (chdir(pwent->pw_dir) < 0)
	{
		chdir("/");
	}
#if DEBUG
 { where(); fprintf(stderr, "calling iruserok(%s, %d, %s, %s)\n", 
	inet_ntoa(tcpconf.nwtc_remaddr), 0, remuser, locuser); }
#endif
	if (iruserok(tcpconf.nwtc_remaddr, 0, remuser, locuser) < 0)
	{
		printf("\1Permission denied.\n");
		exit(1);
	}
	if (err_port)
	{
		/* Let's go to a different process group. */
		new_pg= setsid();
		pid= fork();
		if (pid<0)
		{
			if (errno != EAGAIN)
			{
				fprintf(stderr, "%s: fork()= %d : %s\n",
					prog_name, errno, strerror(errno));
			}
			printf("\1Try again.\n");
			exit(1);
		}
		if (pid)
		{
			close(0);	/* stdin */
			close(1);	/* stdout */
#if USEATTACH
			close(err_fd);	/* stderr for shell */
#endif
			dup2(2,0);
			dup2(2,1);
			for (;;)
			{
#if !USEATTACH
				if (read(err_fd, &sig, 1) <= 0)
#else
				if (read(err2_fd, &sig, 1) <= 0)
#endif
				{
#if 0
					printf("read failed: %d\n", errno);
#endif
					exit(0);
				}
				pid= 0;
#if 0
				printf("killing %d with %d\n", -new_pg, sig);
#endif
				kill(-new_pg, sig);
			}
		}
#if USEATTACH
		close(err2_fd);	/* signal channel for parent */
#endif
		result= pipe(pds);
		if (result<0)
		{
			printf("\1Can't make pipe\n");
			kill(getppid(), SIGTERM);
			exit(1);
		}
		pid1= fork();
		if (pid1<0)
		{
			if (errno != EAGAIN)
			{
				fprintf(stderr, "%s: fork()= %d : %s\n",
					prog_name, errno, strerror(errno));
			}
			printf("\1Try again.\n");
			kill(-new_pg, SIGTERM);
			exit(1);
		}
		if (pid1)
		{
			close(pds[1]);	/* write side of pipe */
			for (;;)
			{
				result= read(pds[0], buffer, sizeof(buffer));
				if (result<=0)
				{
					kill(pid, SIGTERM);
					exit(0);
				}
				buff_ptr= buffer;
				while (result>0)
				{
					result1= write (err_fd, buff_ptr,
						result);
					if (result1 <= 0)
					{
						fprintf(stderr, 
						"%s: write()= %d : %s\n",
							prog_name, errno,
							strerror(errno));
						kill(-new_pg, SIGTERM);
						exit(1);
					}
					result -= result1;
				}
			}
		}
		close(err_fd);	/* file descriptor for error channel */
		close (pds[0]);	/* read side of pipe */
		dup2(pds[1], 2);
		close (pds[1]);	/* write side of pipe */
	}
	if (*pwent->pw_shell == '\0')
		pwent->pw_shell= "/bin/sh";
#if __minix_vmd
	initgroups(pwent->pw_name, pwent->pw_gid);
#endif
	setgid(pwent->pw_gid);
	setuid(pwent->pw_uid);
	TZ=getenv("TZ");
	environ= envinit;
	strncat(homedir, pwent->pw_dir, sizeof(homedir)-6);
	strncat(shell, pwent->pw_shell, sizeof(shell)-7);
	strncat(username, pwent->pw_name, sizeof(username)-6);
	if (TZ)
		strncat(tz, TZ, sizeof(tz)-4);
	else
		envinit[3]= NULL;

	cp= strrchr(pwent->pw_shell, '/');
	if (cp)
		cp++;
	else
		cp= pwent->pw_shell;

	if (!err_port)
		dup2(1, 2);
	write(1, "\0", 1);

	execl(pwent->pw_shell, cp, "-c", cmdbuf, 0);
	close(2);
	open("/dev/tty", O_RDWR);
	fprintf(stderr, "%s: execl(%s, %s, .., %s)= %d : %s\n", prog_name,
		pwent->pw_shell, cp, cmdbuf, errno, strerror(errno));
	kill(getppid(), SIGTERM);
	exit(1);
}

void getstr(buf, cnt, err)
char *buf;
int cnt;
char *err;
{
	char c;

	do
	{
		if (read(0, &c, 1) != 1)
			exit(1);
		*buf++ = c;
		if (--cnt == 0)
		{
			printf("\1%s too long", err);
			exit(1);
		}
	} while (c != 0);
}

void close_on_exec(fd)
int fd;
{
	(void) fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
}
