/*
 * ultravnc_dsm_helper.c unix/openssl UltraVNC encryption encoder/decoder. 
 *                       (also a generic symmetric encryption tunnel)
 *                       (also a generic TCP relay and supports IPv6)
 *
 * compile via:

   cc       -O -o ultravnc_dsm_helper ultravnc_dsm_helper.c -lssl -lcrypto
   cc -DDBG -O -o ultravnc_dsm_helper ultravnc_dsm_helper.c -lssl -lcrypto

 *
 * See usage below for how to run it.
 *
 * Note: since the UltraVNC DSM plugin implementation changes the RFB
 * (aka VNC) protocol (extra data is sent), you will *ALSO* need to modify
 * your VNC viewer or server to discard (or insert) this extra data.
 *
 * This tool knows nothing about the RFB protocol: it simply
 * encrypts/decrypts a stream using a symmetric cipher, arc4 and aesv2,
 * (others have been added, see usage).  It could be used as a general
 * encrypted tunnel:
 *
 *   any-client <=> ultravnc_dsm_helper <--network--> ultravnc_dsm_helper(reverse mode) <=> any-server
 *
 * e.g. to connect a non-ultra-dsm-vnc viewer to a non-ultra-dsm-vnc server
 * without using SSH or SSL.
 *
 * It can also be used as a general TCP relay (no encryption.)
 *
 * It supports IPv6 and so can also be used as a IPv6 gateway.
 *
 * -----------------------------------------------------------------------
 * Copyright (C) 2008-2010 Karl J. Runge <runge@karlrunge.com>
 * All rights reserved.
 *
 *  This is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License, or (at
 *  your option) any later version.
 *
 *  This software is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this software; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 *  USA  or see <http://www.gnu.org/licenses/>.
 *
 *  In addition, as a special exception, Karl J. Runge gives permission
 *  to link the code of its release of ultravnc_dsm_helper with the
 *  OpenSSL project's "OpenSSL" library (or with modified versions of it
 *  that use the same license as the "OpenSSL" library), and distribute
 *  the linked executables.  You must obey the GNU General Public License
 *  in all respects for all of the code used other than "OpenSSL".  If you
 *  modify this file, you may extend this exception to your version of the
 *  file, but you are not obligated to do so.  If you do not wish to do
 *  so, delete this exception statement from your version.
 * -----------------------------------------------------------------------
 */

static char *usage = 
    "\n"
    "ultravnc_dsm_helper: a symmetric encryption tunnel. version 0.2\n"
    "\n"
    "       Created to enable encrypted VNC connections to UltraVNC, it can act as\n"
    "       a general encrypted tunnel between any two applications.  It can also\n"
    "       be used as a general TCP relay (i.e. no encryption) or an IPv6 gateway.\n"
    "\n"
    "Usage: ultravnc_dsm_helper cipher keyfile listenport remotehost:port\n"
    "       ultravnc_dsm_helper relay listenport remotehost:port\n"
    "       ultravnc_dsm_helper showcert remotehost:port\n"
    "\n"
    "e.g.:  ultravnc_dsm_helper arc4 ./arc4.key 5901 snoopy.net:5900\n"
    "\n"
    "       IPv6 is supported: both IPv4 and IPv6 are attempted to listen on (port\n"
    "               'listenport'.)  For connections to remotehost, if IPv4 fails\n"
    "               then IPv6 is tried.  Set the env. var ULTRAVNC_DSM_HELPER_NOIPV6\n"
    "               to completely disable the use of IPv6.\n"
    "\n"
    "\n"
    "       cipher: specify 'msrc4', 'msrc4_sc', 'arc4', 'aesv2', 'aes-cfb',\n"
    "               'aes256', 'blowfish', '3des', 'securevnc'.\n"
    "\n"
    "               Also 'none', 'relay', or 'showcert'.  See below for details.\n"
    "\n"
    "         'msrc4_sc' enables a workaround for UVNC SC -plugin use.\n"
    "            (it might not be required in SC circa 2009 and later; try 'msrc4'.)\n"
    "\n"
    "         use 'securevnc' for SecureVNCPlugin (RSA key exchange).  'keyfile' is\n"
    "           used as a server RSA keystore in this mode.  If 'keyfile' does not\n"
    "           exist the user is prompted whether to save the key or not (a MD5\n"
    "           hash of it is shown)  If 'keyfile' already exists the server key\n"
    "           must match its contents or the connection is dropped.\n"
    "\n"
    "           HOWEVER, if 'keyfile' ends in the string 'ClientAuth.pkey', then the\n"
    "           normal SecureVNCPlugin client key authentication is performed.\n"
    "           If you want to do both have 'keyfile' end with 'ClientAuth.pkey.rsa'\n"
    "           that file will be used for the RSA keystore, and the '.rsa' will be\n"
    "           trimmed off and the remaining name used as the Client Auth file.\n"
    "\n"
    "         use '.' to have it try to guess the cipher from the keyfile name,\n"
    "           e.g. 'arc4.key' implies arc4, 'rc4.key' implies msrc4, etc.\n"
    "\n"
    "         use 'rev:arc4', etc. to reverse the roles of encrypter and decrypter.\n"
    "           (i.e. if you want to use it for a vnc server, not vnc viewer)\n"
    "\n"
    "         use 'noultra:...' to skip steps involving salt and IV to try to be\n"
    "           compatible with UltraVNC DSM, i.e. assume a normal symmetric cipher\n"
    "           at the other end.\n"
    "\n"
    "         use 'noultra:rev:...' if both are to be supplied.\n"
    "\n"
    "\n"
    "       keyfile: file holding the key (16 bytes for arc4 and aesv2, 87 for msrc4)\n"
    "           E.g. dd if=/dev/random of=./my.key bs=16 count=1\n"
    "           keyfile can also be pw=<string> to use \"string\" for the key.\n"
    "           Or for 'securevnc' the RSA keystore and/or ClientAuth file.\n"
    "\n"
    "\n"
    "       listenport: port to listen for incoming connection on. (use 0 to connect\n"
    "                   to stdio, use a negative value to force localhost listening)\n"
    "\n"
    "\n"
    "       remotehost:port: host and port to connect to. (e.g. ultravnc server)\n"
    "\n"
    "\n"
    "       Also: cipher may be cipher@n,m where n is the salt size and m is the\n"
    "       initialization vector size. E.g. aesv2@8,16  Use n=-1 to disable salt\n"
    "       and the MD5 hash (i.e. insert the keydata directly into the cipher.)\n"
    "\n"
    "       Use cipher@md+n,m to change the message digest. E.g. arc4@sha+8,16\n"
    "       Supported: 'md5', 'sha', 'sha1', 'ripemd160'.\n"
    "\n"
    "\n"
    "       TCP Relay mode: to connect without any encryption use a cipher type of\n"
    "       either 'relay' or 'none' (both are the equivalent):\n"
    "\n"
    "         ultravnc_dsm_helper relay listenport remotehost:port\n"
    "         ultravnc_dsm_helper none  listenport remotehost:port\n"
    "\n"
    "       where 'relay' or 'none' is a literal string.\n"
    "       Note that for this mode no keyfile is suppled.\n"
    "       Note that this mode can act as an IPv4 to IPv6 gateway.\n"
    "\n"
    "         ultravnc_dsm_helper relay 8080 ipv6.beijing2008.cn:80\n"
    "\n"
    "\n"
    "       SSL Show Certificate mode:  Set the cipher to 'showcert' to fetch\n"
    "       the SSL certificate from remotehost:port and print it to the stdout.\n"
    "       No certificate authentication or verification is performed.  E.g.\n"
    "\n"
    "         ultravnc_dsm_helper showcert www.verisign.com:443\n"
    "\n"
    "       (the output resembles that of 'openssl s_client ...')  Set the env var\n"
    "       ULTRAVNC_DSM_HELPER_SHOWCERT_ADH=1 for Anonymous Diffie Hellman mode.\n"
    "\n"
    "\n"
    "       Looping Mode:  Set the env. var. ULTRAVNC_DSM_HELPER_LOOP=1 to have it\n"
    "       restart itself after every disconnection in an endless loop.  It pauses\n"
    "       500 msec before restarting.  Use ULTRAVNC_DSM_HELPER_LOOP=N to set the\n"
    "       pause to N msec.\n"
    "\n"
    "       You can also set the env. var. ULTRAVNC_DSM_HELPER_BG to have the\n"
    "       program fork into the background for each connection, thereby acting\n"
    "       as a simple daemon.\n"
;

/*
 * We can also run as a module included into x11vnc (-enc option)
 * The includer must set ENC_MODULE and ENC_HAVE_OPENSSL.
 *
 * Note that when running as a module we still assume we have been
 * forked off of the parent process and are communicating back to it
 * via a socket.  So we *still* exit(3) at the end or on error.  And
 * the global settings won't work.
 */
#ifdef ENC_MODULE
#  define main __enc_main
static char *prog = "enc_helper";
#else
#  define ENC_HAVE_OPENSSL 1
static char *prog = "ultravnc_dsm_helper";
#endif

/* unix includes */
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>

#include <string.h>
#include <errno.h>
#include <signal.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>


/* Solaris (sysv?) needs INADDR_NONE */
#ifndef INADDR_NONE
#define INADDR_NONE ((in_addr_t) 0xffffffff)
#endif

/* openssl includes */
#if ENC_HAVE_OPENSSL
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/rsa.h>
static const EVP_CIPHER *Cipher;
static const EVP_MD *Digest;
#endif

