/*
 *
 * sst: Simple Ssl Tunneling.
 *
 *    -	yet another generic SSL client/server/forwarder.
 *
 *    -	can be used as a drop-in program for encoding
 *	or decoding networked I/O.
 *
 *    - can be used as an SSL preprocessor/decoder for inetd servers.
 *
 * NOTE:
 *	netcat (nc) is needed to handle networking I/O
 *	when operating as a standalone client, as a
 *	standalone server, or as an inetd forwarder.
 *
 ***
 *
 * Written by P Kern <pkern@utcc.utoronto.ca>.
 *
 * Copyright (c) 2000 University of Toronto. All rights reserved.
 * Anyone may use or copy this software, except that this copyright
 * notice remain intact, and that credit is given where it is due.
 * The University of Toronto and the author make no warranty and
 * accept no liability for this software.
 *
 ***
 *
 * Partly inspired by:
 *
 *   stunnel       Universal SSL tunnel
 *   Copyright (c) 1998-2000 Michal Trojnara <Michal.Trojnara@centertel.pl>
 *                 All Rights Reserved
 *
 ***
 *
 * SSL code usage based on openssl/demos/ssl/{cli,serv}.cpp:
 * 
 * from cli.cpp:
 *	/* cli.cpp  -  Minimal ssleay client for Unix
 *	   30.9.1996, Sampo Kellomaki <sampo@iki.fi> **
 *	/* mangled to work with SSLeay-0.9.0b and OpenSSL 0.9.2b
 *	   Simplified to be even more minimal
 *	   12/98 - 4/99 Wade Scholine <wades@mail.cybg.com> **
 *
 * from serv.cpp:
 *	/* serv.cpp  -  Minimal ssleay server for Unix
 *	   30.9.1996, Sampo Kellomaki <sampo@iki.fi> **
 *	/* mangled to work with SSLeay-0.9.0b and OpenSSL 0.9.2b
 *	   Simplified to be even more minimal
 *	   12/98 - 4/99 Wade Scholine <wades@mail.cybg.com> **
 *
 ***
 */
