/*
 * NAME:
 *	SSLrsh - rsh over SSL
 *
 * DESCRIPTION:
 *	This is the client for my SSLrshd.  It is a simplified version
 *	of the BSD rsh.  Unlike many SSL applications, in this one,
 *	we make the SSL_accept() call and therefor must always supply
 *	a valid certificate - after all that's the whole point.
 *
 *	By default, we do not ask the server for his certificate.  The
 *	-v option is equivalent to -z verify=3 and ensures we receive
 *	a valid certificate from the server.  This should result in
 *	output like:
 *
 *.nf
 *	SSLrsh[17121]: please note the pid.
 *	SSLrsh[17121]: SSL cert verify: depth=0,xs=/C=AU/...\
 *		/CN=zen.quick.com.au/Email=root@zen.quick.com.au
 *	SSLrsh[17121]: SSL cert verify: depth=1,xs=/C=AU/...\
 *		/CN=Quick CA/Email=certs@quick.com.au
 *.fi
 *
 *	The above output can be suppressed with the -q option.
 *
 * AUTHOR:
 *	Hacked from BSD rsh by Simon J. Gerraty <sjg@quick.com.au>
 */
/*-
 * Copyright (c) 1996 Simon J. Gerraty, all rights reserved.
 * Copyright (c) 1983, 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

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

#ifndef lint
/*static char sccsid[] = "from: @(#)rsh.c	5.24 (Berkeley) 7/1/91";*/
static char rcsid[] = "$Id: rsh.c,v 1.19 1997/02/08 07:12:31 sjg Exp $";
#endif /* not lint */

/*
 * $Source: /local/src/master/sjg/ssl/bin/rsh/rsh.c,v $
 * $Header: /local/src/master/sjg/ssl/bin/rsh/rsh.c,v 1.19 1997/02/08 07:12:31 sjg Exp $
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#include <netinet/in.h>
#include <netdb.h>

#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <varargs.h>
#include <unistd.h>
#include "pathnames.h"

#ifdef DEBUG
#include <syslog.h>
#endif

#ifndef SA_NOCLDSTOP
# include "sigact.h"				/* <sjg> fake POSIX signals */
#endif

#include "sslfd.h"

#ifdef sun
#include "sunos.h"
#endif

/* use defaults in ssld.c */
char		*cipher_list = 0;
char		*cert_file = 0;
char		*key_file = 0;
int		verify_flag = 0;	/* We don't care about server auth */

/*
 * rsh - remote shell
 */
extern int errno;
int rfd2 = -1;
int Verbose = 1;

#define MYNAME "SSLrsh"
extern char *MyId;

void
sendsig(signo)
	char signo;
{
	if (rfd2 > 0)
		(void) write_net(rfd2, &signo, 1);
	else
		exit(1);
}