static char *cipher = NULL;	/* name of cipher, e.g. "aesv2" */
static int reverse = 0;		/* listening connection */
static int msrc4_sc = 0;	/* enables workaround for SC I/II */
static int noultra = 0;		/* manage salt/iv differently from ultradsm */
static int nomd = 0;		/* use the keydata directly, no md5 or salt */
static int pw_in = 0;		/* pw=.... read in */


/* The data that was read in from key file (or pw=password) */
static char keydata[1024];
static int keydata_len;

/* Size of salt and IV; based on UltraVNC DSM */
#define SALT 16
#define MSRC4_SALT 11
#define IVEC 16

/* Set default values of salt and IV */
static int salt_size = SALT;
static int ivec_size = IVEC;

/* To track parent and child pids */
static pid_t parent, child;

/* transfer buffer size */
#define BSIZE 8192

/* Some very verbose debugging stuff I enable for testing */
#ifdef DBG
#  include "dbg.h"
#else
#  define DEC_CT_DBG(p, n)
#  define DEC_PT_DBG(p, n)
#  define ENC_CT_DBG(p, n)
#  define ENC_PT_DBG(p, n)
#  define PRINT_IVEC
#  define PRINT_KEYDATA
#  define PRINT_KEYSTR_AND_FRIENDS
#  define PRINT_LOOP_DBG1
#  define PRINT_LOOP_DBG2
#  define PRINT_LOOP_DBG3
#endif

/* SecureVNCPlugin from: http://adamwalling.com/SecureVNC/ */
#define SECUREVNC_RSA_PUBKEY_SIZE 270
#define SECUREVNC_ENCRYPTED_KEY_SIZE 256
#define SECUREVNC_SIGNATURE_SIZE 256
#define SECUREVNC_KEY_SIZE 16
#define SECUREVNC_RESERVED_SIZE 4
#define SECUREVNC_RC4_DROP_BYTES 3072
#define SECUREVNC_RAND_KEY_SOURCE 1024
static int securevnc = 0;
static int securevnc_arc4 = 0;
static char *securevnc_file = NULL;

static void enc_connections(int, char*, int);

#if !ENC_HAVE_OPENSSL

/* In case we are a module and there is no OpenSSL buildtime support */

void enc_do(char *ciph, char *keyfile, char *lport, char *rhp) { 
	fprintf(stderr, "%s: not compiled with OpenSSL\n", prog);
	exit(1);
}

#else

#if defined(NO_EVP_aes_256_cfb) || (defined (__SVR4) && defined (__sun) && !defined(EVP_aes_256_cfb) && !defined(ASSUME_EVP_aes_256_cfb))
/*
 * For Solaris 10 missing 192 & 256 bit crypto.
 * Note that EVP_aes_256_cfb is a macro.
 */
#undef EVP_aes_256_cfb
#define EVP_aes_256_cfb() EVP_aes_128_cfb(); {fprintf(stderr, "Not compiled with EVP_aes_256_cfb() 'aes256' support.\n"); exit(1);}
#endif

/* If we are a module, enc_do() is the only interface we export.  */


/* This works out key type & etc., reads key, calls enc_connections */

void enc_do(char *ciph, char *keyfile, char *lport, char *rhp) { 

	struct stat sb;
	char *q, *p, *connect_host;
	char tmp[16];
	int fd, len = 0, listen_port = 0, connect_port, mbits;

	q = ciph;

	/* check for noultra mode: */
	if (strstr(q, "noultra:") == q) {
		noultra = 1;
		q += strlen("noultra:");
	}

	/* check for reverse mode: */
	if (strstr(q, "rev:") == q) {
		reverse = 1;
		q += strlen("rev:");
	}

	/* work out which cipher and set Cipher to the selected one. */
	if (!strcasecmp(q, "msrc4")) {
		Cipher = EVP_rc4();		cipher = "msrc4";

	} else if (!strcasecmp(q, "msrc4_sc")) {
		Cipher = EVP_rc4();		cipher = "msrc4";
		msrc4_sc = 1;			/* no salt/iv workaround */

	} else if (strstr(q, "arc4") == q) {
		Cipher = EVP_rc4();		cipher = "arc4";

	} else if (strstr(q, "aesv2") == q || strstr(q, "aes-ofb") == q) {
		Cipher = EVP_aes_128_ofb();	cipher = "aesv2";

	} else if (strstr(q, "aes-cfb") == q) {
		Cipher = EVP_aes_128_cfb();	cipher = "aes-cfb";

	} else if (strstr(q, "aes256") == q) {
		Cipher = EVP_aes_256_cfb();	cipher = "aes256";

	} else if (strstr(q, "blowfish") == q) {
		Cipher = EVP_bf_cfb();		cipher = "blowfish";

	} else if (strstr(q, "3des") == q) {
		Cipher = EVP_des_ede3_cfb();	cipher = "3des";

	} else if (strstr(q, "securevnc") == q) {
		Cipher = EVP_aes_128_ofb();	cipher = "securevnc";
		securevnc = 1;

	} else if (strstr(q, "none") == q || strstr(q, "relay") == q) {
		cipher = "none";

	} else if (strstr(q, "showcert") == q) {
		cipher = "showcert";

	} else if (strstr(q, ".") == q) {
		/* otherwise, try to guess cipher from key filename: */
		if (strstr(keyfile, "arc4.key")) {
			Cipher = EVP_rc4();		cipher = "arc4";

		} else if (strstr(keyfile, "rc4.key")) {
			Cipher = EVP_rc4();		cipher = "msrc4";

		} else if (strstr(keyfile, "aesv2.key")) {
			Cipher = EVP_aes_128_ofb();	cipher = "aesv2";

		} else if (strstr(keyfile, "aes-cfb.key")) {
			Cipher = EVP_aes_128_cfb();	cipher = "aes-cfb";

		} else if (strstr(keyfile, "aes256.key")) {
			Cipher = EVP_aes_256_cfb();	cipher = "aes256";

		} else if (strstr(keyfile, "blowfish.key")) {
			Cipher = EVP_bf_cfb();		cipher = "blowfish";

		} else if (strstr(keyfile, "3des.key")) {
			Cipher = EVP_des_ede3_cfb();	cipher = "3des";

		} else if (strstr(keyfile, "securevnc.")) {
			Cipher = EVP_aes_128_ofb();	cipher = "securevnc";
			securevnc = 1;

		} else {
			fprintf(stderr, "cannot figure out cipher, supply 'msrc4', 'arc4', or 'aesv2' ...\n");
			exit(1);
		}
	} else {
		fprintf(stderr, "cannot figure out cipher, supply 'msrc4', 'arc4', or 'aesv2' ...\n");
		exit(1);
	}

	/* set the default message digest (md5) */
	if (!securevnc) {
		Digest = EVP_md5();
	} else {
		Digest = EVP_sha1();
	}

	/*
	 * Look for user specified salt and IV sizes at the end
	 * ( ciph@salt,iv and ciph@[md+]salt,iv ):
	 */
	p = strchr(q, '@');
	if (p) {
		int s, v;
		p++;
		if (strstr(p, "md5+") == p) {
			Digest = EVP_md5();        p += strlen("md5+");
		} else if (strstr(p, "sha+") == p) {
			Digest = EVP_sha();        p += strlen("sha+");
		} else if (strstr(p, "sha1+") == p) {
			Digest = EVP_sha1();       p += strlen("sha1+");
		} else if (strstr(p, "ripe+") == p) {
			Digest = EVP_ripemd160();  p += strlen("ripe+");
		} else if (strstr(p, "ripemd160+") == p) {
			Digest = EVP_ripemd160();  p += strlen("ripemd160+");
		}
		if (sscanf(p, "%d,%d", &s, &v) == 2) {
			/* cipher@n,m */
			if (-1 <= s && s <= SALT) {
				salt_size = s;
			} else {
				fprintf(stderr, "%s: invalid salt size: %d\n",
				    prog, s);
				exit(1);
			}
			if (0 <= v && v <= EVP_MAX_IV_LENGTH) {
				ivec_size = v;
			} else {
				fprintf(stderr, "%s: invalid IV size: %d\n",
				    prog, v);
				exit(1);
			}
		} else if (sscanf(p, "%d", &s) == 1) {
			/* cipher@n */
			if (-1 <= s && s <= SALT) {
				salt_size = s;
			} else {
				fprintf(stderr, "%s: invalid salt size: %d\n",
				    prog, s);
				exit(1);
			}
		}
		if (salt_size == -1) {
			/* let salt = -1 mean skip both MD5 and salt */
			nomd = 1;
			salt_size = 0;
		}
	}

	/* port to listen on (0 => stdio, negative => localhost) */
	if (lport != NULL) {
		listen_port = atoi(lport);
	}

	/* extract remote hostname and port */
	q = strrchr(rhp, ':');
	if (q) {
		connect_port = atoi(q+1);
		*q = '\0';
	} else {
		/* otherwise guess VNC display 0 ... */
		connect_port = 5900;
	}
	connect_host = strdup(rhp);

	/* check for and read in the key file */
	memset(keydata, 0, sizeof(keydata));

	if (!strcmp(cipher, "none")) {
		goto readed_in;
	}
	if (!strcmp(cipher, "showcert")) {
		goto readed_in;
	}

	if (securevnc) {
		/* note the keyfile for rsa verification later */
		if (keyfile != NULL && strcasecmp(keyfile, "none")) {
			securevnc_file = keyfile;
		}
		goto readed_in;
	}

	if (stat(keyfile, &sb) != 0) {
		if (strstr(keyfile, "pw=") == keyfile) {
			/* user specified key/password on cmdline */
			int i;
			len = 0;
			pw_in = 1;
			for (i=0; i < (int) strlen(keyfile); i++) {
				/* load the string to keydata: */
				int n = i + strlen("pw=");
				keydata[i] = keyfile[n];
				if (keyfile[n] == '\0') break;
				len++;
				if (i > 100) break;
			}
			goto readed_in;
		}
		/* otherwise invalid file */
		perror("stat");
		exit(1);
	}
	if (sb.st_size > 1024) {
		fprintf(stderr, "%s: key file too big.\n", prog);
		exit(1);
	}
	fd = open(keyfile, O_RDONLY);
	if (fd < 0) {
		perror("open");
		exit(1);
	}

	/* read it all in */
	len = (int) read(fd, keydata, (size_t) sb.st_size);
	if (len != sb.st_size) {
		perror("read");
		fprintf(stderr, "%s, could not read key file.\n", prog);
		exit(1);
	}
	close(fd);

	readed_in:


	/* check for ultravnc msrc4 format 'rc4.key' */
	mbits = 0;
	if (strstr(keydata, "128 bit") == keydata) {
		mbits = 128;
	} else if (strstr(keydata, " 56 bit") == keydata) {
		mbits = 56;
	} else if (strstr(keydata, " 40 bit") == keydata) {
		mbits = 40;
	}
	if (mbits > 0) {
		/* 4 is for int key length, 12 is for BLOBHEADER. */
		int i, offset = strlen("xxx bit") + 4 + 12;

		/* the key is stored in reverse order! */
		len = mbits/8;
		for (i=0; i < len; i++) {
			tmp[i] = keydata[offset + len - i - 1];
		}

		/* clear keydata and then copy the reversed bytes there: */
		memset(keydata, 0, sizeof(keydata));
		memcpy(keydata, tmp, len);
	}

	keydata_len = len;

	/* initialize random */
	RAND_poll();

	/*
	 * Setup connections, then transfer data when they are all
	 * hooked up.
	 */
	enc_connections(listen_port, connect_host, connect_port);
}
#endif