/***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** *****
 * example usages:
 ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** *****
 * example 1.
 *
 * sst -c
 * ------
 *                          +-----------+
 *     unencrypted stream   |           |  SSL encrypted stream
 *  <======================>+fd0     fd1+<======================>
 *                          |           |
 *                          +-----------+
 * 
 * - relay between unencrypted data on stdin
 *   and SSL encrypted data on stdout.
 * - expects the remote side to have certificates in order.
 * - NOTE: not intended for interactive use since shells tend to
 *   create stdin and stdout as unidirectional file-descriptors.
 *   in this mode, sst expects to be able to both read and write 
 *   on either descriptor.
 * 
 ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** *****
 * example 2.
 * 
 * sst -s
 * ------
 *                          +-----------+
 *    SSL encrypted stream  |           |   decrypted stream  
 *  <======================>+fd0     fd1+<======================>
 *                          |           |
 *                          +-----------+
 * 
 * - relay between SSL encrypted data on stdin
 *   and decrypted raw data on stdout
 * - try to have the certificates in order.
 * - NOTE: not intended for interactive use since shells tend to
 *   create stdin and stdout as unidirectional file-descriptors.
 *   in this mode, sst expects to be able to both read and write 
 *   on either descriptor.
 * 
 ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** *****
 * example 3.
 * 
 * sst -c -p host:NNN
 * ------------------
 *                          +-----------+
 *     unencrypted stream   |           |    decrypted stream
 *  >>-------------------->>+fd0     fd1+>>--------------------->>
 *                          |           |
 *                          |           | (parent process)
 *                          +-----+-----+
 *                                ^
 *                                |  SSL encrypted stream
 *                                |
 *                                v
 *                          +-----+-----+
 *        (child process)   |  fd0/fd1  |
 *                          |           |   network data stream
 *                          |           +<======================>
 *         "nc host NNN"    |           |
 *                          +-----------+
 * 
 * - same as example 1 but use netcat to establish the
 *   network connection to host hhhh at port NNN on the remote side.
 * - suitable for interactive use/testing.
 * - sample usages:
 *	% sst -c -p mbox.ca:993	# interact with a remote IMAP/SSL server
 *	% sst -c -p 995		# interact with the local POP/SSL server
 * 
 ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** *****
 * example 4.
 * 
 * sst -s -p NNN
 * -------------
 *                          +-----------+
 *     unencrypted stream   |           |    decrypted stream
 *  >>-------------------->>+fd0     fd1+>>--------------------->>
 *                          |           |
 *     (parent process)     |           |
 *                          +-----+-----+
 *                                ^
 *                                |  SSL encrypted stream
 *                                |
 *                                v
 *                          +-----+-----+
 *                          |  fd0/fd1  |  (child process)   
 *     network data stream  |           |
 *  <======================>+           |
 *                          |           |  "nc -l -p NNN"    
 *                          +-----------+
 * 
 * - same as example 2 but use netcat to listen
 *   for network connections on port NNN.
 * - suitable for interactive use/testing.
 * 
 ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** *****
 * example 5.
 * 
 * sst -i -p hhhh:NNN
 * ------------------
 *                          +-----------+
 *    SSL encrypted stream  |           |
 *  <======================>+fd0        |
 *   [inherited from inetd] |           | (parent process)
 *                          |    fd1    |
 *                          +-----+-----+
 *                                ^
 *                                |  decrypted stream
 *                                v
 *                          +-----+-----+
 *        (child process)   |  fd0/fd1  |
 *                          |           |   network data stream
 *                          |           +<======================>
 *         "nc hhhh NNN"    |           |
 *                          +-----------+
 *
 * - inetd mode ("-i") implies server mode ("-s") as with example 2.
 * - forwards (punts) the decrypted stream to another host (hhhh) and
 *   port (NNN) by using netcat to establish the network connection.
 *
 * - sample inetd.conf entries:
 *
 *	# handle SSL encryption for our local imap server.
 *	simap stream tcp nowait root /local/bin/sst sst -i -p 143
 *
 *    or
 *
 *	# handle SSL encryption for the imap server on mboxhost.
 *	simap stream tcp nowait root /local/bin/sst sst -i -p mboxhost:143
 * 
 ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** *****
 * example 6.
 * 
 * sst -i -- <command ...>
 * -----------------------
 *                          +-----------+
 *    SSL encrypted stream  |           |
 *  <======================>+fd0        |
 *   [inherited from inetd] |           | (parent process)
 *                          |    fd1    |
 *                          +-----+-----+
 *                                ^
 *                                |  decrypted stream
 *                                v
 *                          +-----+-----+
 *        (child process)   |  fd0/fd1  |
 *                          |           |
 *        "<command ... >"  |           |
 *                          +-----------+
 * - as with example 5 but instead of running netcat, just execute an
 *   arbitrary command to use/process the data on the decrypted stream.
 *
 * - a sample inetd.conf entry:
 *
 *	# encrypted quotes - make fortunes available via SSL.
 *	qotd stream tcp nowait root /local/bin/sst sst -i -- /usr/games/fortune -l
 * 
 ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** *****
 */
#ifndef lint
static char rcsid[] = "$Header: /c/src/local.bin/sst/RCS/sst.c,v 1.23 2015/05/06 13:24:00 pkern Exp $";
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <memory.h>
#include <errno.h>
#include <syslog.h>

#include <sys/param.h>		/* for MAXPATHLEN */

#include <sys/types.h>
#include <sys/socket.h>

#include <sys/ioctl.h>
#include <signal.h>
#include <sys/wait.h>
#include <time.h>
#if defined(sun) || defined(__FreeBSD__)
#include <sys/time.h>
#endif
#include <sys/resource.h>

#ifndef AF_LOCAL
#define AF_LOCAL AF_UNIX
#endif

/* #include <openssl/rsa.h>	/* SSLeay stuff */
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define Perror(s)	\
	if (logging) syslog(LOG_ERR, "%s: %m", (s)); else perror((s));
#define CHK_NULL(x) if ((x)==NULL) quit (1)
#define CHK_ERR(err,s) if ((err)==-1) { Perror(s); quit(2); }
#define CHK_SSL(err) if ((err)==-1) { show_SSL_errors(); quit(3); }

int server = 0;
int debug = 0;
int verbose = 0;
int logging = 0;
int timeout = 0;
int inetd = 0;
int eofclnt = 0;
int nossl = 0;

