/*
 * Copyright (c) 1983 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by the University of California, Berkeley.  The name of the
 * University may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

/*
 * Modified by S. Levy, Geometry Group/Univ of Minn, April 1989, to:
 *  enlarge data buffers (32K) for speed,
 *  allow adjustable TCP send/receive buffers if the system supports it,
 *  use getopt (!),
 *  call rlogin from SGI's funny /usr/bsd directory too,
 *  allow specifying /bin/sh commands to be hooked directly onto
 *   stdin/out/error streams of remote process, saving copying & pipe overhead.
 */

#ifndef lint
char copyright[] =
"@(#) Copyright (c) 1983 The Regents of the University of California.\n\
 All rights reserved.\n";
#endif /* not lint */

#ifndef lint
static char sccsid[] = "@(#)rsh.c	5.7 (Berkeley) 9/20/88";
#endif /* not lint */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/wait.h>

#include <netinet/in.h>

#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <pwd.h>
#include <netdb.h>

/*
 * rsh - remote shell
 */
/* VARARGS */
int	error();
char	*index(), *rindex(), *malloc(), *getpass(), *strcpy();

struct	passwd *getpwuid();

int	errno;
int	options;
int	rfd2;
int	rem;
int	nflag;
int	sendsig(), await();

int	inpid = 0, outpid = 0, errpid = 0;

#define	mask(s)	(1 << ((s) - 1))

main(argc, argv0)
	int argc;
	char **argv0;
{
	int pid;
	char *host, *cp, **ap, *args, **argv = argv0, *user = 0;
	register int cc;
	int asrsh = 0;
	struct passwd *pwd;
	int readfrom, ready;
	int one = 1;
	struct servent *sp;
	int omask;
	char *incmd = NULL, *outcmd = NULL, *errcmd = NULL;
	int tcpbufsize = 0;
	char buf[32768];
	extern int getopt();
	extern char *optarg;
	extern int optind, opterr;

	host = rindex(argv[0], '/');
	if (host)
		host++;
	else
		host = argv[0];
	if (!strcmp(host, "rsh")) {
		host = *++argv, --argc;
		asrsh = 1;
	}
	opterr = 0;
	while((cc = getopt(argc, argv, "l:ndb:I:O:E:Lwe8")) != EOF) {
	    switch(cc) {
	    case 'l': user = optarg; break;
	    case 'n': nflag++; break;
	    case 'd': options |= SO_DEBUG; break;
	    case 'I': incmd = optarg; break;
	    case 'O': outcmd = optarg; break;
	    case 'E': errcmd = optarg; break;

#ifdef SO_SNDBUF
	    case 'b': tcpbufsize = atoi(optarg); break;
#else /*!SO_SNDBUF*/
	    case 'b': /* Fall into '?' */
#endif /*!SO_SNDBUF*/

	    case '?': goto usage;
	    /* Ignore L, w, e, 8; pass those to rlogin */
	    }
	}
	if (host == NULL) {
	    usage:
		fprintf(stderr,
#ifdef SO_SNDBUF
"Usage: rsh host [-l login] [-n] [-d] [-b tcpbufsize]\n\
	[-I 'stdin-cmd'] [-O 'stdout-cmd'] [-E 'stderr-cmd'] command args ...\n\
where stdin-cmd if present sends its stdout to remote command's stdin, &c.\n");
#else /*!SO_SNDBUF*/
"Usage: rsh host [-l login] [-n] [-d]\n\
	[-I 'stdin-cmd'] [-O 'stdout-cmd'] [-E 'stderr-cmd'] command args ...\n\
where stdin-cmd if present sends its stdout to remote command's stdin, &c.\n");
#endif /*!SO_SNDBUF*/
		exit(1);
	}

	argv += optind;
	if (argv[0] == 0) {
		if (asrsh)
			*argv0 = "rlogin";
#ifdef sgi
		execv("/usr/bsd/rlogin", argv0);
#endif /*sgi*/
		execv("/usr/ucb/rlogin", argv0);
		perror("/usr/ucb/rlogin");
		exit(1);
	}
	pwd = getpwuid(getuid());
	if (pwd == 0) {
		fprintf(stderr, "who are you?\n");
		exit(1);
	}
	cc = 0;
	for (ap = &argv[0]; *ap; ap++)
		cc += strlen(*ap) + 1;
	cp = args = malloc(cc);
	for (ap = &argv[0]; *ap; ap++) {
		(void) strcpy(cp, *ap);
		while (*cp)
			cp++;
		if (ap[1])
			*cp++ = ' ';
	}
	sp = getservbyname("shell", "tcp");
	if (sp == 0) {
		fprintf(stderr, "rsh: shell/tcp: unknown service\n");
		exit(1);
	}
        rem = rcmd(&host, sp->s_port, pwd->pw_name,
	    user ? user : pwd->pw_name, args, &rfd2);
        if (rem < 0)
                exit(1);
	if (rfd2 < 0) {
		fprintf(stderr, "rsh: can't establish stderr\n");
		exit(2);
	}
	if (options & SO_DEBUG) {
		if (setsockopt(rem, SOL_SOCKET, SO_DEBUG, &one, sizeof (one)) < 0)
			perror("setsockopt (stdin)");
		if (setsockopt(rfd2, SOL_SOCKET, SO_DEBUG, &one, sizeof (one)) < 0)
			perror("setsockopt (stderr)");
	}