static void enc_raw_xfer(int sock_fr, int sock_to) {

	unsigned char buf[BSIZE];
	unsigned char *psrc = NULL;
	int len, m, n = 0;
	
	/* zero the buffers */
	memset(buf, 0, BSIZE);

	/* now loop forever processing the data stream */
	while (1) {
		errno = 0;

		/* general case of loop, read some in: */
		n = read(sock_fr, buf, BSIZE);

		if (n == 0 || (n < 0 && errno != EINTR)) {
			/* failure to read any data, it is EOF or fatal error */
			int err = errno;

			/* debug output: */
			fprintf(stderr, "%s: input stream finished: n=%d, err=%d", prog, n, err);

			/* EOF or fatal error */
			break;

		} else if (n > 0) {

			/* write data to the other end: */
			len = n;
			psrc = buf;
			while (len > 0) {
				errno = 0;
				m = write(sock_to, psrc, len);

				if (m > 0) {
					/* scoot them by how much was written: */
					psrc += m;
					len  -= m;
				}
				if (m < 0 && (errno == EINTR || errno == EAGAIN)) {
					/* interrupted or blocked */
					continue;
				}
				/* EOF or fatal error */
				break;
			}
		} else {
			/* this is EINTR */
		}
	}

	/* transfer done (viewer exited or some error) */

	fprintf(stderr, "\n%s: close sock_to\n", prog);
	close(sock_to);

	fprintf(stderr,   "%s: close sock_fr\n", prog);
	close(sock_fr);

	/* kill our partner after 1 secs. */
	sleep(1);
	if (child)  {
		if (kill(child, SIGTERM) == 0) {
			fprintf(stderr, "%s[%d]: killed my partner: %d\n",
			    prog, (int) getpid(), (int) child);
		}
	} else {
		if (kill(parent, SIGTERM) == 0) {
			fprintf(stderr, "%s[%d]: killed my partner: %d\n",
			    prog, (int) getpid(), (int) parent);
		}
	}
}

#if ENC_HAVE_OPENSSL
/*
 * Initialize cipher context and then loop till EOF doing transfer &
 * encrypt or decrypt.
 */