int require_certs = 0;
int verify_cert = 0;
int self_signed_ok = 1;

char *prog = "sst";
char *host = NULL;
char *port = NULL;
char *method = NULL;

char certfbuf[MAXPATHLEN], ssldbuf[MAXPATHLEN];
char *certf = NULL, *pkeyf = NULL, *ssld = NULL;

#ifndef CONFDIR
#define	CONFDIR	"/local/lib/openssl"
#endif

#ifndef CERTF
#define CERTF	"certs/sst.pem"
#endif

#ifndef LOG_SSL
#define LOG_SSL	LOG_USER
#endif

#ifndef NETCAT
#define NETCAT	"/local/bin/nc"
#endif

#ifdef USE_EGD
/*
 * USE_EGD - added for openssl-0.9.6a.
 * according to OpenSSL's FAQs, the need for this extra USE_EGD code 
 * will be made obsolete because the RAND_* routines in openssl-0.9.7
 * will automatically search for EGD sockets at pre-defined locations.
 */
#ifndef EGD_SOCK
#define EGD_SOCK "/tmp/.egd-pool"
#endif
char *egds = EGD_SOCK;
#endif	/* USE_EGD */

FILE *tty = NULL;

pid_t pid = 0;

/*
 * ERR_log_errors():
 * adapted from ERR_print_errors_fp()
 * as found in OpenSSL's ...
 *	crypto/err/err_prn.c
 *	Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
 *	All rights reserved.
 */
void
ERR_log_errors()
{
	unsigned long l;
	char buf[200];
	const char *file,*data;
	int line,flags;
	unsigned long es;

	es=CRYPTO_thread_id();
	while ((l=ERR_get_error_line_data(&file,&line,&data,&flags)) != 0) {
		syslog(LOG_ERR,"%lu:%s:%s:%d:%s\n",
			es,ERR_error_string(l,buf),file,line,
			(flags&ERR_TXT_STRING)?data:"");
	}
}

void
show_SSL_errors()
{
	if (logging)	ERR_log_errors();
	else		ERR_print_errors_fp(stderr);
}

#define SHOW_x(L,F,x)	do { \
	if (logging)	syslog((L), "%s", (x)); \
	else 	fprintf((F), "%d: %s\n", getpid(), (x)); } while(0)

#define SHOW_x1(L,F,M,x)	do { \
	if (logging)	syslog((L), (M), (x));		\
	else {	fprintf((F), "%d: ", getpid());	\
		fprintf((F), (M), (x)); fputs("\n", (F)); } } while(0)

#define SHOW_x2(L,F,M,x1,x2)	do { \
	if (logging)	syslog((L), (M), (x1), (x2));	\
	else {	fprintf((F), "%d: ", getpid());	\
		fprintf((F), (M), (x1), (x2)); fputs("\n", (F)); } } while(0)

#define SHOW_err(a)	SHOW_x(LOG_ERR,stderr,a)

#define SHOW_err1(f,a)	SHOW_x1(LOG_ERR,stderr,f,a)

#define SHOW_err2(f,a1,a2)	SHOW_x2(LOG_ERR,stderr,f,a1,a2)

#define SHOW_info(a)	SHOW_x(LOG_DEBUG,tty,a)

#define SHOW_info1(f,a)	SHOW_x1(LOG_DEBUG,tty,f,a)

#define SHOW_info2(f,a1,a2)	SHOW_x2(LOG_DEBUG,tty,f,a1,a2)