main(argc, argv)
	int argc;
	char **argv;
{
	extern char *optarg;
	extern int optind;
	struct passwd *pw;
	struct servent *sp;
	sigset_t mask, omask;
	struct sigaction oldsa, newsa;
	int port = 0, argoff, asrsh, ch, dflag, nflag, one, pid, rem, uid;
	int *efdp = &rfd2;
	register char *p;
	char *cp, *args, *copyargs();
	const char *user, *host;
	
	argoff = asrsh = dflag = nflag = 0;
	one = 1;
	host = user = NULL;

	setMyId(argv[0]);
#ifdef DEBUG
	openlog(MYNAME, LOG_PID | LOG_ODELAY, LOG_DAEMON);
#endif
	/* if called as something other than "rsh", use it as the host name */
	if (p = strrchr(argv[0], '/'))
		++p;
	else
		p = argv[0];
	if (strcmp(p, MYNAME))
		host = p;
	else
		asrsh = 1;

	/* handle "rsh host flags" */
	if (!host && argc > 2 && argv[1][0] != '-') {
		host = argv[1];
		argoff = 1;
	}
#ifdef NO_RFD2
	efdp = 0;
#else
	efdp = &rfd2;
#endif

#define	OPTIONS	"dl:enz:p:vq"
#define OPTARG_VAL(str, cp, key) ((cp = strchr(str, '=')) && strncmp(str, key, cp - str) == 0 && ++cp)

	while ((ch = getopt(argc - argoff, argv + argoff, OPTIONS)) != EOF)
		switch(ch) {
		case 'e':
#ifdef NO_RFD2
			efdp = &rfd2;
#else
			efdp = 0;
#endif
			break;
		case 'q':
			Verbose = 0;
			break;
		case 'v':
			/* short hand for -z verify=3 */
			verify_flag=3;		/* must see valid sever cert */
			break;
		case 'p':
			port = htons(atoi(optarg));
			break;
		case 'z':
			/*
			 * for comaptability with SSLtelnet
			 */
			if (OPTARG_VAL(optarg, cp, "cert"))
				cert_file = cp;
			else if (OPTARG_VAL(optarg, cp, "cipher"))
				cipher_list = cp;
			else if (OPTARG_VAL(optarg, cp, "key"))
				key_file = cp;
			else if (OPTARG_VAL(optarg, cp, "verify"))
				verify_flag = atoi(cp);
			break;
		case 'd':
			Verbose |= 2;
			dflag = 1;
			break;
		case 'l':
			user = optarg;
			break;
		case 'n':
			nflag = 1;
			break;
		case '?':
		default:
			usage();
		}
	optind += argoff;

	/* if haven't gotten a host yet, do so */
	if (!host && !(host = argv[optind++]))
		usage();

	if (!(pw = getpwuid(uid = getuid()))) {
		(void)fprintf(stderr, "%s: unknown user id.\n", MYNAME);
		exit(1);
	}
	if (!user)
		user = pw->pw_name;
	
	/*
	 * if no further arguments, must have been called as rlogin.
	 * <sjg> but we don't support that, so we run her shell...
	 * So getpwuid() call moved to above.
	 */
	if (!argv[optind]) {
		optind--;
		argv[optind] = pw->pw_shell;
	}
	argc -= optind;
	argv += optind;

	args = copyargs(argv);

	if (!port) {
		sp = NULL;
		if (sp == NULL)
			sp = getservbyname("SSLshell", "tcp");
		if (sp == NULL) {
			(void)fprintf(stderr, "%s: SSLshell/tcp: unknown service.\n", MYNAME);
			exit(1);
		}
		port = sp->s_port;
	}
	if (Verbose && verify_flag > 0)
		fprintf(stderr, "%s: please note the pid.\n", MyId);

	if (sslc_init(cert_file, key_file, cipher_list, verify_flag,
		      Verbose) < 0)
		exit(1);

	rem = ssl_rcmd((char **)&host, port, pw->pw_name, user, args, efdp);

	if (rem < 0)
		exit(1);

	/*
	 * <sjg> POSIX for portability
	 */
	sigemptyset(&mask);
	sigaddset(&mask,SIGINT);
	sigaddset(&mask,SIGQUIT);
	sigaddset(&mask,SIGTERM);
	(void) sigprocmask(SIG_BLOCK, &mask, &omask);

	newsa.sa_handler = sendsig;
	newsa.sa_flags = 0;
	sigaction(SIGINT, 0, &oldsa);
	if (oldsa.sa_handler != SIG_IGN)
		sigaction(SIGINT, &newsa, 0);
	sigaction(SIGQUIT, 0, &oldsa);
	if (oldsa.sa_handler != SIG_IGN)
		sigaction(SIGQUIT, &newsa, 0);
	sigaction(SIGTERM, 0, &oldsa);
	if (oldsa.sa_handler != SIG_IGN)
		sigaction(SIGTERM, &newsa, 0);

	if (!nflag) {
		pid = fork();
		if (pid < 0) {
			(void)fprintf(stderr,
			    "%s: fork: %s.\n", MYNAME, strerror(errno));
			exit(1);
		}
	}
	talk(nflag, &omask, pid, rem);

	if (!nflag)
		(void)kill(pid, SIGKILL);
	exit(0);
}