static void enc_xfer(int sock_fr, int sock_to, int encrypt) {
	/*
	 * We keep both E and D aspects in case we revert back to a
	 * single process calling select(2) on all fds...
	 */
	unsigned char E_keystr[EVP_MAX_KEY_LENGTH];
	unsigned char D_keystr[EVP_MAX_KEY_LENGTH];
	EVP_CIPHER_CTX E_ctx, D_ctx;
	EVP_CIPHER_CTX *ctx = NULL;

	unsigned char buf[BSIZE], out[BSIZE];
	unsigned char *psrc = NULL, *keystr;
	unsigned char salt[SALT+1];
	unsigned char ivec_real[EVP_MAX_IV_LENGTH];
	unsigned char *ivec = ivec_real;

	int i, cnt, len, m, n = 0, vb = 0, first = 1;
	int whoops = 1; /* for the msrc4 problem */
	char *encstr, *encsym;
	
	/* zero the buffers */
	memset(buf,  0, BSIZE);
	memset(out,  0, BSIZE);
	memset(salt, 0, sizeof(salt));
	memset(ivec_real, 0, sizeof(ivec_real));
	memset(E_keystr, 0, sizeof(E_keystr));
	memset(D_keystr, 0, sizeof(D_keystr));

	if (!strcmp(cipher, "msrc4")) {
		salt_size = MSRC4_SALT; /* 11 vs. 16 */
	}

	if (msrc4_sc) {
		whoops = 1;	/* force workaround in SC mode */
	}

	if (getenv("ENCRYPT_VERBOSE")) {
		vb = 1;	/* let user turn on some debugging via env. var. */
	}

	/*
	 * reverse mode, e.g. we help a vnc server instead of a viewer.
	 */
	if (reverse) {
		encrypt = (!encrypt);
	}
	encstr = encrypt ? "encrypt" : "decrypt";  /* string for messages */
	encsym = encrypt ? "+" : "-";

	/* use the encryption/decryption context variables below */
	if (encrypt) {
		ctx = &E_ctx;
		keystr = E_keystr;
	} else {
		ctx = &D_ctx;
		keystr = D_keystr;
	}

	if (securevnc) {
		first = 0;	/* no need for salt+iv on first time */
		salt_size = 0;	/* we want no salt */
		n = 0;		/* nothing read */
		ivec_size = 0;	/* we want no IV. */
		ivec = NULL;
	} else if (encrypt) {
		/* encrypter initializes the salt and initialization vector */

		/*
		 * Our salt is 16 bytes but I believe only the first 8
		 * bytes are used by EVP_BytesToKey(3).  Since we send it
		 * to the other "plugin" we need to keep it 16.  Also,
		 * the IV size can depend on the cipher type.  Again, 16.
		 */
		RAND_bytes(salt, salt_size);
		RAND_bytes(ivec, ivec_size);

		/* place them in the send buffer: */
		memcpy(buf, salt, salt_size);
		memcpy(buf+salt_size, ivec, ivec_size);

		n = salt_size + ivec_size;

		ENC_PT_DBG(buf, n);

	} else {
		/* decrypter needs to read salt + iv from the wire: */

		/* sleep 100 ms (TODO: select on fd) */
		struct timeval tv;
		tv.tv_sec  = 0;
		tv.tv_usec = 100 * 1000;
		select(1, NULL, NULL, NULL, &tv);

		if (salt_size+ivec_size == 0) {
			n = 0;	/* no salt or iv, skip reading. */
		} else {
			n = read(sock_fr, buf, salt_size+ivec_size+96);
		}
		if (n == 0 && salt_size+ivec_size > 0) {
			fprintf(stderr, "%s: decrypt finished.\n", prog);
			goto finished;
		}
		if (n < salt_size+ivec_size) {
		    if (msrc4_sc && n == 12) {
			fprintf(stderr, "%s: only %d bytes read. Assuming "
			    "UVNC Single Click server.\n", prog, n);
		    } else {
			if (n < 0) perror("read");
			fprintf(stderr, "%s: could not read enough for salt "
			    "and ivec: n=%d\n", prog, n);
			goto finished;
		    }
		}

		DEC_CT_DBG(buf, n);

		if (msrc4_sc && n == 12) {
			; /* send it as is */
		} else {
			/* extract them to their buffers: */
			memcpy(salt, buf, salt_size);
			memcpy(ivec, buf+salt_size, ivec_size);

			/* the rest is some encrypted data: */
			n = n - salt_size - ivec_size;
			psrc = buf + salt_size + ivec_size;
		
			if (n > 0) {
				/*
				 * copy it down to the start of buf for
				 * sending below:
				 */
				for (i=0; i < n; i++) {
					buf[i] = psrc[i];
				}
			}
		}
	}

	/* debug output */
	PRINT_KEYDATA;
	PRINT_IVEC;

	if (!strcmp(cipher, "msrc4")) {
		/* special cases for MSRC4: */

		if (whoops) {
			fprintf(stderr, "%s: %s - WARNING: MSRC4 mode and IGNORING random salt\n", prog, encstr);
			fprintf(stderr, "%s: %s - WARNING: and initialization vector!!\n", prog, encstr);
			EVP_CIPHER_CTX_init(ctx);
			if (pw_in) {
			    /* for pw=xxxx a md5 hash is used */
			    EVP_BytesToKey(Cipher, Digest, NULL, (unsigned char *) keydata,
			        keydata_len, 1, keystr, NULL);
			    EVP_CipherInit_ex(ctx, Cipher, NULL, keystr, NULL,
			        encrypt);
			} else {
			    /* otherwise keydata as is */
			    EVP_CipherInit_ex(ctx, Cipher, NULL,
			        (unsigned char *) keydata, NULL, encrypt);
			}
		} else {
			/* XXX might not be correct, just exit. */
			fprintf(stderr, "%s: %s - Not sure about msrc4 && !whoops case, exiting.\n", prog, encstr);
			exit(1);

			EVP_BytesToKey(Cipher, Digest, NULL, (unsigned char *) keydata,
			    keydata_len, 1, keystr, ivec); 
			EVP_CIPHER_CTX_init(ctx);
			EVP_CipherInit_ex(ctx, Cipher, NULL, keystr, ivec,
			    encrypt);
		}

	} else {
		unsigned char *in_salt = NULL;

		/* check salt and IV source and size. */
		if (securevnc) {
			in_salt = NULL;
		} else if (salt_size <= 0) {
			/* let salt_size = 0 mean keep it out of the MD5 */
			fprintf(stderr, "%s: %s - WARNING: no salt\n",
			    prog, encstr);
			in_salt = NULL;
		} else {
			in_salt = salt;
		}

		if (ivec_size < Cipher->iv_len && !securevnc) {
			fprintf(stderr, "%s: %s - WARNING: short IV %d < %d\n",
			    prog, encstr, ivec_size, Cipher->iv_len);
		}

		/* make the hashed value and place in keystr */

		/*
		 * XXX N.B.: DSM plugin had count=0, and overwrote ivec
		 * by not passing NULL iv.
		 */

		if (nomd) {
			/* special mode: no salt or md5, use keydata directly */

			int sz = keydata_len < EVP_MAX_KEY_LENGTH ?
			    keydata_len : EVP_MAX_KEY_LENGTH; 

			fprintf(stderr, "%s: %s - WARNING: no-md5 specified: ignoring salt & hash\n", prog, encstr);
			memcpy(keystr, keydata, sz);

		} else if (noultra && ivec_size > 0) {
			/* "normal" mode, don't overwrite ivec. */

			EVP_BytesToKey(Cipher, Digest, in_salt, (unsigned char *) keydata,
			    keydata_len, 1, keystr, NULL);

		} else {
			/* 
			 * Ultra DSM compatibility mode.  Note that this
			 * clobbers the ivec we set up above!  Under
			 * noultra we overwrite ivec only if ivec_size=0.
			 *
			 * SecureVNC also goes through here. in_salt and ivec are NULL.
			 * And ivec is NULL below in the EVP_CipherInit_ex() call.
			 */
			EVP_BytesToKey(Cipher, Digest, in_salt, (unsigned char *) keydata,
			    keydata_len, 1, keystr, ivec);
		}


		/* initialize the context */
		EVP_CIPHER_CTX_init(ctx);


		/* set the cipher & initialize */

		/*
		 * XXX N.B.: DSM plugin implementation had encrypt=1
		 * for both (i.e. perfectly symmetric)
		 */

		EVP_CipherInit_ex(ctx, Cipher, NULL, keystr, ivec, encrypt);
	}

	if (securevnc && securevnc_arc4) {
		/* need to discard initial 3072 bytes */
		unsigned char buf1[SECUREVNC_RC4_DROP_BYTES];
		unsigned char buf2[SECUREVNC_RC4_DROP_BYTES];
		int cnt = 0;
		EVP_CipherUpdate(ctx, buf1, &cnt, buf2, SECUREVNC_RC4_DROP_BYTES);
	}

	/* debug output */
	PRINT_KEYSTR_AND_FRIENDS;

	/* now loop forever processing the data stream */

	while (1) {
		errno = 0;
		if (first && n > 0) {
			if (encrypt && msrc4_sc) {
				/* skip sending salt+iv */
				first = 0;
				continue;
			} else {
				/* use that first block of data placed in buf */
			}
		} else if (first && n == 0 && salt_size + ivec_size == 0) {
			first = 0;
			continue;
		} else {
			/* general case of loop, read some in: */
			n = read(sock_fr, buf, BSIZE);
		}

		/* debug output: */
		if (vb) fprintf(stderr, "%s%d/%d ", encsym, n, errno);
		PRINT_LOOP_DBG1;

		if (n == 0 || (n < 0 && errno != EINTR)) {
			/* failure to read any data, it is EOF or fatal error */
			int err = errno;

			/* debug output: */
			PRINT_LOOP_DBG2;
			fprintf(stderr, "%s: %s - input stream finished: n=%d, err=%d", prog, encstr, n, err);

			/* EOF or fatal error */
			break;

		} else if (n > 0) {
			/* we read in some data, now transform it: */

			if (first && encrypt) {
				/* first time, copy the salt and ivec to out[] for sending */
				memcpy(out, buf, n);
				cnt = n;

			} else if (!EVP_CipherUpdate(ctx, out, &cnt, buf, n)) {
				/* otherwise, we transform the data */
				fprintf(stderr, "%s: enc_xfer EVP_CipherUpdate failed.\n", prog);
				break;
			}

			/* debug output: */
			if (vb) fprintf(stderr, "%sc%d/%d ", encsym, cnt, n);
			PRINT_LOOP_DBG3;

			/* write transformed data to the other end: */
			len = cnt;
			psrc = out;
			while (len > 0) {
				errno = 0;
				m = write(sock_to, psrc, len);

				/* debug output: */
				if (vb) fprintf(stderr, "m%s%d/%d ", encsym, m, errno);

				if (m > 0) {
					/* scoot them by how much was written: */
					psrc += m;
					len  -= m;
				}
				if (m < 0 && (errno == EINTR || errno == EAGAIN)) {
					/* interrupted or blocked */
					continue;
				}
				/* EOF or fatal error */
				break;
			}
		} else {
			/* this is EINTR */
		}
		first = 0;
	}

	/* transfer done (viewer exited or some error) */
	finished:

	fprintf(stderr, "\n%s: %s - close sock_to\n", prog, encstr);
	close(sock_to);

	fprintf(stderr,   "%s: %s - close sock_fr\n", prog, encstr);
	close(sock_fr);

	/* kill our partner after 2 secs. */
	sleep(2);
	if (child)  {
		if (kill(child, SIGTERM) == 0) {
			fprintf(stderr, "%s[%d]: %s - killed my partner: %d\n",
			    prog, (int) getpid(), encstr, (int) child);
		}
	} else {
		if (kill(parent, SIGTERM) == 0) {
			fprintf(stderr, "%s[%d]: %s - killed my partner: %d\n",
			    prog, (int) getpid(), encstr, (int) parent);
		}
	}
}

static int securevnc_server_rsa_save_dialog(char *file, char *md5str, unsigned char* rsabuf) {
	/* since we are likely running in the background, use this kludge by running tk */
	FILE *p;
	char str[2], *q = file, *cmd = getenv("WISH") ? getenv("WISH") : "wish";
	int rc;

	memset(str, 0, sizeof(str));

	p = popen(cmd, "w");
	if (p == NULL) {
		fprintf(stderr, "checkserver_rsa: could not run: %s\n", cmd); 
		return 0;
	}

	/* start piping tk/tcl code to it: */
	fprintf(p, "wm withdraw .\n");
	fprintf(p, "set x [expr [winfo screenwidth  .]/2]\n");
	fprintf(p, "set y [expr [winfo screenheight .]/2]\n");
	fprintf(p, "wm geometry . +$x+$y; update\n");
	fprintf(p, "catch {option add *Dialog.msg.font {helvetica -14 bold}}\n");
	fprintf(p, "catch {option add *Dialog.msg.wrapLength 6i}\n");
	fprintf(p, "set ans [tk_messageBox -title \"Save and Trust UltraVNC RSA Key?\" -icon question ");
	fprintf(p, "-type yesno -message \"Save and Trust UltraVNC SecureVNCPlugin RSA Key\\n\\n");
	fprintf(p, "With MD5 sum: %s\\n\\n", md5str);
	fprintf(p, "In file: ");
	while (*q != '\0') {
		/* sanitize user supplied string: */
		str[0] = *q;
		if (strpbrk(str, "[](){}`'\"$&*|<>") == NULL) {
			fprintf(p, "%s", str);
		}
		q++;
	}
	fprintf(p, " ?\"]\n");
	fprintf(p, "if { $ans == \"yes\" } {destroy .; exit 0} else {destroy .; exit 1}\n");
	rc = pclose(p);
	if (rc == 0) {
		fprintf(stderr, "checkserver_rsa: query returned: %d.  saving it.\n", rc);
		p = fopen(file, "w");
		if (p == NULL) {
			fprintf(stderr, "checkserver_rsa: could not open %s\n", file);
			return 0;
		}
		write(fileno(p), rsabuf, SECUREVNC_RSA_PUBKEY_SIZE);
		fclose(p);
		return 2;
	} else {
		fprintf(stderr, "checkserver_rsa: query returned: %d.  NOT saving it.\n", rc);
		return -1;
	}
}