char *usageopts[] = {
"",
" options:",
" --------",
"  -c	= client mode.",
"  -s	= server mode.",
"  -e	= shutdown on client's EOF.",
"  -l	= redirect all messages to syslog(3).",
"  -i	= inetd mode (implies both '-s' and '-l').",
"  -n	= raw mode. don't use SSL to process the I/O.",
"  -v	= be chatty about what's happening.",
"  -r	= server mode: require a peer certificate.",
"	  client mode: provide a certificate.",
"  -d 	= enable debugging messages (this implies '-v').",
"         repeated use of '-d' enables more debugging output.",
"  -p [host:]port  = connect to (or listen to) portnum.",
"       {client,inetd - assumes 'localhost' if no 'host:' part given}",
"       {server       - 'host:' part is ignored if it's present     }",
"  -t timeout	= set maximum idle time (in seconds).",
"  -C cert-file	= use <cert-file> instead of the default certificate.",
"  -K pkey-file	= use <pkey-file> instead of the default private key file.",
"  -D ssl-conf	= use <ssl-conf> as the path to default cert/keys.",
"  -M method	= use a specific SSL method (ssl2, ssl3 or tls1).",
#ifdef USE_EGD
"  -E skt-path	= use <skt-path> instead of the default EGD socket.",
#endif
"  -V number	= verify peer certificate to a 'depth' of <number>.",
"  -S	= [server mode] strict checking. no self-signed certificates.",
"",
" auxiliary command:",
" ------------------",
"  the command to be executed instead of the default netcat commands.",
"",
NULL
};

usage()
{
	char **uop = usageopts;

	if (logging) {
		syslog(LOG_ERR, "usage: %s <options> [ '--' <auxiliary command + options> ]", prog);
		while (*uop != NULL) syslog(LOG_ERR, "%s", *uop++);
	}
	else {
		fprintf(stderr, "usage: %s <options> [ '--' <auxiliary command + options> ]\n", prog);
		while (*uop != NULL) fprintf(stderr, "%s\n", *uop++);
	}
}

/* reaper -- zombie prevention */
void
reaper()
{
	int w;
	pid_t p;

	while ((p = wait3(&w, WNOHANG, 0)) > 0) {
		if (debug) SHOW_info1("reaped %d", p);
		if (p == pid) pid = 0;
		continue;
	}
}

/* quit -- clean up and exit */
static void
quit(n)
int n;
{
	if (debug) SHOW_info1("quit %d", n);

	if (pid > 0) {
		if (debug) SHOW_info1("kill %d", pid);
		(void) kill (pid, SIGKILL);
	}

	exit(n);
}

/*
 * relay data.
 *
 * - decrypt data recvd on the SSL descriptor (sd) and
 *   write the resulting output to the wd descriptor.
 *
 * - read data recvd on the rd descriptor, and send it out, via
 *   SSL_write, to be encrypted and written to the sd descriptor.
 *
 * - EOF on the SSL stream means that the SSL connection has shutdown.
 *
 * - EOF on rd when in server mode means the actual server has finished.
 */