#ifdef SO_SNDBUF
	if (tcpbufsize > 0) {
		if (setsockopt(rem, SOL_SOCKET, SO_SNDBUF, &tcpbufsize, sizeof(int)) < 0
		  || setsockopt(rem, SOL_SOCKET, SO_RCVBUF, &tcpbufsize, sizeof(int)) < 0)
			perror("rsh: warning, can't set SO_SNDBUF/RCVBUF");
	}
#endif /*SO_SNDBUF*/

	(void) setuid(getuid());
#ifndef SVR3
	omask = sigblock(mask(SIGINT)|mask(SIGQUIT)|mask(SIGTERM));
#endif /*BSD*/
	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
		signal(SIGINT, sendsig);
	if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
		signal(SIGQUIT, sendsig);
	if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
		signal(SIGTERM, sendsig);

	(void) signal(SIGCHLD, await);

	ioctl(rfd2, FIONBIO, &one);
	ioctl(rem, FIONBIO, &one);

	(void) fcntl(rem, F_SETFD, 1);
	(void) fcntl(rfd2, F_SETFD, 1);

	if (incmd != NULL) {
		if(nflag) {
			(void) close(0);
			(void) open("/dev/null", 0);
		}
		inpid = attach(incmd, 1, rem);
	} else if (nflag) {
		shutdown(rem, 1);
	}

	if (outcmd != NULL)
		outpid = attach(outcmd, 0, rem);
	if (errcmd != NULL)
		errpid = attach(errcmd, 0, rfd2);

	pid = -1;
	if (nflag == 0 && incmd == NULL) {
		pid = fork();
		if (pid < 0) {
			perror("fork");
			exit(1);
		}
	}
        if (pid == 0) {
		char *bp; int rembits, wc;
#ifdef SVR3
		(void) signal(SIGINT, SIG_IGN);
		(void) signal(SIGQUIT, SIG_IGN);
		(void) signal(SIGTERM, SIG_IGN);
#endif /*!BSD*/
		(void) close(rfd2);
	reread:
		errno = 0;
		cc = read(0, buf, sizeof buf);
		if (cc <= 0)
			goto done;
		bp = buf;
	rewrite:
		rembits = 1<<rem;
		if (select(16, 0, &rembits, 0, 0) < 0) {
			if (errno != EINTR) {
				perror("select");
				exit(1);
			}
			goto rewrite;
		}
		if ((rembits & (1<<rem)) == 0)
			goto rewrite;
		wc = write(rem, bp, cc);
		if (wc < 0) {
			if (errno == EWOULDBLOCK)
				goto rewrite;
			goto done;
		}
		cc -= wc; bp += wc;
		if (cc == 0)
			goto reread;
		goto rewrite;
	done:
		(void) shutdown(rem, 1);
		exit(0);
	}
#ifndef SVR3
	sigsetmask(omask);
#endif /*BSD*/
	readfrom = (errcmd == NULL) ? (1<<rfd2) : 0;
	if (outcmd == NULL) readfrom |= (1<<rem);
	do {
		ready = readfrom;
		if (select(16, &ready, 0, 0, 0) < 0) {
			if (errno != EINTR) {
				perror("select");
				exit(1);
			}
			continue;
		}
		if (ready & (1<<rfd2)) {
			errno = 0;
			cc = read(rfd2, buf, sizeof buf);
			if (cc <= 0) {
				if (errno != EWOULDBLOCK)
					readfrom &= ~(1<<rfd2);
			} else
				(void) write(2, buf, cc);
		}
		if (ready & (1<<rem)) {
			errno = 0;
			cc = read(rem, buf, sizeof buf);
			if (cc <= 0) {
				if (errno != EWOULDBLOCK)
					readfrom &= ~(1<<rem);
			} else
				(void) write(1, buf, cc);
		}
        } while (readfrom);
	if (pid > 0)
		(void) kill(pid, SIGKILL);
	(void) signal(SIGCHLD, SIG_IGN);
	while(inpid || outpid || errpid)
		await();
	exit(0);
}

sendsig(signo)
	char signo;
{

	(void) write(rfd2, &signo, 1);
}

attach(cmd, cmdfd, remfd)
	char *cmd;
	int cmdfd, remfd;
{
	register int kid;
	int off = 0;

	switch(kid = vfork()) {
	case 0:		/* Child, run the command */
		(void) ioctl(remfd, FIONBIO, &off);
		if(dup2(remfd, cmdfd) < 0) {
			perror("rsh: can't redirect");
		} else if(execl("/bin/sh", "sh", "-c", cmd, NULL) < 0) {
			perror("rsh: can't exec /bin/sh");
		}
		/* Failed one way or another */
		(void) shutdown(remfd, cmdfd==0? 1 : 0);
		_exit(1);
		/*NOTREACHED*/

	case -1:
		perror("rsh: can't vfork");
		exit(2);
		/*NOTREACHED*/

	default:	/* Parent */
		return(kid);
	}
}

/* SIGCHLD handler */
await()
{
	union wait status;
	register int pid;
	static char sigpipe = SIGPIPE;

	pid = wait3(&status, WNOHANG, NULL);
	if(pid == 0) {
		return;
	} else if(pid == inpid) {
		inpid = 0;
		(void) shutdown(rem, 1);
	} else if(pid == outpid) {
		outpid = 0;
		(void) shutdown(rem, 0);
		write(rfd2, &sigpipe, 1);
	} else if(pid == errpid) {
		errpid = 0;
		(void) shutdown(rfd2, 0);
	}
}