static char *rsa_md5_sum(unsigned char* rsabuf) {
	EVP_MD_CTX md;
	char digest[EVP_MAX_MD_SIZE], tmp[16];
	char md5str[EVP_MAX_MD_SIZE * 8];
	unsigned int i, size = 0;

	EVP_DigestInit(&md, EVP_md5());
	EVP_DigestUpdate(&md, rsabuf, SECUREVNC_RSA_PUBKEY_SIZE);
	EVP_DigestFinal(&md, (unsigned char *)digest, &size);

	memset(md5str, 0, sizeof(md5str));
	for (i=0; i < size; i++) {
		unsigned char uc = (unsigned char) digest[i];
		sprintf(tmp, "%02x", (int) uc);
		strcat(md5str, tmp);
	}
	return strdup(md5str);
}

static int securevnc_check_server_rsa(char *file, unsigned char *rsabuf) {
	struct stat sb;
	unsigned char filebuf[SECUREVNC_RSA_PUBKEY_SIZE];
	char *md5str = rsa_md5_sum(rsabuf);

	if (!file) {
		return 0;
	}

	memset(filebuf, 0, sizeof(filebuf));
	if (stat(file, &sb) == 0) {
		int n, fd, i, ok = 1;

		if (sb.st_size != SECUREVNC_RSA_PUBKEY_SIZE) {
			fprintf(stderr, "checkserver_rsa: file is wrong size: %d != %d '%s'\n",
			    (int) sb.st_size, SECUREVNC_RSA_PUBKEY_SIZE, file);
			return 0;
		}

		fd = open(file, O_RDONLY);
		if (fd < 0) {
			fprintf(stderr, "checkserver_rsa: could not open: '%s'\n", file);
			return 0;
		}

		n = (int) read(fd, filebuf, SECUREVNC_RSA_PUBKEY_SIZE);
		close(fd);
		if (n != SECUREVNC_RSA_PUBKEY_SIZE) {
			fprintf(stderr, "checkserver_rsa: could not read all of file: %d != %d '%s'\n",
			    n, SECUREVNC_RSA_PUBKEY_SIZE, file);
			return 0;
		}

		for (i=0; i < SECUREVNC_RSA_PUBKEY_SIZE; i++) {
			if (filebuf[i] != rsabuf[i]) {
				ok = 0;
			}
		}
		if (!ok) {
			char *str1 = rsa_md5_sum(rsabuf);
			char *str2 = rsa_md5_sum(filebuf);
			fprintf(stderr, "checkserver_rsa: rsa keystore contents differ for '%s'\n", file);
			fprintf(stderr, "checkserver_rsa: MD5 sum of server key: %s\n", str1);
			fprintf(stderr, "checkserver_rsa: MD5 sum of keystore:   %s\n", str2);
		}
		return ok;
	} else {

		fprintf(stderr, "checkserver_rsa: rsa keystore file does not exist: '%s'\n", file);
		fprintf(stderr, "checkserver_rsa: asking user if we should store rsa key in it.\n\n");
		fprintf(stderr, "checkserver_rsa: RSA key has MD5 sum: %s\n\n", md5str); 

		return securevnc_server_rsa_save_dialog(file, md5str, rsabuf);
	}
}

static RSA *load_client_auth(char *file) {
	struct stat sb;
	int fd, n;
	char *contents;
	RSA *rsa;

	if (!file) {
		return NULL;
	}
	if (stat(file, &sb) != 0) {
		return NULL;
	}

	fd = open(file, O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "load_client_auth: could not open: '%s'\n", file);
		return NULL;
	}

	contents = (char *) malloc(sb.st_size);
	n = (int) read(fd, contents, sb.st_size);
	close(fd);

	if (n != sb.st_size)  {
		fprintf(stderr, "load_client_auth: could not read all of: '%s'\n", file);
		free(contents);
		return NULL;
	}

	rsa = d2i_RSAPrivateKey(NULL, (const unsigned char **) ((void *) &contents), sb.st_size);
	if (!rsa) {
		fprintf(stderr, "load_client_auth: d2i_RSAPrivateKey failed for: '%s'\n", file);
		return NULL;
	}

	if (RSA_check_key(rsa) != 1) {
		fprintf(stderr, "load_client_auth: rsa key invalid: '%s'\n", file);
		return NULL;
	}

	return rsa;
}

static void sslexit(char *msg) {
	fprintf(stderr, "%s: %s\n", msg, ERR_error_string(ERR_get_error(), NULL));
	exit(1);
}