relay(ssl, sd, rd, wd)
SSL *ssl;
int sd, rd, wd;
{
	fd_set fds;
	char *p, buf[4096];
	int err, r, tblw;
	struct timeval tv, *tp = NULL;
	off_t nsr, nsw, nlr, nlw;
	int n, nw, rf = 1, sf = 1;

	nsr = nsw = nlr = nlw = (off_t)0;
	if (timeout > 0) {
		tp = &tv;
		tv.tv_sec = timeout;
		tv.tv_usec = 0;
	}

	while (sf || rf) {
		FD_ZERO(&fds);
		if (sf) FD_SET(sd, &fds);
		if (rf) FD_SET(rd, &fds);
		tblw = 1 + (( sd > rd ) ? sd : rd );

#if defined(__FreeBSD__) && __FreeBSD__ >= 9
		r = select(FD_SETSIZE, &fds, NULL, NULL, tp);
#else	/* */
		r = select(tblw, &fds, NULL, NULL, tp);
#endif	/* */
		if (debug > 3) SHOW_info1("select = %d", r);

		if (r < 0) {
			if (errno == EINTR) {
				/* Interrupted system call */
				if (debug) Perror("select");
			}
			else {
				Perror("select");
				quit(10);
			}
		}
		else if (r == 0) {
			if (verbose) SHOW_info("timed out.");
			goto done;
		}
		else for ( ; r > 0; r--) {
			if (FD_ISSET(sd, &fds)) {
				if (debug > 3) SHOW_info1("select: sd: %d", r);
				if (ssl != NULL) {
					err = SSL_read(ssl, buf, sizeof(buf) - 1);
					CHK_SSL(err);
				}
				else {
					err = read(sd, buf, sizeof(buf) - 1);
					if (err < 0) {
						Perror("read(sd)");
						quit(41);
					}
				}
				buf[err] = '\0';

				if (err == 0) {
					sf = 0;
					if (debug) SHOW_info("EOF(sd)");
					goto done;
				}
				nsr += err;

				for (p = buf, nw = err; nw > 0; nw -= n, p += n) {
					n = write(wd, p, nw);
					if (n < 0) {
						Perror("local write");
						quit(12);
					}
				}
				nlw += err;
				FD_CLR(sd, &fds);
				continue;
			}
			if (FD_ISSET(rd, &fds)) {
				if (debug > 3) SHOW_info1("select: rd: %d", r);
				n = read(rd, buf, sizeof(buf)-1);
				if (n < 0) {
					Perror("local read");
					quit(13);
				}
				if (n == 0) {
					rf = 0;
					if (debug) SHOW_info("EOF(rd)");
					if (!server && eofclnt) {
						if (ssl != NULL) {
							err = SSL_shutdown(ssl);
							if (verbose) SHOW_info1("shutdown ssl client (%d)", err);
						}
					}
					else if (server && sf) {
						if (ssl != NULL) {
							err = SSL_shutdown(ssl);
							if (verbose) SHOW_info1("shutdown ssl server (%d)", err);
						}
						if (err) {
							sf = 0;
							if (verbose) SHOW_info("close relay");
						}
					}
				}
				else {
					nlr += n;

					if (ssl != NULL) {
						err = SSL_write(ssl, buf, n);
						CHK_SSL(err);
					}
					else {
						err = write(sd, buf, n);
						if (err < 0) {
							Perror("write(sd)");
							quit(42);
						}
					}
					nsw += err;
				}
				FD_CLR(rd, &fds);
				continue;
			}
		}
	}

done:
	if (sf && ssl != NULL) {
		err = SSL_shutdown(ssl);
		if (verbose) SHOW_info1("shutdown ssl (%d)", err);
	}

	if (verbose) {
		if (sizeof(off_t) > 4) {
			if (ssl != NULL) {
				SHOW_info1("bytes from   ssl: %qd", nsr);
				SHOW_info1("bytes  to    ssl: %qd", nsw);
			}
			else {
				SHOW_info1("bytes from remote: %qd", nsr);
				SHOW_info1("bytes  to  remote: %qd", nsw);
			}
			SHOW_info1("bytes from local: %qd", nlr);
			SHOW_info1("bytes  to  local: %qd", nlw);
		}
		else {
			if (ssl != NULL) {
				SHOW_info1("bytes from   ssl: %ld", nsr);
				SHOW_info1("bytes  to    ssl: %ld", nsw);
			}
			else {
				SHOW_info1("bytes from remote: %ld", nsr);
				SHOW_info1("bytes  to  remote: %ld", nsw);
			}
			SHOW_info1("bytes from local: %ld", nlr);
			SHOW_info1("bytes  to  local: %ld", nlw);
		}
	}
}


/*
 * see SSL_CTX_set_verify(3).
 */
static int
my_vrfy(pv_ok, ctx)
int pv_ok;
X509_STORE_CTX *ctx;
{
	char	*subj = "''", *bp;
	SSL	*ssl;
	X509	*err_cert;
	int	err, depth;

	err_cert = X509_STORE_CTX_get_current_cert(ctx);
	err = X509_STORE_CTX_get_error(ctx);
	depth = X509_STORE_CTX_get_error_depth(ctx);

	/*
	 * Retrieve the pointer to the SSL of the connection currently treated
	 * and the application specific data stored into the SSL object.
	 */
	ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());

	bp = X509_NAME_oneline(X509_get_subject_name(err_cert), 0, 0);
	if (bp) { subj = strdup(bp); CRYPTO_free(bp); }

	/*
	 * Catch a too long certificate chain. The depth limit set using
	 * SSL_CTX_set_verify_depth() is by purpose set to "limit+1" so
	 * that whenever the "depth>verify_depth" condition is met, we
	 * have violated the limit and want to log this error condition.
	 * We must do it here, because the CHAIN_TOO_LONG error would not
	 * be found explicitly; only errors introduced by cutting off the
	 * additional certificates would be logged.
	 */

	if (depth > verify_cert) {
		pv_ok = 0;
		err = X509_V_ERR_CERT_CHAIN_TOO_LONG;
		X509_STORE_CTX_set_error(ctx, err);
	}

	if (!pv_ok) {
		if (debug > 1) {
			SHOW_err1("verify: cert error #%d", err);
			SHOW_err1("verify: cert error: %s", X509_verify_cert_error_string(err));
			SHOW_err1("verify: cert depth: %d", depth);
			SHOW_err1("verify: cert subject: %s", subj);
		}
	}
	else if (debug > 1) {
		SHOW_info1("verify: cert depth: %d", depth);
		SHOW_info1("verify: cert subject: %s", subj);
	}

	if (subj) free(subj);

	if (pv_ok)
		return pv_ok;

	/*
	 * At this point, err contains the last verification error.
	 * We can use it for something special.
	 */
	switch (err) {
	case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
		bp = X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), 0, 0);
		if (bp == NULL) SHOW_err("verify: cert: no issuer.");
		else {
			if (debug > 1) SHOW_info1("verify: cert issuer: %s", bp);
			CRYPTO_free(bp);
		}
		break;
	case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
		pv_ok = self_signed_ok;
		break;
	}

	return pv_ok;
}