int
talk(nflag, omask, pid, rem)
	int nflag, pid;
	sigset_t *omask;
	register int rem;
{
	register int cc, wc;
	register char *bp;
	int nfd;
	char buf[BUFSIZ];
	fd_set ready, readfrom, rembits;
	int sslunread, errunread = 0;
	nfd = 16;

	FD_ZERO(&rembits);
	
	if (!nflag && pid == 0) {
		if (rfd2 > 0)
			(void)ssl_close(rfd2);

reread:		errno = 0;
		if ((cc = read(0, buf, sizeof buf)) <= 0)
			goto done;
		bp = buf;

rewrite:
		FD_SET(rem, &rembits);
		if (select(nfd, 0, &rembits, 0, 0) < 0) {
			if (errno != EINTR) {
				(void)fprintf(stderr,
					      "%s: select: %s.\n", MYNAME,
					      strerror(errno));
				exit(1);
			}
			goto rewrite;
		}
		if (FD_ISSET(rem, &rembits) == 0)
			goto rewrite;
		wc = write_net(rem, bp, cc);
		if (wc < 0) {
			if (errno == EWOULDBLOCK)
				goto rewrite;
			goto done;
		}
		bp += wc;
		cc -= wc;
		if (cc == 0)
			goto reread;
		goto rewrite;
done:
		(void)shutdown(rem, 1);
		exit(0);
	}

	(void) sigprocmask(SIG_SETMASK, omask, 0);

	FD_ZERO(&readfrom);
	FD_SET(rem, &readfrom);
	if (rfd2 > 0)
		FD_SET(rfd2, &readfrom);
	do {
		/*
		 * we need to avoid calling select() if we have
		 * unprocessed data within SSL
		 */
		sslunread = ssl_pending(0);
		if (rfd2 > 0)
			errunread = ssl_pending(rfd2);
		
		if ((sslunread | errunread) == 0) {
			ready = readfrom;
			if (select(nfd, &ready, 0, 0, 0) < 0) {
				if (errno != EINTR) {
					(void)fprintf(stderr,
						      "%s: select: %s.\n", MYNAME,
						      strerror(errno));
					exit(1);
				}
				continue;
			}
		}
		if (errunread || (rfd2 > 0 && FD_ISSET(rfd2, &ready))) {
			errno = 0;
			cc = read_net(rfd2, buf, sizeof buf);
			if (cc <= 0) {
				if (errno != EWOULDBLOCK)
					FD_CLR(rfd2, &readfrom);
			} else
				(void)write(2, buf, cc);
		}
		if (sslunread || FD_ISSET(rem, &ready)) {
			errno = 0;
			cc = read_net(rem, buf, sizeof buf);
			if (cc <= 0) {
				if (errno != EWOULDBLOCK)
					FD_CLR(rem, &readfrom);
			} else
				(void)write(1, buf, cc);
		}
	} while (FD_ISSET(rem, &readfrom) ||
		 (rfd2 > 0 && FD_ISSET(rfd2, &readfrom)));
}

char *
copyargs(argv)
	char **argv;
{
	register int cc;
	register char **ap, *p;
	char *args, *malloc();

	cc = 0;
	for (ap = argv; *ap; ++ap)
		cc += strlen(*ap) + 1;
	if (!(args = malloc((u_int)cc))) {
		(void)fprintf(stderr, "%s: %s.\n", MYNAME, strerror(ENOMEM));
		exit(1);
	}
	for (p = args, ap = argv; *ap; ++ap) {
		(void)strcpy(p, *ap);
		for (p = strcpy(p, *ap); *p; ++p);
		if (ap[1])
			*p++ = ' ';
	}
	return(args);
}

usage()
{
	(void)fprintf(stderr,
		      "usage: %s [-qvnd][-z sslopt][-l login] host [command]\n",
		      MYNAME);
	exit(1);
}