static void securevnc_setup(int conn1, int conn2) {
	RSA *rsa = NULL;
	EVP_CIPHER_CTX init_ctx;
	unsigned char keystr[EVP_MAX_KEY_LENGTH];
	unsigned char *rsabuf, *rsasav;
	unsigned char *encrypted_keybuf;
	unsigned char *initkey;	
	unsigned int server_flags = 0;
	unsigned char one = 1, zero = 0, sig = 16;
	unsigned char b1, b2, b3, b4;
	unsigned char buf[BSIZE], to_viewer[BSIZE];
	int to_viewer_len = 0;
	int n = 0, len, rc;
	int server = reverse ? conn1 : conn2;
	int viewer = reverse ? conn2 : conn1;
	char *client_auth = NULL;
	int client_auth_req = 0;
	int keystore_verified = 0;

	ERR_load_crypto_strings();

	/* alloc and read from server the 270 comprising the rsa public key: */
	rsabuf = (unsigned char *) calloc(SECUREVNC_RSA_PUBKEY_SIZE, 1);
	rsasav = (unsigned char *) calloc(SECUREVNC_RSA_PUBKEY_SIZE, 1);
	len = 0;
	while (len < SECUREVNC_RSA_PUBKEY_SIZE) {
		n = read(server, rsabuf + len, SECUREVNC_RSA_PUBKEY_SIZE - len);
		if (n == 0 || (n < 0 && errno != EINTR)) {
			fprintf(stderr, "securevnc_setup: fail read rsabuf: n=%d len=%d\n", n, len);
			exit(1);
		}
		len += n;
	}
	if (len != SECUREVNC_RSA_PUBKEY_SIZE) {
		fprintf(stderr, "securevnc_setup: fail final read rsabuf: n=%d len=%d\n", n, len);
		exit(1);
	}
	fprintf(stderr, "securevnc_setup: rsa data read len: %d\n", len);
	memcpy(rsasav, rsabuf, SECUREVNC_RSA_PUBKEY_SIZE);

	fprintf(stderr, "securevnc_setup: RSA key has MD5 sum: %s\n", rsa_md5_sum(rsabuf)); 
	fprintf(stderr, "securevnc_setup:\n"); 
	fprintf(stderr, "securevnc_setup: One way to print out the SecureVNC Server key MD5 sum is:\n\n"); 
	fprintf(stderr, "openssl rsa -inform DER -outform DER -pubout -in ./Server_SecureVNC.pkey | dd bs=1 skip=24 | md5sum\n\n");
	if (securevnc_file == NULL) {
		fprintf(stderr, "securevnc_setup:\n");
		fprintf(stderr, "securevnc_setup: ** WARNING: ULTRAVNC SERVER RSA KEY NOT VERIFIED.   **\n");
		fprintf(stderr, "securevnc_setup: ** WARNING: A MAN-IN-THE-MIDDLE ATTACK IS POSSIBLE. **\n");
		fprintf(stderr, "securevnc_setup:\n");
	} else {
		char *q = strrchr(securevnc_file, 'C');
		int skip = 0;
		if (q) {
			if (!strcmp(q, "ClientAuth.pkey")) {
				client_auth = strdup(securevnc_file);
				skip = 1;
			} else if (!strcmp(q, "ClientAuth.pkey.rsa")) {
				client_auth = strdup(securevnc_file);
				q = strrchr(client_auth, '.');
				*q = '\0';
			}
		}
		if (!skip) {
			rc = securevnc_check_server_rsa(securevnc_file, rsabuf);
		}
		if (skip) {
			;
		} else if (rc == 0) {
			fprintf(stderr, "securevnc_setup:\n");
			fprintf(stderr, "securevnc_setup: VERIFY_ERROR: SERVER RSA KEY DID NOT MATCH:\n");
			fprintf(stderr, "securevnc_setup:     %s\n", securevnc_file);
			fprintf(stderr, "securevnc_setup:\n");
			exit(1);
		} else if (rc == -1) {
			fprintf(stderr, "securevnc_setup: User cancelled the save and hence the connection.\n");
			fprintf(stderr, "securevnc_setup:     %s\n", securevnc_file);
			exit(1);
		} else if (rc == 1) {
			fprintf(stderr, "securevnc_setup: VERIFY SUCCESS: server rsa key matches the contents of:\n");
			fprintf(stderr, "securevnc_setup:     %s\n", securevnc_file);
			keystore_verified = 1;
		} else if (rc == 2) {
			fprintf(stderr, "securevnc_setup: Server rsa key stored in:\n");
			fprintf(stderr, "securevnc_setup:     %s\n", securevnc_file);
			keystore_verified = 2;
		}
	}

	/*
	 * read in the server flags. Note that SecureVNCPlugin sends these
	 * in little endian and not network order!!
	 */
	read(server, (char *) &b1, 1);
	read(server, (char *) &b2, 1);
	read(server, (char *) &b3, 1);
	read(server, (char *) &b4, 1);
	
	server_flags = 0;
	server_flags |= ((unsigned int) b4) << 24;
	server_flags |= ((unsigned int) b3) << 16;
	server_flags |= ((unsigned int) b2) << 8;
	server_flags |= ((unsigned int) b1) << 0;
	fprintf(stderr, "securevnc_setup: server_flags: 0x%08x\n", server_flags);

	/* check for arc4 usage: */
	if (server_flags & 0x1) {
		fprintf(stderr, "securevnc_setup: server uses AES cipher.\n");
	} else {
		fprintf(stderr, "securevnc_setup: server uses ARC4 cipher.\n");
		securevnc_arc4 = 1;
		Cipher = EVP_rc4();
	}

	/* check for client auth signature requirement: */
	if (server_flags & (sig << 24)) {
		fprintf(stderr, "securevnc_setup: server requires Client Auth signature.\n");
		client_auth_req = 1;
		if (!client_auth) {
			fprintf(stderr, "securevnc_setup: However, NO *ClientAuth.pkey keyfile was supplied on our\n");
			fprintf(stderr, "securevnc_setup: command line.  Exiting.\n");
			exit(1);
		}
	}

	/*
	 * The first packet 'RFB 003.006' is obscured with key
	 * that is a sha1 hash of public key.  So make this tmp key now:
 	 *
	 */
	initkey = (unsigned char *) calloc(SECUREVNC_KEY_SIZE, 1);
	EVP_BytesToKey(EVP_rc4(), EVP_sha1(), NULL, rsabuf, SECUREVNC_RSA_PUBKEY_SIZE, 1, initkey, NULL);

	/* expand the transported rsabuf into an rsa object */
	rsa = d2i_RSAPublicKey(NULL, (const unsigned char **) &rsabuf, SECUREVNC_RSA_PUBKEY_SIZE);
	if (rsa == NULL) {
		sslexit("securevnc_setup: failed to create rsa");
	}

	/*
	 * Back to the work involving the tmp obscuring key:
	 */
	EVP_CIPHER_CTX_init(&init_ctx);
	rc = EVP_CipherInit_ex(&init_ctx, EVP_rc4(), NULL, initkey, NULL, 1);
	if (rc == 0) {
		sslexit("securevnc_setup: EVP_CipherInit_ex(init_ctx) failed");
	}

	/* for the first obscured packet, read what we can... */
	n = read(server, (char *) buf, BSIZE);
	fprintf(stderr, "securevnc_setup: data read: %d\n", n);
	if (n < 0) {
		exit(1);
	}
	fprintf(stderr, "securevnc_setup: initial data[%d]: ", n);
	
	/* decode with the tmp key */
	if (n > 0) {
		memset(to_viewer, 0, sizeof(to_viewer));
		if (EVP_CipherUpdate(&init_ctx, to_viewer, &len, buf, n) == 0) {
			sslexit("securevnc_setup: EVP_CipherUpdate(init_ctx) failed");
			exit(1);
		}
		to_viewer_len = len;
	}
	EVP_CIPHER_CTX_cleanup(&init_ctx);
	free(initkey);

	/* print what we would send to the viewer (sent below): */
	write(2, to_viewer, 12);	/* and first 12 bytes 'RFB ...' as message */

	/* now create the random session key: */
	encrypted_keybuf = (unsigned char*) calloc(RSA_size(rsa), 1);

	fprintf(stderr, "securevnc_setup: creating random session key: %d/%d\n",
	    SECUREVNC_KEY_SIZE, SECUREVNC_RAND_KEY_SOURCE);
	keydata_len = SECUREVNC_RAND_KEY_SOURCE;

	rc = RAND_bytes((unsigned char *)keydata, SECUREVNC_RAND_KEY_SOURCE);
	if (rc <= 0) {
		fprintf(stderr, "securevnc_setup: RAND_bytes() failed: %s\n", ERR_error_string(ERR_get_error(), NULL));
		rc = RAND_pseudo_bytes((unsigned char *)keydata, SECUREVNC_RAND_KEY_SOURCE);
		fprintf(stderr, "securevnc_setup: RAND_pseudo_bytes() rc=%d\n", rc);
		if (getenv("RANDSTR")) {
			char *s = getenv("RANDSTR"); 
			fprintf(stderr, "securevnc_setup: seeding with RANDSTR len=%d\n", strlen(s));
			RAND_add(s, strlen(s), strlen(s));
		}
	}

	/* N.B. this will be repeated in enc_xfer() setup. */
	EVP_BytesToKey(Cipher, Digest, NULL, (unsigned char *) keydata, keydata_len, 1, keystr, NULL);

	/* encrypt the session key with the server's public rsa key: */
	n = RSA_public_encrypt(SECUREVNC_KEY_SIZE, keystr, encrypted_keybuf, rsa, RSA_PKCS1_PADDING);
	if (n == -1) {
		sslexit("securevnc_setup: RSA_public_encrypt() failed");
		exit(1);
	}
	fprintf(stderr, "securevnc_setup: encrypted session key size: %d. sending to server.\n", n);

	/* send it to the server: */
	write(server, encrypted_keybuf, n);
	free(encrypted_keybuf);

	/*
	 * Reply back with flags indicating cipher (same as one sent to
	 * us) and we do not want client-side auth.
	 *
	 * We send it out on the wire in little endian order:
	 */
	if (securevnc_arc4) {
		write(server, (char *)&zero, 1);
	} else {
		write(server, (char *)&one, 1);
	}
	write(server, (char *)&zero, 1);
	write(server, (char *)&zero, 1);
	if (client_auth_req) {
		write(server, (char *)&sig, 1);
	} else {
		write(server, (char *)&zero, 1);
	}

	if (client_auth_req && client_auth) {
		RSA *client_rsa = load_client_auth(client_auth);
		EVP_MD_CTX dctx;
		unsigned char digest[EVP_MAX_MD_SIZE], *signature;
		unsigned int ndig = 0, nsig = 0;

		if (0) {
			/* for testing only, use the wrong RSA key: */
			client_rsa = RSA_generate_key(2048, 0x10001, NULL, NULL);
		}
		
		if (client_rsa == NULL) {
			fprintf(stderr, "securevnc_setup: problem reading rsa key from '%s'\n", client_auth);
			exit(1);
		}

		EVP_DigestInit(&dctx, EVP_sha1());
		EVP_DigestUpdate(&dctx, keystr, SECUREVNC_KEY_SIZE);
		/*
		 * Without something like the following MITM is still possible.
		 * This is because the MITM knows keystr and can use it with
		 * the server connection as well, and then he just forwards our
		 * signed digest.  The additional information below would be the
		 * MITM's rsa public key, and so the real VNC server will notice
		 * the difference.  And MITM can't sign keystr+server_rsa.pub since
		 * he doesn't have Viewer_ClientAuth.pkey.
		 */
		if (0) {
			EVP_DigestUpdate(&dctx, rsasav, SECUREVNC_RSA_PUBKEY_SIZE);
			if (!keystore_verified) {
				fprintf(stderr, "securevnc_setup:\n");
				fprintf(stderr, "securevnc_setup: Warning: even *WITH* Client Authentication in SecureVNC,\n");
				fprintf(stderr, "securevnc_setup: an attacker may be able to trick you into connecting to his\n");
				fprintf(stderr, "securevnc_setup: fake VNC server and supplying VNC or Windows passwords, etc.\n");
				fprintf(stderr, "securevnc_setup: To increase security manually verify the Server RSA key's MD5\n");
				fprintf(stderr, "securevnc_setup: checksum and then have SSVNC save the key in its keystore to\n");
				fprintf(stderr, "securevnc_setup: be used to verify the server in subsequent connections.\n");
				fprintf(stderr, "securevnc_setup:\n");
			}
		} else {
			if (!keystore_verified) {
				fprintf(stderr, "securevnc_setup:\n");
				fprintf(stderr, "securevnc_setup: WARNING: THE FIRST VERSION OF THE SECUREVNC PROTOCOL IS\n");
				fprintf(stderr, "securevnc_setup: WARNING: BEING USED.  *EVEN* WITH CLIENT AUTHENTICATION IT\n");
				fprintf(stderr, "securevnc_setup: WARNING: IS SUSCEPTIBLE TO A MAN-IN-THE-MIDDLE ATTACK.\n");
				fprintf(stderr, "securevnc_setup: To increase security manually verify the Server RSA key's MD5\n");
				fprintf(stderr, "securevnc_setup: checksum and then have SSVNC save the key in its keystore to\n");
				fprintf(stderr, "securevnc_setup: be used to verify the server in subsequent connections.\n");
				fprintf(stderr, "securevnc_setup:\n");
			}
		}
		EVP_DigestFinal(&dctx, (unsigned char *)digest, &ndig);

		signature = (unsigned char *) calloc(RSA_size(client_rsa), 1);
		RSA_sign(NID_sha1, digest, ndig, signature, &nsig, client_rsa);

		fprintf(stderr, "securevnc_setup: sending ClientAuth.pkey signed data: %d\n", nsig);
		write(server, signature, nsig);
		free(signature);

		RSA_free(client_rsa);
	}

	fprintf(stderr, "securevnc_setup: done.\n");

	/* now send the 'RFB ...' to the viewer */
	if (to_viewer_len > 0) {
		write(viewer, to_viewer, to_viewer_len);
	}
}