peer_cert_prep(ctx)
SSL_CTX *ctx;
{
	if (verify_cert <= 0 && !require_certs) return;

	SSL_CTX_set_verify(ctx,
		SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE,
		my_vrfy);

	/*
	 * Let the verify_callback catch the verify_depth error
	 * so that we get an appropriate error in the logfile.
	 */
	SSL_CTX_set_verify_depth(ctx, verify_cert + 1);
}


/*
 * Get peer's certificate
 * (note: beware of dynamic allocation)
 */
peer_cert_chk(ctx, ssl)
SSL_CTX *ctx;
SSL *ssl;
{
	X509 *peer_cert;
  
	if (verbose) SHOW_info1("cipher: [%s]", SSL_get_cipher (ssl));

	peer_cert = SSL_get_peer_certificate (ssl);

	if (peer_cert == NULL) {
		if (require_certs) {
			SHOW_err("no peer certificate.");
			quit(51);
		}
		if (verbose) SHOW_info("no peer certificate.");
		return;
	}

	if (verbose) {
		char *bp;

		bp = X509_NAME_oneline (X509_get_subject_name (peer_cert), 0, 0);
		if (bp == NULL) SHOW_err("peer cert: no subject.");
		else {
			SHOW_info1("peer cert subject: %s", bp);
			CRYPTO_free(bp);
		}

		bp = X509_NAME_oneline (X509_get_issuer_name  (peer_cert), 0, 0);
		if (bp == NULL) SHOW_err("peer cert: no issuer.");
		else {
			SHOW_info1("peer cert issuer: %s", bp);
			CRYPTO_free(bp);
		}
	}

	if (verify_cert > 0) {
		int r = SSL_get_verify_result(ssl);

		switch (r) {
		case X509_V_OK:
			if (verbose) SHOW_info("peer cert is OK.");
			break;
		case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
			if (verbose) SHOW_info("peer cert is self-signed.");
			break;
		default:
			SHOW_err1("peer cert error #%d", r);
			SHOW_err1("peer cert error: %s", X509_verify_cert_error_string(r));
		}
	}

	/* deallocate the certificate. */
	X509_free (peer_cert);
}


cert_prep(ctx)
SSL_CTX *ctx;
{
	if (certf == NULL) { SHOW_err("need a cert file."); quit(60); }
	if (pkeyf == NULL) { SHOW_err("need a pkey file."); quit(61); }

	if (SSL_CTX_use_certificate_file(ctx, certf, SSL_FILETYPE_PEM) <= 0) {
		show_SSL_errors();
		quit(62);
	}
	if (SSL_CTX_use_PrivateKey_file(ctx, pkeyf, SSL_FILETYPE_PEM) <= 0) {
		show_SSL_errors();
		quit(63);
	}

	if (!SSL_CTX_check_private_key(ctx)) {
		SHOW_err("private key does not match the certificate public key");
		quit(64);
	}
}


srvr_prep(ctx, ssl, sd)
SSL_CTX **ctx;
SSL **ssl;
int sd;
{
	int err;
	SSL_METHOD *meth;
	X509 *client_cert;

	/*
	 * SSL preliminaries:
	 * keep the certificate and key with the context.
	 */
	SSL_load_error_strings();
	SSLeay_add_ssl_algorithms();

	if (method == NULL)
		meth = SSLv23_server_method();
	else if (strcmp(method, "ssl2") == 0)
		meth = SSLv2_server_method();
	else if (strcmp(method, "ssl3") == 0)
		meth = SSLv3_server_method();
	else if (strcmp(method, "tls1") == 0)
		meth = TLSv1_server_method();
	else
		meth = SSLv23_server_method();

	*ctx = SSL_CTX_new (meth);
	if (!*ctx) { show_SSL_errors(); quit(2); }

	cert_prep(*ctx);

#ifdef USE_EGD
	if (RAND_egd(egds) == -1) {
		SHOW_err1("failed to contact EGD via %s", egds);
		quit(23);
	}
#endif

	peer_cert_prep(*ctx);

	*ssl = SSL_new (*ctx);		CHK_NULL(ssl);
	SSL_set_fd (*ssl, sd);

	err = SSL_accept (*ssl);	CHK_SSL(err);

	peer_cert_chk(*ctx, *ssl);
}


clnt_prep(ctx, ssl, sd)
SSL_CTX **ctx;
SSL **ssl;
int sd;
{
	int err;
	SSL_METHOD *meth;
	X509 *server_cert;

	/*
	 * SSL preliminaries:
	 * keep the certificate and key with the context.
	 */
	SSL_load_error_strings();
	SSLeay_add_ssl_algorithms();

	if (method == NULL)
		meth = SSLv23_client_method();
	else if (strcmp(method, "ssl2") == 0)
		meth = SSLv2_client_method();
	else if (strcmp(method, "ssl3") == 0)
		meth = SSLv3_client_method();
	else if (strcmp(method, "tls1") == 0)
		meth = TLSv1_client_method();
	else
		meth = SSLv23_client_method();

	*ctx = SSL_CTX_new (meth);
	if (!*ctx) { show_SSL_errors(); quit(2); }

	if (require_certs) cert_prep(*ctx);

#ifdef USE_EGD
	if (RAND_egd(egds) == -1) {
		SHOW_err1("failed to contact EGD via %s", egds);
		quit(33);
	}
#endif

	*ssl = SSL_new (*ctx);		CHK_NULL(*ssl);    
	SSL_set_fd (*ssl, sd);

	err = SSL_connect (*ssl);	CHK_SSL(err);

	require_certs = 1;	/* XXX */
	peer_cert_chk(*ctx, *ssl);
}