#ifndef ENC_DISABLE_SHOW_CERT
static void enc_sslerrexit(void) {
	unsigned long err = ERR_get_error();

	if (err) {
		char str[256];
		ERR_error_string(err, str);
		fprintf(stdout, "ssl error: %s\n", str);
	}
	exit(1);
}
#endif

static void show_cert(int sock) {
#ifndef ENC_DISABLE_SHOW_CERT
	SSL_CTX *ctx;
	SSL *ssl = NULL;
	STACK_OF(X509) *sk = NULL;
	X509 *peer = NULL;
	SSL_CIPHER *c;
	BIO *bio;
	unsigned char *sid =  (unsigned char *) "ultravnc_dsm_helper SID";
	long mode;
	int i;

	fprintf(stdout, "CONNECTED(%08X)\n",sock);

	SSL_library_init();
	SSL_load_error_strings();

	if (!RAND_status()) {
		RAND_poll();
	}
	/* this is not for a secured connection. */
	for (i=0; i < 100; i++) {
		if (!RAND_status()) {
			char tmp[32];
			sprintf(tmp, "%d", getpid() * (17 + i));
			RAND_add(tmp, strlen(tmp), 5);
		} else {
			break;
		}
	}

	ctx = SSL_CTX_new( SSLv23_client_method() );
	if (ctx == NULL) {
		fprintf(stdout, "show_cert: SSL_CTX_new failed.\n");
		close(sock);
		enc_sslerrexit();
	}

	mode = 0;
	mode |= SSL_MODE_ENABLE_PARTIAL_WRITE;
	mode |= SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER;
	SSL_CTX_set_mode(ctx, mode);

	if (getenv("ULTRAVNC_DSM_HELPER_SHOWCERT_ADH")) {
		SSL_CTX_set_cipher_list(ctx, "ADH:@STRENGTH");
		SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
	}

	ssl = SSL_new(ctx);

	if (ssl == NULL) {
		fprintf(stdout, "show_cert: SSL_new failed.\n");
		close(sock);
		enc_sslerrexit();
	}

	SSL_set_session_id_context(ssl, sid, strlen((char *)sid));

	if (! SSL_set_fd(ssl, sock)) {
		fprintf(stdout, "show_cert: SSL_set_fd failed.\n");
		close(sock);
		enc_sslerrexit();
	}

	SSL_set_connect_state(ssl);

	if (SSL_connect(ssl) <= 0) {
		unsigned long err = ERR_get_error();
		fprintf(stdout, "show_cert: SSL_connect failed.\n");
		if (err) {
			char str[256];
			ERR_error_string(err, str);
			fprintf(stdout, "ssl error: %s\n", str);
		}
	}

	SSL_get_verify_result(ssl);

	sk = SSL_get_peer_cert_chain(ssl);
	if (sk != NULL) {
		fprintf(stdout, "---\nCertificate chain\n");
		for (i=0; i < sk_X509_num(sk); i++) {
			char buf[2048];
			X509_NAME_oneline(X509_get_subject_name(sk_X509_value(sk,i)), buf, sizeof buf);
			fprintf(stdout, "%2d s:%s\n", i, buf);
			X509_NAME_oneline(X509_get_issuer_name(sk_X509_value(sk,i)), buf, sizeof buf);
			fprintf(stdout, "   i:%s\n", buf);
		}
	} else {
		fprintf(stdout, "show_cert: SSL_get_peer_cert_chain failed.\n");
	}
	fprintf(stdout, "---\n");
	peer = SSL_get_peer_certificate(ssl);
	bio = BIO_new_fp(stdout, BIO_NOCLOSE);
	if (peer != NULL) {
		char buf[2048];
		BIO_printf(bio,"Server certificate\n");
		PEM_write_bio_X509(bio, peer);

		X509_NAME_oneline(X509_get_subject_name(peer), buf, sizeof buf);
		BIO_printf(bio,"subject=%s\n",buf);
		X509_NAME_oneline(X509_get_issuer_name(peer), buf, sizeof buf);
		BIO_printf(bio,"issuer=%s\n",buf);
	} else {
		fprintf(stdout, "show_cert: SSL_get_peer_certificate failed.\n");
	}

	c = SSL_get_current_cipher(ssl);
	BIO_printf(bio,"---\nNew, %s, Cipher is %s\n", SSL_CIPHER_get_version(c), SSL_CIPHER_get_name(c));

	if (peer != NULL) {
		EVP_PKEY *pktmp;
		pktmp = X509_get_pubkey(peer);
		BIO_printf(bio,"Server public key is %d bit\n", EVP_PKEY_bits(pktmp));
		EVP_PKEY_free(pktmp);
	}
	BIO_printf(bio,"---\nDONE\n---\n");
	
	fflush(stdout);

#endif
	close(sock);
	exit(0);
}

#ifndef SOL_IPV6
#ifdef  IPPROTO_IPV6
#define SOL_IPV6 IPPROTO_IPV6
#endif
#endif

/*
 * Listens on incoming port for a client, then connects to remote server.
 * Then forks into two processes one is the encrypter the other the
 * decrypter.
 */
static void enc_connections(int listen_port, char *connect_host, int connect_port) {
	int listen_fd = -1, listen_fd6 = -1, conn1 = -1, conn2 = -1, ret, one = 1;
	socklen_t clen;
	struct hostent *hp;
	struct sockaddr_in client, server;
	fd_set fds;
	int maxfd = -1;

	/* zero means use stdio (preferably from socketpair()) */
	if (listen_port == 0) {
		conn1 = fileno(stdin);
		goto use_stdio;
	}

	if (!strcmp(cipher, "showcert")) {
		goto use_stdio;
	}

	/* fd=n,m means use the supplied already established sockets */
	if (sscanf(connect_host, "fd=%d,%d", &conn1, &conn2) == 2) {
		goto use_input_fds;
	}

	/* create the listening socket: */
	memset(&client, 0, sizeof(client));
	client.sin_family = AF_INET;
	if (listen_port < 0) {
		/* negative port means use loopback */
		client.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
		client.sin_port = htons(-listen_port);
	} else {
		client.sin_addr.s_addr = htonl(INADDR_ANY);
		client.sin_port = htons(listen_port);
	}

	listen_fd = socket(AF_INET, SOCK_STREAM, 0); 
	if (listen_fd < 0) {
		perror("socket");
		goto try6;
	}

	ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR,
	    (char *)&one, sizeof(one));
	if (ret < 0) {
		perror("setsockopt");
		close(listen_fd);
		listen_fd = -1;
		goto try6;
	}

	ret = bind(listen_fd, (struct sockaddr *) &client, sizeof(client));
	if (ret < 0) {
		perror("bind");
		close(listen_fd);
		listen_fd = -1;
		goto try6;
	}

	ret = listen(listen_fd, 2);
	if (ret < 0) {
		perror("listen");
		close(listen_fd);
		listen_fd = -1;
		goto try6;
	}

	try6:
#ifdef AF_INET6
	if (!getenv("ULTRAVNC_DSM_HELPER_NOIPV6")) {
		struct sockaddr_in6 sin;
		int one = 1, sock = -1;

		sock = socket(AF_INET6, SOCK_STREAM, 0);
		if (sock < 0) {
			perror("socket6");
			goto fail;
		}

		if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)) < 0) {
			perror("setsockopt6 SO_REUSEADDR");
			close(sock);
			sock = -1;
			goto fail;
		}

#if defined(SOL_IPV6) && defined(IPV6_V6ONLY)
		if (setsockopt(sock, SOL_IPV6, IPV6_V6ONLY, (char *)&one, sizeof(one)) < 0) {
			perror("setsockopt6 IPV6_V6ONLY");
			close(sock);
			sock = -1;
			goto fail;
		}
#endif

		memset((char *)&sin, 0, sizeof(sin));
		sin.sin6_family = AF_INET6;

		if (listen_port < 0) {
			sin.sin6_addr = in6addr_loopback;
			sin.sin6_port = htons(-listen_port);
		} else {
			sin.sin6_addr = in6addr_any;
			sin.sin6_port = htons(listen_port);
		}

		if (bind(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
			perror("bind6");
			close(sock);
			sock = -1;
			goto fail;
		}

		if (listen(sock, 2) < 0) {
			perror("listen6");
			close(sock);
			sock = -1;
			goto fail;
		}

		fail:
		listen_fd6 = sock;
	}