main(ac, av)
int ac;
char *av[];
{
	SSL *ssl;
	SSL_CTX *ctx;
	int sd = -1, rd, wd;
  
	extern char *optarg;
	extern int optind;
	int ch, errflg = 0;

	rd = fileno(stdin);
	wd = fileno(stdout);

	prog = av[0];
	while ((ch = getopt(ac, av,
#ifdef USE_EGD
				"cdeilnqsvrSp:t:C:K:D:M:E:V:"
#else
				"cdeilnqsvrSp:t:C:K:D:M:V:"
#endif
							)) != -1) {
		switch(ch) {
		case 'c': server = 0; break;
		case 's': server = 1; break;
		case 'd': debug++; break;
		case 'e': eofclnt = 1; break;
		case 'i': inetd = 1, logging = 1, server = 1 ; break;
		case 'n': nossl = 1; break;
		case 'p': port = optarg; break;
		case 'q': verbose = 0; break;
		case 'v': verbose = 1; break;
		case 'l': logging = 1; break;
		case 't': timeout = atoi(optarg); break;
		case 'K': pkeyf = optarg; break;
		case 'C': certf = optarg; break;
		case 'D': ssld = optarg; break;
		case 'M': method = optarg; break;
#ifdef USE_EGD
		case 'E': egds = optarg; break;
#endif
		case 'r': require_certs = 1; break;
		case 'V': verify_cert = atoi(optarg); break;
		case 'S': self_signed_ok = 0; break;
		default:
			errflg = 1;
			break;
		}
	}

	if (logging) openlog(prog, LOG_PID, LOG_SSL);

	if (errflg) {
usage:
		usage();
		quit(1);
	}

	if (ssld == NULL) {
		strcpy(ssldbuf, CONFDIR);
		ssld = ssldbuf;
	}
	if (certf == NULL) {
		strcpy(certfbuf, ssld);
		strcat(certfbuf, "/");
		strcat(certfbuf, CERTF);
		certf = certfbuf;
	}
	if (pkeyf == NULL) pkeyf = certf;

	if (debug) verbose = debug;

	if (tty == NULL) tty = stderr;

	if (port != NULL) {
		char *cp = (char *)index(port, ':');

		if (cp == NULL)
			host = "127.0.0.1";
		else {
			host = port;
			*cp++ = '\0';
			port = cp;
		}
	}

	if (verbose) {
		SHOW_info1("base: %s", ssld);
		SHOW_info1("cert: %s", certf);
		SHOW_info1("pkey: %s", pkeyf);
#ifdef USE_EGD
		SHOW_info1("EGD sock: %s", egds);
#endif
		if (host != NULL) SHOW_info1("punt host: %s", host);
		if (port != NULL) SHOW_info1("punt port: %s", port);
		if (timeout > 0) SHOW_info1("timeout: %d", timeout);
	}

	/*
	 * if an additional command was specified or if a host/port was
	 * given (which means that netcat will be used to handle the raw
	 * network I/O), then create the socket/filedescriptors which
	 * will be used to communicate with the child process and then
	 * fork+exec that child process.
	 */
	if (optind < ac || port != NULL) {
		int sv[2];

		if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, sv) < 0) {
			 Perror("socketpair()");
			 quit(1);
		}

		signal(SIGCHLD, reaper);	/* zombie prevention */

		pid = fork();
		if (pid < 0) { Perror("fork()"); quit(1); }
		if (pid == 0) {
			/* child */
			if (dup2(sv[1], 0) < 0) Perror("dup2(0)");
			if (dup2(sv[1], 1) < 0) Perror("dup2(1)");

			for (sd = getdtablesize(); sd > 2; sd--)
				(void) close(sd);

			if (verbose && logging)
				openlog(prog, LOG_PID, LOG_SSL);

			if (optind < ac) {
				av += optind;
				if (verbose) {
					SHOW_info1("exec '%s ...'", av[0]);
					if (logging) closelog();
				}
				execvp(av[0], av);
			}
			else if (port && server && !inetd) {
				if (verbose) {
					SHOW_info1("exec 'nc -l -p %s'", port);
					if (logging) closelog();
				}
				execl(NETCAT, "nc", "-l", "-p", port, NULL);
			}
			else if (port) {
				if (verbose) {
					SHOW_info2("exec 'nc %s %s'", host, port);
					if (logging) closelog();
				}
				execl(NETCAT, "nc", host, port, NULL);
			}

			if (logging) openlog(prog, LOG_PID, LOG_SSL);
			Perror("execl/execvp");
			_exit(1);
		}


		/* parent */
		if (debug) SHOW_info1("launched %d", pid);
		if (inetd) {
			if (debug > 2) {
				SHOW_info1("rd %d", rd);
				SHOW_info1("wd %d", wd);
				SHOW_info1("sd %d", sd);
				SHOW_info1("sv[0] %d", sv[0]);
				SHOW_info1("sv[1] %d", sv[1]);
			}
			sd = rd;
			rd = wd = sv[0];
		}
		else
			sd = sv[0];
		close(sv[1]);
	}
	
	if (nossl) { ssl = NULL; ctx = NULL; }
	if (server) {
		if (sd < 0) {
			/* see example 2. */
			sd = fileno(stdin);
			rd = wd;
		}
		if (!nossl) srvr_prep(&ctx, &ssl, sd);
	}
	else {
		if (sd < 0) {
			/* see example 1. */
			sd = fileno(stdout);
			wd = rd;
		}
		if (!nossl) clnt_prep(&ctx, &ssl, sd);
	}

	if (debug > 2) {
		SHOW_info1("rd: %d", rd);
		SHOW_info1("wd: %d", wd);
		SHOW_info1("sd: %d", sd);
	}

	relay(ssl, sd, rd, wd);

	close (sd);
	if (ssl != NULL) SSL_free (ssl);
	if (ctx != NULL) SSL_CTX_free (ctx);

	if (pid > 0) (void) kill (pid, SIGKILL);

	quit(0);
}