#endif

	if (listen_fd < 0 && listen_fd6 < 0) {
		fprintf(stderr, "%s: could not listen on port: %d\n",
		    prog, listen_port);
		exit(1);
	}

	fprintf(stderr, "%s: waiting for connection on port: %d\n",
	    prog, listen_port);

	/* wait for a connection: */
	FD_ZERO(&fds);
	if (listen_fd >= 0) {
		FD_SET(listen_fd, &fds);
		if (listen_fd > maxfd) {
			maxfd = listen_fd;
		}
	}
	if (listen_fd6 >= 0) {
		FD_SET(listen_fd6, &fds);
		if (listen_fd6 > maxfd) {
			maxfd = listen_fd6;
		}
	}
	if (select(maxfd+1, &fds, NULL, NULL, NULL) <= 0) {
		perror("select");
		exit(1);
	}

	if (FD_ISSET(listen_fd, &fds)) {
		clen = sizeof(client);
		conn1 = accept(listen_fd, (struct sockaddr *) &client, &clen);
		if (conn1 < 0) {
			perror("accept");
			exit(1);
		}
	} else if (FD_ISSET(listen_fd6, &fds)) {
#ifdef AF_INET6
		struct sockaddr_in6 addr;
		socklen_t addrlen = sizeof(addr);

		conn1 = accept(listen_fd6, (struct sockaddr *) &addr, &addrlen);
		if (conn1 < 0) {
			perror("accept6");
			exit(1);
		}
#else
		fprintf(stderr, "No IPv6 / AF_INET6 support.\n");
		exit(1);
#endif
	}

	if (setsockopt(conn1, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one)) < 0) {
		perror("setsockopt TCP_NODELAY");
		exit(1);
	}

	/* done with the listening socket(s): */
	if (listen_fd >= 0) {
		close(listen_fd);
	}
	if (listen_fd6 >= 0) {
		close(listen_fd6);
	}

	if (getenv("ULTRAVNC_DSM_HELPER_BG")) {
		int p, n;
		if ((p = fork()) > 0)  {
			fprintf(stderr, "%s: putting child %d in background.\n",
			    prog, p);
			exit(0);
		} else if (p == -1) {
			fprintf(stderr, "%s: could not fork\n", prog);
			perror("fork");
			exit(1);
		}
		if (setsid() == -1) {
			fprintf(stderr, "%s: setsid failed\n", prog);
			perror("setsid");
			exit(1);
		}
		/* adjust our stdio */
		n = open("/dev/null", O_RDONLY);
		dup2(n, 0);
		dup2(n, 1);
		dup2(n, 2);
		if (n > 2) {
			close(n);
		}
	}

	use_stdio:

	fprintf(stderr, "%s: got connection: %d\n", prog, conn1);

	/* now connect to remote server: */
	memset(&server, 0, sizeof(server));
	server.sin_family = AF_INET;
	server.sin_port = htons(connect_port);

	if ((server.sin_addr.s_addr = inet_addr(connect_host)) == htonl(INADDR_NONE)) {
		if (!(hp = gethostbyname(connect_host))) {
			perror("gethostbyname");
			goto tryconn6;
		}
		server.sin_addr.s_addr = *(unsigned long *)hp->h_addr;
	}

	conn2 = socket(AF_INET, SOCK_STREAM, 0);
	if (conn2 < 0) {
		perror("socket");
		goto tryconn6;
	}

	if (connect(conn2, (struct sockaddr *)&server, (sizeof(server))) < 0) {
		perror("connect");
		goto tryconn6;
	}

	tryconn6:
#ifdef AF_INET6
	if (conn2 < 0 && !getenv("ULTRAVNC_DSM_HELPER_NOIPV6")) {
		int err;
		struct addrinfo *ai;
		struct addrinfo hints;
		char service[32];

		fprintf(stderr, "connect[ipv6]: trying to connect via IPv6 to %s\n", connect_host);
		conn2 = -1;

		memset(&hints, 0, sizeof(hints));
		sprintf(service, "%d", connect_port);

		hints.ai_family = AF_UNSPEC;
		hints.ai_socktype = SOCK_STREAM;
#ifdef AI_ADDRCONFIG
		hints.ai_flags |= AI_ADDRCONFIG;
#endif
#ifdef AI_NUMERICSERV
		hints.ai_flags |= AI_NUMERICSERV;
#endif

		err = getaddrinfo(connect_host, service, &hints, &ai);
		if (err != 0) {
			fprintf(stderr, "getaddrinfo[%d]: %s\n", err, gai_strerror(err));
		} else {
			struct addrinfo *ap = ai;
			while (ap != NULL) {
				int fd = -1;
				fd = socket(ap->ai_family, ap->ai_socktype, ap->ai_protocol);
				if (fd == -1) {
					perror("socket6");
				} else {
					int dmsg = 0; 
					int res = connect(fd, ap->ai_addr, ap->ai_addrlen);
#if defined(SOL_IPV6) && defined(IPV6_V6ONLY)
					if (res != 0) {
						int zero = 0;
						perror("connect6");
						dmsg = 1;
						if (setsockopt(fd, SOL_IPV6, IPV6_V6ONLY, (char *)&zero, sizeof(zero)) == 0) {
							fprintf(stderr, "connect[ipv6]: trying again with IPV6_V6ONLY=0\n");
							res = connect(fd, ap->ai_addr, ap->ai_addrlen);
							dmsg = 0;
						}
					}
#endif
					if (res == 0) {
						conn2 = fd; 
						break;
					} else {
						if (!dmsg) perror("connect6");
						close(fd);
					}
				}
				ap = ap->ai_next;
			}
			freeaddrinfo(ai);
		}
	}
#endif
	if (conn2 < 0) {
		fprintf(stderr, "could not connect to %s\n", connect_host);
		exit(1);
	}
	if (conn2 >= 0 && setsockopt(conn2, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one)) < 0) {
		perror("setsockopt TCP_NODELAY");
	}

	use_input_fds:

	if (!strcmp(cipher, "showcert")) {
		show_cert(conn2);
		close(conn2);
		exit(0);
	}

	if (securevnc) {
		securevnc_setup(conn1, conn2);
	}

	/* fork into two processes; one for each direction: */
	parent = getpid();
	
	child = fork();
	
	if (child == (pid_t) -1) {
		/* couldn't fork... */
		perror("fork");
		close(conn1);
		close(conn2);
		exit(1);
	}

	/* Do transfer/encode/decode loop: */

	if (child == 0) {
		/* encrypter: local-viewer -> remote-server */
		if (!strcmp(cipher, "none") || !strcmp(cipher, "relay")) {
			enc_raw_xfer(conn1, conn2);
		} else {
			enc_xfer(conn1, conn2, 1);
		}
	} else {
		/* decrypter: remote-server -> local-viewer */
		if (!strcmp(cipher, "none") || !strcmp(cipher, "relay")) {
			enc_raw_xfer(conn2, conn1);
		} else {
			enc_xfer(conn2, conn1, 0);
		}
	}
}
#endif /* ENC_HAVE_OPENSSL */

static void doloop (int argc, char *argv[]) {
	int ms = atoi(getenv("ULTRAVNC_DSM_HELPER_LOOP"));
	if (ms > 0) {
		char *cmd;
		int i, len = 0;
		for (i = 0; i < argc; i++) {
			len += strlen(argv[i]) + 2;
		}
		cmd = (char *)malloc(len);
		cmd[0] = '\0';
		for (i = 0; i < argc; i++) {
			strcat(cmd, argv[i]);
			if (i < argc - 1) {
				strcat(cmd, " ");
			}
		}

		putenv("ULTRAVNC_DSM_HELPER_LOOP_SET=1");
		if (ms == 1) {
			ms = 500;
		}
		i = 0;
		while (1) {
			fprintf(stderr, "loop running[%d]: %s\n", ++i, cmd);
			system(cmd);
			usleep(1000 * ms);
		}
	}
}

int main (int argc, char *argv[]) {
	char *kf, *q;

	if (getenv("ULTRAVNC_DSM_HELPER_LOOP")) {
		if (!getenv("ULTRAVNC_DSM_HELPER_LOOP_SET")) {
			doloop(argc, argv);
		}
	}

	if (argc == 3) {
		if (!strcmp(argv[1], "showcert")) {
			enc_do(argv[1], NULL, NULL, argv[2]);
			return 0;
		}
	}
	if (argc == 4) {
		if (!strcmp(argv[1], "none") || !strcmp(argv[1], "relay")) {
			enc_do(argv[1], NULL, argv[2], argv[3]);
			return 0;
		}
	}
	if (argc < 5) {
		fprintf(stdout, "%s\n", usage);
		exit(1);
	}

	/* guard against pw= on cmdline (e.g. linux) */
	kf = strdup(argv[2]);
	q = strstr(argv[2], "pw=");
	if (q) {
		while (*q != '\0') {
			*q = '\0';	/* now ps(1) won't show it */
			q++;
		}
	}

	enc_do(argv[1], kf, argv[3], argv[4]);

	return 0;
}

/*
 * a crude utility to have this work "keyless" i.e. the vnc password
 * is used instead of a pre-shared key file.
 */

/*

#!/usr/bin/perl
#
# md5_to_rc4key.pl
#
# This program requires md5sum(1) installed on your machine.
#
# It translates a VNC password to a ultravnc dsm plugin 
# compatible key file.
#
# Supply VNC password on cmdline, capture in key file:
#
#	md5_to_rc4key.pl swordfish    > rc4.key
#	md5_to_rc4key.pl -a swordfish > arc4.key
#
# Use rc4.key with ultravnc_dsm_helper in msrc4 mode,
# or arc4.key in either arc4 or aesv4 mode.
#
#
$rfmt = 1;
if ($ARGV[0] eq '-a') {
	$rfmt = 0;
	shift;
}

# n.b. this is not super secure against bad locals...

$pw = shift;
$tmp = "/tmp/md5out.$$";

open(MD5, "| md5sum > $tmp");
print MD5 $pw;
close MD5;

$md5 = `cat $tmp`;
unlink $tmp;

($md5, $junk) = split(/\s/, $md5);

print "128 bit" if $rfmt;
print 'a' x 4	if $rfmt;
print 'b' x 12	if $rfmt;

$str = '';
foreach $d (split(//, $md5)) {
	$str .= $d;
	if (length($str) == 2) {
		push @key, $str;
		$str = '';
	}
}

@key = (reverse @key) if $rfmt;

foreach $h (@key) {
	$c = pack('c', hex("0x$h"));	
	print $c;
}

print 'c' x 48	if $rfmt;

*/
