/*
 * Copyright (c) 1999 - 2002 Peter 'Luna' Runestig <peter@runestig.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modifi-
 * cation, are permitted provided that the following conditions are met:
 *
 *   o  Redistributions of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 *
 *   o  Redistributions in binary form must reproduce the above copyright no-
 *      tice, this list of conditions and the following disclaimer in the do-
 *      cumentation and/or other materials provided with the distribution.
 *
 *   o  The names of the contributors may not be used to endorse or promote
 *      products derived from this software without specific prior written
 *      permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 LI-
 * ABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUEN-
 * TIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEV-
 * ER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABI-
 * LITY, 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.
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifndef lint
static char copyright[] =
"@(#) Copyright (c) Peter 'Luna' Runestig 1999 - 2002 <peter@runestig.com>.\n";
#endif /* not lint */

#if !defined(PR_OBSD_FTP) && !defined(PR_TELNET)
#error You must define PR_OBSD_FTP or PR_TELNET !
#endif

#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif /* HAVE_NETDB_H */
#include <sys/param.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/bio.h>

#ifdef TLS_SESSION_FILE_CACHE
#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <openssl/pem.h>
#endif /* TLS_SESSION_FILE_CACHE */

#ifdef PR_TELNET
#include "ring.h"
#include "externs.h"
#define MODE_EDIT		0x01
void	NetNonblockingIO(int fd, int onoff);
extern int	net;	/* the socket */
extern int	tin;	/* stdin */
extern char	*hostname;
FILE *ttyout;
#define SOCK_TO_SSL(s)		\
    ( tls_active ? ssl : NULL )
#endif /* PR_TELNET */

#ifdef PR_OBSD_FTP
# ifdef __STDC__
#  include <stdarg.h>
# else
#  include <varargs.h>
# endif
typedef struct _CONN {
    SSL *ssl;
    int sock;
} CONN;
#define SOCK_TO_SSL(s)				\
    ( s == data_conn.sock ? data_conn.ssl : 	\
	( s == ctrl_conn.sock ? ctrl_conn.ssl : NULL ) )
extern	FILE  *ttyout;	/* stdout or stderr, depending on interactive */
#endif /* PR_OBSD_FTP */

/* define if you want to check for OpenSSL-related memory leaks */
/* #define DEBUG_OPENSSL_MEM */
#define FPUTC_BUFFERSIZE	1024
#define DEFAULTCIPHERLIST	"HIGH:MEDIUM:LOW:+KRB5:+ADH:+EXP"

void	tls_cleanup(void);
void	tls_fputc_fflush(int fd);
int	x509rc_read_filenames(void);

static unsigned char fputc_buffer[FPUTC_BUFFERSIZE];
static int fputc_buflen = 0;
static int verify_error_flag = 0;
static int x509rc_override = 0;
static int tls_nozlib = 0;
char	*tls_key_file = NULL;
char	*tls_cert_file = NULL;
char	*tls_capath_dir = NULL;
char	*tls_cafile_file = NULL;
char	*tls_crl_file = NULL;
char	*tls_crl_dir = NULL;
char	*tls_rand_file = NULL;
char	*tls_hostname = NULL;	/* hostname used by user to connect */
char	*tls_cipher_list = NULL;
static SSL_CTX *ssl_ctx = NULL;
static X509_STORE *crl_store = NULL;
static SSL_METHOD *tls_method = NULL;

#ifdef PR_TELNET
int 	tls_active = 0;
int 	tls_suspend_iacs = 0;
int 	tls_reset_state = 0;
static SSL *ssl = NULL;
#endif /* PR_TELNET */

#ifdef PR_OBSD_FTP
CONN	data_conn = { NULL, -1 }, ctrl_conn = { NULL, -1 };
SSL	*ctrl_ssl_clone = NULL;
int	tls_want_on_ctrl = 1;
int	tls_want_on_data = 0;
int	tls_no_verify = 0;
#endif /* PR_OBSD_FTP */

void tls_set_cipher_list(char *list)
{
    if (tls_cipher_list)
	free(tls_cipher_list);
    tls_cipher_list = strdup(list);
}

#ifdef PR_OBSD_FTP
int tls_active(int s)
{
    if (SOCK_TO_SSL(s))
	return 1;
    else
	return 0;
}
#endif /* PR_OBSD_FTP */

char *tls_get_cipher_info_string(int fd)
{
    static char r[60];
    SSL *s = SOCK_TO_SSL(fd);

    if (s)
	snprintf(r, sizeof(r), "cipher %s (%d bits)", SSL_get_cipher(s),
		 SSL_get_cipher_bits(s, NULL));
    else
	snprintf(r, sizeof(r), "clear");
    return r;
}

void tls_set_defaults(void)
{
#ifdef ZLIB	/* compression doesn't work with SSLv23_client_method() */
    tls_method = SSLv3_client_method();
#else
    tls_method = SSLv23_client_method();
#endif /* !ZLIB */
    if (!tls_cipher_list)
	tls_cipher_list = strdup(DEFAULTCIPHERLIST);
}

int tls_optarg(char *optarg)
{
    char *p;

    if ((p = strchr(optarg, '='))) {
    	*p++ = 0;
	if (!strcmp(optarg, "cert")) {
	    tls_cert_file = strdup(p);
	    x509rc_override = 1;
	}
	else if (!strcmp(optarg, "key"))
	    tls_key_file = strdup(p);
	else if (!strcmp(optarg, "CAfile"))
	    tls_cafile_file = strdup(p);
	else if (!strcmp(optarg, "CApath"))
	    tls_capath_dir = strdup(p);
	else if (!strcmp(optarg, "crlfile"))
	    tls_crl_file = strdup(p);
	else if (!strcmp(optarg, "crldir"))
	    tls_crl_dir = strdup(p);
	else if (!strcmp(optarg, "cipher"))
	    tls_set_cipher_list(p);
	else
	    return 1;
    }
    else if (!strcmp(optarg, "sslv3"))
	tls_method = SSLv3_client_method();
    else if (!strcmp(optarg, "tlsv1"))
	tls_method = TLSv1_client_method();
    else if (!strcmp(optarg, "nozlib"))
	tls_nozlib = 1;
#ifdef PR_OBSD_FTP
    else if (!strcmp(optarg, "noprotect"))
	tls_want_on_ctrl = 0;
    else if (!strcmp(optarg, "private"))
	tls_want_on_data = 1;
    else if (!strcmp(optarg, "certsok"))
	tls_no_verify = 1;
#endif /* PR_OBSD_FTP */
    else
	return 1;

    return 0;
}

char **tls_get_SAN_objs(SSL *s, int type)
/* returns NULL or an array of malloc'ed objects of type `type' from the server's
 * subjectAltName, remember to free() them all!
 */
{
#define NUM_SAN_OBJS 50
    static char *objs[NUM_SAN_OBJS];
    char **rv = NULL;
    X509 *server_cert = NULL;
    int i, j;
    X509_EXTENSION *ext = NULL;
    STACK_OF(GENERAL_NAME) *ialt = NULL;
    GENERAL_NAME *gen = NULL;

    memset(objs, 0, sizeof(objs));
    if ((server_cert = SSL_get_peer_certificate(s))) {
    	if ((i = X509_get_ext_by_NID(server_cert, NID_subject_alt_name, -1)) < 0)
	    goto eject;
	if (!(ext = X509_get_ext(server_cert, i)))
	    goto eject;
	if (!(ialt = X509V3_EXT_d2i(ext)))
	    goto eject;
	rv = objs;
	for (i = 0, j = 0; i < sk_GENERAL_NAME_num(ialt) && j < NUM_SAN_OBJS - 2; i++) {
	    gen = sk_GENERAL_NAME_value(ialt, i);
	    if (gen->type == type) {
		if(!gen->d.ia5 || !gen->d.ia5->length)
		    continue;
		objs[j] = malloc(gen->d.ia5->length + 1);
		if (objs[j]) {
		    memcpy(objs[j], gen->d.ia5->data, gen->d.ia5->length);
		    objs[j][gen->d.ia5->length] = 0;
		    j++;
		}
	    }
	    GENERAL_NAME_free(gen);
	}
    }
eject:
    if (ialt)		sk_GENERAL_NAME_free(ialt);
    if (server_cert)	X509_free(server_cert);
    return rv;
}

char *x509v3_subjectAltName_oneline(SSL *s, char *buf, int len)
{
    X509 *server_cert = NULL;
    X509_EXTENSION *ext = NULL;
    BIO *mem = NULL;
    char *data = NULL, *rv = NULL;

    if ((server_cert = SSL_get_peer_certificate(s))) {
	int i, data_len = 0, ok;
    	if ((i = X509_get_ext_by_NID(server_cert, NID_subject_alt_name, -1)) < 0)
	    goto eject;
	if (!(ext = X509_get_ext(server_cert, i)))
	    goto eject;
	if (!(mem = BIO_new(BIO_s_mem())))
	    goto eject;
	
	ok = X509V3_EXT_print(mem, ext, 0, 0);
	if (ok)
	    data_len = BIO_get_mem_data(mem, &data);
	if (data) {
	    /* the 'data' returned is not '\0' terminated */
	    if (buf) {
		memcpy(buf, data, data_len < len ? data_len : len);
		buf[data_len < len ? data_len : len - 1] = 0;
		rv = buf;
		goto eject;
	    } else {
		char *b = malloc(data_len + 1);
		if (b) {
		    memcpy(b, data, data_len);
		    b[data_len] = 0;
		}
		rv = b;
		goto eject;
	    }
	} else
	    goto eject;
    }
eject:
    if (server_cert)	X509_free(server_cert);
    if (mem)		BIO_free(mem);
    return rv;
}

char *tls_get_commonName(SSL *s)
{
    static char name[256];
    int err = 0;
    X509 *server_cert;
    
    if ((server_cert = SSL_get_peer_certificate(s))) {
    	err = X509_NAME_get_text_by_NID(X509_get_subject_name(server_cert),
		NID_commonName, name, sizeof(name));
	X509_free(server_cert);
    }
    if (err > 0)
    	return name;
    else
    	return NULL;
}

/* if we are using OpenSSL 0.9.6 or newer, we want to use X509_NAME_print_ex()
 * instead of X509_NAME_oneline().
 */
char *x509_name_oneline(X509_NAME *n, char *buf, int len)
{
#if OPENSSL_VERSION_NUMBER < 0x000906000
    return X509_NAME_oneline(n, buf, len);
#else
    BIO *mem = BIO_new(BIO_s_mem());
    char *data = NULL;
    int data_len = 0, ok;
    
    ok = X509_NAME_print_ex(mem, n, 0, XN_FLAG_ONELINE);
    if (ok)
	data_len = BIO_get_mem_data(mem, &data);
    if (data) {
	/* the 'data' returned is not '\0' terminated */
	if (buf) {
	    memcpy(buf, data, data_len < len ? data_len : len);
	    buf[data_len < len ? data_len : len - 1] = 0;
	    BIO_free(mem);
	    return buf;
	} else {
	    char *b = malloc(data_len + 1);
	    if (b) {
		memcpy(b, data, data_len);
		b[data_len] = 0;
	    }
	    BIO_free(mem);
	    return b;
	}
    } else {
	BIO_free(mem);
	return NULL;
    }
#endif /* OPENSSL_VERSION_NUMBER >= 0x000906000 */
}

char *tls_get_issuer_name(SSL *s)
{
    static char name[256];
    X509 *server_cert;

    if ((server_cert = SSL_get_peer_certificate(s))) {
	char *n = x509_name_oneline(X509_get_issuer_name(server_cert), name, sizeof(name));
	X509_free(server_cert);
	return n;
    }
    else
    	/* no certificate from server */
	return NULL;
}

char *tls_get_subject_name(SSL *s)
{
    static char name[256];
    X509 *server_cert;

    if ((server_cert = SSL_get_peer_certificate(s))) {
	char *n = x509_name_oneline(X509_get_subject_name(server_cert), name, sizeof(name));
	X509_free(server_cert);
	return n;
    }
    else
    	/* no certificate from server */
	return NULL;
}

static char read_char(void)
{
    char inl[10];

#ifdef PR_TELNET
    /* we must add MODE_EDIT for the fgets(), or CR won't terminate the line */
    int mode = getconnmode();
    TerminalNewMode(mode | MODE_EDIT);
    NetNonblockingIO(tin, 0);
    fgets(inl, sizeof(inl), stdin);
    NetNonblockingIO(tin, 1);
    TerminalNewMode(mode);
#else
    fgets(inl, sizeof(inl), stdin);
#endif /* !PR_TELNET */

    return *inl;
}

/* this one is (very much!) based on work by Ralf S. Engelschall <rse@engelschall.com>.
 * comments by Ralf.
 */
static int verify_crl(int ok, X509_STORE_CTX *ctx)
{
    X509_OBJECT obj;
    X509_NAME *subject;
    X509_NAME *issuer;
    X509 *xs;
    X509_CRL *crl;
    X509_REVOKED *revoked;
    X509_STORE_CTX store_ctx;
    long serial;
    int i, n, rc;
    char *cp;

    /*
     * Unless a revocation store for CRLs was created we
     * cannot do any CRL-based verification, of course.
     */
    if (!crl_store)
        return ok;

    /*
     * Determine certificate ingredients in advance
     */
    xs      = X509_STORE_CTX_get_current_cert(ctx);
    subject = X509_get_subject_name(xs);
    issuer  = X509_get_issuer_name(xs);

    /*
     * OpenSSL provides the general mechanism to deal with CRLs but does not
     * use them automatically when verifying certificates, so we do it
     * explicitly here. We will check the CRL for the currently checked
     * certificate, if there is such a CRL in the store.
     *
     * We come through this procedure for each certificate in the certificate
     * chain, starting with the root-CA's certificate. At each step we've to
     * both verify the signature on the CRL (to make sure it's a valid CRL)
     * and it's revocation list (to make sure the current certificate isn't
     * revoked).  But because to check the signature on the CRL we need the
     * public key of the issuing CA certificate (which was already processed
     * one round before), we've a little problem. But we can both solve it and
     * at the same time optimize the processing by using the following
     * verification scheme (idea and code snippets borrowed from the GLOBUS
     * project):
     *
     * 1. We'll check the signature of a CRL in each step when we find a CRL
     *    through the _subject_ name of the current certificate. This CRL
     *    itself will be needed the first time in the next round, of course.
     *    But we do the signature processing one round before this where the
     *    public key of the CA is available.
     *
     * 2. We'll check the revocation list of a CRL in each step when
     *    we find a CRL through the _issuer_ name of the current certificate.
     *    This CRLs signature was then already verified one round before.
     *
     * This verification scheme allows a CA to revoke its own certificate as
     * well, of course.
     */

    /*
     * Try to retrieve a CRL corresponding to the _subject_ of
     * the current certificate in order to verify it's integrity.
     */
    memset((char *)&obj, 0, sizeof(obj));
    X509_STORE_CTX_init(&store_ctx, crl_store, NULL, NULL);
    rc = X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, subject, &obj);
    X509_STORE_CTX_cleanup(&store_ctx);
    crl = obj.data.crl;
    if (rc > 0 && crl != NULL) {
        /*
         * Verify the signature on this CRL
         */
        if (X509_CRL_verify(crl, X509_get_pubkey(xs)) <= 0) {
            fprintf(stderr, "Invalid signature on CRL!\r\n");
            X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE);
            X509_OBJECT_free_contents(&obj);
            return 0;
        }

        /*
         * Check date of CRL to make sure it's not expired
         */
        i = X509_cmp_current_time(X509_CRL_get_nextUpdate(crl));
        if (i == 0) {
            fprintf(stderr, "Found CRL has invalid nextUpdate field.\r\n");
            X509_STORE_CTX_set_error(ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
            X509_OBJECT_free_contents(&obj);
            return 0;
        }
        if (i < 0) {
            fprintf(stderr, "Found CRL is expired - revoking all certificates until you get updated CRL.\r\n");
            X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_HAS_EXPIRED);
            X509_OBJECT_free_contents(&obj);
            return 0;
        }
        X509_OBJECT_free_contents(&obj);
    }

    /*
     * Try to retrieve a CRL corresponding to the _issuer_ of
     * the current certificate in order to check for revocation.
     */
    memset((char *)&obj, 0, sizeof(obj));
    X509_STORE_CTX_init(&store_ctx, crl_store, NULL, NULL);
    rc = X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, issuer, &obj);
    X509_STORE_CTX_cleanup(&store_ctx);
    crl = obj.data.crl;
    if (rc > 0 && crl != NULL) {
        /*
         * Check if the current certificate is revoked by this CRL
         */
        n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl));
        for (i = 0; i < n; i++) {
            revoked = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i);
            if (ASN1_INTEGER_cmp(revoked->serialNumber, X509_get_serialNumber(xs)) == 0) {
                serial = ASN1_INTEGER_get(revoked->serialNumber);
                cp = x509_name_oneline(issuer, NULL, 0);
                fprintf(stderr,
		    "Certificate with serial %ld (0x%lX) revoked per CRL from issuer %s\r\n",
                        serial, serial, cp ? cp : "(ERROR)");
                if (cp) free(cp);

                X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED);
                X509_OBJECT_free_contents(&obj);
                return 0;
            }
        }
        X509_OBJECT_free_contents(&obj);
    }
    return ok;
}

static void print_x509_v_error(int error)
{
    switch (error) {
	case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
	    fprintf(ttyout, "WARNING: Server's certificate is self signed.\r\n");
	    break;
	case X509_V_ERR_CERT_HAS_EXPIRED:
	    fprintf(ttyout, "WARNING: Server's certificate has expired.\r\n");
	    break;
	case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
	    fprintf(ttyout,
	        "WARNING: Server's certificate issuer's certificate isn't available locally.\r\n");
	    break;
	case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
	    fprintf(ttyout, "WARNING: Unable to verify leaf signature.\r\n");
	    break;
	case X509_V_ERR_CERT_REVOKED:
	    fprintf(ttyout, "WARNING: Certificate revoked.\r\n");
	    break;
	case X509_V_ERR_INVALID_CA:
	    fprintf(ttyout, "WARNING: Invalid CA.\r\n");
	    break;
	case X509_V_ERR_CRL_HAS_EXPIRED:
	    fprintf(ttyout, "WARNING: CRL has expired.\r\n");
	    break;
	case X509_V_ERR_CERT_UNTRUSTED:
	    fprintf(ttyout, "WARNING: Certificate is untrusted.\r\n");
	    break;
	default:
	    fprintf(ttyout,
	        "WARNING: Error %d while verifying server's certificate.\r\n", error);
	    break;
    }
}

static int verify_callback(int ok, X509_STORE_CTX *ctx)
{
    int prev_error = 0;
    /*fprintf(stderr, "depth = %d, error = %d, ok = %d\n", ctx->error_depth, ctx->error, ok);*/
    /* TODO: Make up my mind on what errors to accept or not. */
#ifdef PR_OBSD_FTP
    /* we can configure the client to skip the peer's cert verification */
    if (tls_no_verify)
    	return 1;
#endif /* PR_OBSD_FTP */
    if (!ok) {
    	verify_error_flag = 1;
    	print_x509_v_error(prev_error = ctx->error);
    }
    /* since the CRL check isn't included in the OpenSSL automatic certificate
     * check, we must call verify_crl() after we first check what errors the
     * automatic check might have found, otherwise they might be lost.
     */
    ok = verify_crl(ok, ctx);
    if (!ok) {
    	verify_error_flag = 1;
	if (ctx->error != prev_error)
	    print_x509_v_error(ctx->error);
    }
    ok = 1;
    return ok;
}

static int seed_PRNG(void)
{
    char stackdata[1024];
    static char rand_file[300];
    FILE *fh;
    
#if OPENSSL_VERSION_NUMBER >= 0x00905100
    if (RAND_status())
	return 0;     /* PRNG already good seeded */
#endif
    /* if the device '/dev/urandom' is present, OpenSSL uses it by default.
     * check if it's present, else we have to make random data ourselfs.
     */
    if ((fh = fopen("/dev/urandom", "r"))) {
	fclose(fh);
	return 0;
    }
    if (RAND_file_name(rand_file, sizeof(rand_file)))
	tls_rand_file = rand_file;
    else
	return 1;
    if (!RAND_load_file(rand_file, 1024)) {
	/* no .rnd file found, create new seed */
	unsigned int c;
	c = time(NULL);
	RAND_seed(&c, sizeof(c));
	c = getpid();
	RAND_seed(&c, sizeof(c));
	RAND_seed(stackdata, sizeof(stackdata));
    }
#if OPENSSL_VERSION_NUMBER >= 0x00905100
    if (!RAND_status())
	return 2;   /* PRNG still badly seeded */
#endif
    return 0;
}

#ifdef TLS_SESSION_FILE_CACHE
static char *sfc_filename = NULL;

static char *peer_ip_as_string(int sock)
{
#ifdef INET6
    struct sockaddr_storage saddr;
#else
    struct sockaddr_in saddr;
#endif /* !INET6 */
    int saddr_len = sizeof(saddr);
    static char rv[50];

    if (getpeername(sock, (struct sockaddr *) &saddr, &saddr_len) != 0)
	return NULL;
    switch (((struct sockaddr *)&saddr)->sa_family) {
	case AF_INET:
	    snprintf(rv, sizeof(rv), "%08X",
		     htonl(((struct sockaddr_in *)&saddr)->sin_addr.s_addr));
	    break;
#ifdef INET6
	case AF_INET6:
	    snprintf(rv, sizeof(rv),
		"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[0],
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[1],
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[2],
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[3],
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[4],
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[5],
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[6],
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[7],
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[8],
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[9],
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[10],
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[11],
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[12],
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[13],
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[14],
		     ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[15]);
	    break;
#endif /* INET6 */
	default:
	    return NULL;
    }
    return rv;
}

static char *make_sfc_filename(int sock)
{
    DIR *dir;
    char path[MAXPATHLEN], *home, *peer;
    static char filename[MAXPATHLEN];

    home = getenv("HOME");
    if (home == NULL)
	return NULL;
    snprintf(path, sizeof(path), "%s/.tls_sfc", home);
    dir = opendir(path);
    /* if the ~/.tls_sfc dir doesn't exist, we consider this function disabled */
    if (dir == NULL)
	return NULL;
    closedir(dir);
    /* the file name is based on the hexadecimal representation of the peer's
     * ip address, with `oss' (from `OpenSsl Session') as a prefix.
     * example: connected to 127.0.0.1 -> `oss7F000001'
     */
    if ((peer = peer_ip_as_string(sock)) == NULL)
	return NULL;
    snprintf(filename, sizeof(filename), "%s/oss%s", path, peer);
    return filename;
}

static int session_timed_out(SSL_SESSION *s)
{
    if (SSL_SESSION_get_time(s) + SSL_SESSION_get_timeout(s) < time(NULL))
	return 1;
    else
	return 0;
}

int tls_sfc_client_load(SSL *s)
{
    FILE *file;
    
    if (s == NULL)
	return 1;
    sfc_filename = make_sfc_filename(net);
    if (sfc_filename == NULL)
	return 2;
    file = fopen(sfc_filename, "r");
    if (file) {
	SSL_SESSION *sess;
	fchmod(fileno(file), S_IRUSR | S_IWUSR);
	sess = PEM_read_SSL_SESSION(file, NULL, NULL, NULL);
	if (sess) {
	    /* ``refresh'' the session timeout */
/*XXX		    SSL_SESSION_set_time(sess, time(NULL)); */
	    if (session_timed_out(sess))
		unlink(sfc_filename);
	    else
		SSL_set_session(s, sess);
	    /* dec the ref counter in sess so it will eventually be freed */
	    SSL_SESSION_free(sess);
	}
	fclose(file);
    }
    return 0;
}

int tls_sfc_client_save(SSL *s)
{
    FILE *file;
    
    if (s == NULL)
	return 1;
    if (sfc_filename == NULL)
	return 2;
    file = fopen(sfc_filename, "w");
    if (file) {
	SSL_SESSION *sess;
	sess = SSL_get_session(s);
	if (sess)
	    PEM_write_SSL_SESSION(file, sess);
	fclose(file);
    }
    return 0;
}
#endif /* TLS_SESSION_FILE_CACHE */

int tls_init(void)
{
    int err;

#ifndef PR_OBSD_FTP
    ttyout = stderr;
#endif /* !PR_OBSD_FTP */
#ifdef DEBUG_OPENSSL_MEM
    CRYPTO_malloc_debug_init();
    CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
#endif /* DEBUG_OPENSSL_MEM */
    SSL_load_error_strings();
    SSL_library_init();
#ifdef ZLIB
    {
	COMP_METHOD *cm = COMP_zlib();
	if (!tls_nozlib && cm != NULL && cm->type != NID_undef) {
	    SSL_COMP_add_compression_method(0xe0, cm); /* Eric Young's ZLIB ID */
	}
    }
#endif /* ZLIB */
    ssl_ctx = SSL_CTX_new(tls_method);
    if (!ssl_ctx) {
	fprintf(stderr, "SSL_CTX_new() %s\r\n",
		(char *)ERR_error_string(ERR_get_error(), NULL));
	return 1;
    }
    SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
    SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, verify_callback);

    /* set up the CApath if defined */
    if (tls_capath_dir || tls_cafile_file) {
        printf("Setting verify path \n");
        if (!SSL_CTX_load_verify_locations(ssl_ctx, tls_cafile_file, tls_capath_dir)) {
	    fprintf(stderr,"WARNING: can't set CApath/CAfile verify locations\n");
	}
    }
    else
	SSL_CTX_set_default_verify_paths(ssl_ctx);

#ifdef PR_OBSD_FTP
    /* set up session caching  */
    SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_CLIENT);
    SSL_CTX_set_session_id_context(ssl_ctx, (const unsigned char *) "1", 1);
#endif /* PR_OBSD_FTP */
    
    /* set up the CRL */
    if ((tls_crl_file || tls_crl_dir) && (crl_store = X509_STORE_new()))
	X509_STORE_load_locations(crl_store, tls_crl_file, tls_crl_dir);
    
#ifdef PR_TELNET
    ssl = SSL_new(ssl_ctx);
    if (!ssl) {
	fprintf(stderr, "SSL_new() %s\r\n",
		(char *)ERR_error_string(ERR_get_error(), NULL));
	return 5;
    }
    SSL_set_cipher_list(ssl, tls_cipher_list);
    SSL_set_fd(ssl, net);
#endif /* PR_TELNET */
#ifdef TLS_SESSION_FILE_CACHE
    /* I would love to hear the story on why I must use the
     * SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option to get this working...
     */
    SSL_set_options(ssl, SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG);
    tls_sfc_client_load(ssl);
#endif /* TLS_SESSION_FILE_CACHE */
    
    if (seed_PRNG())
	fprintf(stderr, "Wasn't able to properly seed the PRNG!\r\n");
    return 0;
}

static int show_hostname_warning(char *s1, char *s2)
{
    char inp;
    
    fprintf(stderr,
	"WARNING: Hostname (\"%s\") and server's certificate (\"%s\") don't match, continue? (Y/N) ",
	s1, s2);
    inp = read_char();
    if (!( inp == 'y' || inp == 'Y' ))
	return 1;
    else
	return 0;
}

static int star_stricmp(const char *str, const char *star_str)
/* wildcard compares string `str' with a pattern string `star_str' which may
 * contain ONE wildcard star, '*', case-insensitive. there's probably better
 * ways to do this...
 */
{
    char *str_copy = strdup(str);
    char *star_str_copy = strdup(star_str);
    char *star;
    int rv = -1;

    if (str_copy == NULL || star_str_copy == NULL)
	goto eject;
    star = strchr(star_str_copy, '*');
    if (star) {
	int str_len = strlen(str_copy);
	int star_str_len = strlen(star_str_copy);
	int star_idx = star - star_str_copy;
	
	/* first check a few special cases */
	if (star_str_len > str_len + 1)
	    /* `star_str' is too long to ever match */
	    goto eject;
	else if (star_str_len == str_len + 1) {
	    if (*star_str_copy == '*')
		/* possible "*foo" == "foo" case */
		memmove(star_str_copy, star_str_copy + 1, star_str_len);
	    else if (star_str_copy[star_str_len - 1] == '*')
		/* possible "foo*" == "foo" case */
		star_str_copy[star_str_len - 1] = '\0';
	    else {
		/* possible "f*oo" == "foo" case */
		memmove(star_str_copy + star_idx, star_str_copy + star_idx + 1,
			star_str_len - star_idx);
	    }
	} else {
	    int diff = str_len - star_str_len;
	    /* remove the chars from `str_copy' that the star ``wildcards'' */
	    memmove(str_copy + star_idx, str_copy + star_idx + diff + 1,
		    str_len - star_idx - diff);
	    /* remove the '*' from `star_str_copy' */
	    memmove(star_str_copy + star_idx, star_str_copy + star_idx + 1,
		    star_str_len - star_idx);
	}
    }
    rv = strcasecmp(str_copy, star_str_copy);
eject:
    if (str_copy)	free(str_copy);
    if (star_str_copy)	free(star_str_copy);
    return rv;
}

static int dNSName_cmp(const char *host, const char *dNSName)
{
    int c1 = 0, c2 = 0, num_comp, rv = -1;
    char *p, *p1, *p2, *host_copy, *dNSName_copy;

    /* first we count the number of domain name components in both parameters.
     * they should be equal many, or it's not a match
     */
    p = (char *) host;
    while ((p = strchr(p, '.'))) {
	c1++;
	p++;
    }
    p = (char *) dNSName;
    while ((p = strchr(p, '.'))) {
	c2++;
	p++;
    }
    if (c1 != c2)
	return -1;
    num_comp = c1 + 1;

    host_copy = strdup(host);
    dNSName_copy = strdup(dNSName);
    if (host_copy == NULL || dNSName_copy == NULL)
	goto eject;
    /* make substrings by replacing '.' with '\0' */
    p = dNSName_copy;
    while ((p = strchr(p, '.'))) {
	*p = '\0';
	p++;
    }
    p = host_copy;
    while ((p = strchr(p, '.'))) {
	*p = '\0';
	p++;
    }

    /* compare each component */
    p1 = host_copy;
    p2 = dNSName_copy;
    for (; num_comp; num_comp--) {
	if (star_stricmp(p1, p2))
	    /* failed match */
	    goto eject;
	p1 += strlen(p1) + 1;
	p2 += strlen(p2) + 1;
    }
    /* match ok */
    rv = 0;

eject:
    if (dNSName_copy)	free(dNSName_copy);
    if (host_copy)	free(host_copy);
    return rv;
}

#ifndef INADDR_NONE
#define INADDR_NONE -1
#endif /* !INADDR_NONE */

static int check_server_name(SSL *s)
/* returns 0 if hostname and server's cert matches, else 1 */
{
    char **dNSName, *commonName;
    unsigned char **ipAddress;
    struct in_addr ia;

    /* first we check if `tls_hostname' is in fact an ip address */
    if ((ia.s_addr = inet_addr(tls_hostname)) != INADDR_NONE) {
	ipAddress = (unsigned char **) tls_get_SAN_objs(s, GEN_IPADD);
	if (ipAddress) {
	    int i = 0, rv;
	    char *server_ip = "UNKNOWN";
	    
	    for (i = 0; ipAddress[i]; i++)
		if (*(unsigned long *)ipAddress[i] == ia.s_addr)
		    return 0;
	    
	    if (ipAddress[i - 1]) {
		ia.s_addr = *(unsigned long *)ipAddress[i - 1];
		server_ip = inet_ntoa(ia);
	    }
	    rv = show_hostname_warning(tls_hostname, server_ip);
	    for (i = 0; ipAddress[i]; i++)
		free(ipAddress[i]);
	    return rv;
	} else
	    return show_hostname_warning(tls_hostname, "NO IP IN CERT");
    }
    
    /* look for dNSName(s) in subjectAltName in the server's certificate */
    dNSName = tls_get_SAN_objs(s, GEN_DNS);
    if (dNSName) {
	int i = 0, rv;
	for (i = 0; dNSName[i]; i++) {
	    if (!dNSName_cmp(tls_hostname, dNSName[i]))
		return 0;
	}
	rv = show_hostname_warning(tls_hostname, dNSName[i - 1] ? dNSName[i - 1] : "UNKNOWN");
	for (i = 0; dNSName[i]; i++)
	    free(dNSName[i]);
	return rv;
    } else if ((commonName = tls_get_commonName(s))) {
	/* so the server didn't have any dNSName's, check the commonName */
	if (!dNSName_cmp(tls_hostname, commonName))
	    return 0;
	else
	    return show_hostname_warning(tls_hostname, commonName);
    } else
	return 1;
}

static char *file_fullpath(char *fn)
{
    static char fp[256];
    FILE *file;
    char *dir;
    
    /* check if it is a full path already */
    if (strchr(fn, '/'))
    	return fn;
    /* check if it is in current dir */
    if ((file = fopen(fn, "r"))) {
    	fclose(file);
	return fn;
    }
    if (!(dir = getenv(X509_get_default_cert_dir_env())))	/* $SSL_CERT_DIR */
    	dir = (char *)X509_get_default_cert_dir();
    snprintf(fp, sizeof(fp), "%s/%s", dir, fn);
    if ((file = fopen(fp, "r"))) {
    	fclose(file);
	return fp;
    }
    dir = (char *)X509_get_default_private_dir();
    snprintf(fp, sizeof(fp), "%s/%s", dir, fn);
    if ((file = fopen(fp, "r"))) {
    	fclose(file);
	return fp;
    }
    return fn;	/* here fn is proven wrong, but we return it anyway */
}

#ifdef PR_TELNET
void tls_shutdown(void)
{
    if (tls_active) {
    	SSL_shutdown(ssl);
    	tls_active = 0;
    }
}

int tls_connect(void)
{
    int err;
    char *subject, *issuer, *subjectAltName, inp;

    /* let's see if we are going to use any client certificate */
    x509rc_read_filenames();
    if (tls_cert_file) {
	int mode;
    	char *key_file = tls_key_file;
	if (!key_file)
	    key_file = tls_cert_file;
    	err = SSL_use_certificate_file(ssl, file_fullpath(tls_cert_file),
				       SSL_FILETYPE_PEM);
    	if (err <= 0) {
            fprintf(stderr, "SSL_use_certificate_file(\"%s\") %s\r\n",
		    file_fullpath(tls_cert_file),
		    (char *)ERR_error_string(ERR_get_error(), NULL));
            return 1;
    	}
	/* we must add MODE_EDIT for any password reading routine, or CR won't
	 * terminate the line */
	mode = getconnmode();
	TerminalNewMode(mode | MODE_EDIT);
    	err = SSL_use_PrivateKey_file(ssl, file_fullpath(key_file),
				      SSL_FILETYPE_PEM);
	TerminalNewMode(mode);
    	if (err <= 0) {
            fprintf(stderr, "SSL_use_PrivateKey_file(\"%s\") %s\r\n",
		    file_fullpath(key_file),
		    (char *)ERR_error_string(ERR_get_error(), NULL));
            return 2;
    	}
    	if (!SSL_check_private_key(ssl)) {
    	    fprintf(stderr, "Private key don't match the certificate public key!\r\n");
	    return 3;
    	}
    }

    /* it seems SSL_connect() don't like non-blocking sockets, or...? */
    NetNonblockingIO(net, 0);
    fprintf(stderr, "[Negotiating SSL/TLS session... ]\r\n");
    err = SSL_connect(ssl);
    NetNonblockingIO(net, 1);
    if (err == 1) {
    	if (verify_error_flag) {
	    fprintf(stderr,
		"WARNING: Errors while verifying the server's certificate chain, continue? (Y/N) ");
	    inp = read_char();
	    if (!( inp == 'y' || inp == 'Y' ))
	    	quit();
	}
	tls_active = 1;
	if (subject = tls_get_subject_name(ssl))
	    fprintf(stderr, "[Subject: %s]\r\n", subject);
	else {
	    fprintf(stderr,
	        "WARNING: Server didn't provide a certificate, continue? (Y/N) ");
	    inp = read_char();
	    if (!( inp == 'y' || inp == 'Y' ))
	    	quit();
	}
	if ((subjectAltName = x509v3_subjectAltName_oneline(ssl, NULL, 0))) {
	    fprintf(stderr, "[X509v3 Subject Alternative Name: %s]\r\n", subjectAltName);
	    free(subjectAltName);
	}
	if (issuer = tls_get_issuer_name(ssl))
	    fprintf(stderr, "[Issuer: %s]\r\n", issuer);
	fprintf(stderr, "[Cipher: %s (%d bits)]\r\n", SSL_get_cipher(ssl),
		SSL_get_cipher_bits(ssl, NULL));
	if (ssl->expand && ssl->expand->meth)
	    fprintf(stderr, "Compression: %s\r\n", ssl->expand->meth->name);
	if (check_server_name(ssl)) {
	    /* the host name on the command line didn't match with the server's
	     * cert, and the user didn't ansver `Y' to the question.
	     */
	    quit();
	}
	return 0;
    }
    else {   /* TLS connection failed */
	fprintf(stderr, "SSL_connect() = %d, %s\r\n", err,
		(char *)ERR_error_string(ERR_get_error(), NULL));
	tls_shutdown();
	tls_cleanup();
	return 5;
    }
    return 6;
}

int tls_pending(void)
{
    if (tls_active)
        return SSL_pending(ssl);
    else 
        return 0;
}
#endif /* PR_TELNET */

#ifdef PR_OBSD_FTP
int tls_connect_ctrl(int s)
{
    int err;
    char *subject, *issuer, *commonName, *dNSName, *subjectAltName, inp;

    if (ctrl_conn.ssl) {
	fprintf(ttyout, "Already TLS connected!\r\n");
	return 1;
    }
    ctrl_conn.ssl = SSL_new(ssl_ctx);
    if (!ctrl_conn.ssl) {
	fprintf(ttyout, "SSL_new() %s\r\n",
		(char *)ERR_error_string(ERR_get_error(), NULL));
	return 2;
    }
    SSL_set_cipher_list(ctrl_conn.ssl, tls_cipher_list);
    SSL_set_fd(ctrl_conn.ssl, s);
    ctrl_conn.sock = s;

    /* let's see if we are going to use any client certificate */
    x509rc_read_filenames();
    if (tls_cert_file) {
    	char *key_file = tls_key_file;
	if (!key_file)
	    key_file = tls_cert_file;
    	err = SSL_use_certificate_file(ctrl_conn.ssl, file_fullpath(tls_cert_file),
				       SSL_FILETYPE_PEM);
    	if (err <= 0) {
            fprintf(stderr, "SSL_use_certificate_file(\"%s\") %s\r\n",
		    file_fullpath(tls_cert_file),
		    (char *)ERR_error_string(ERR_get_error(), NULL));
            return 3;
    	}
    	err = SSL_use_PrivateKey_file(ctrl_conn.ssl, file_fullpath(key_file),
				      SSL_FILETYPE_PEM);
    	if (err <= 0) {
            fprintf(stderr, "SSL_use_PrivateKey_file(\"%s\") %s\r\n",
		    file_fullpath(key_file),
		    (char *)ERR_error_string(ERR_get_error(), NULL));
            return 4;
    	}
    	if (!SSL_check_private_key(ctrl_conn.ssl)) {
    	    fprintf(stderr, "Private key don't match the certificate public key!\r\n");
	    return 5;
    	}
    }
    
    fprintf(ttyout, "[Starting SSL/TLS negotiation...]\r\n");
    err = SSL_connect(ctrl_conn.ssl);
    if (err == 1) {
    	if (verify_error_flag) {
	    fprintf(ttyout,
	        "WARNING: Errors while verifying the server's certificate chain, continue? (Y/N) ");
	    inp = read_char();
	    if (!( inp == 'y' || inp == 'Y' ))
	    	return 3;
	}
	if ((subject = tls_get_subject_name(ctrl_conn.ssl)))
	    fprintf(ttyout, "[Subject: %s]\r\n", subject);
	else {
	    fprintf(ttyout,
	        "WARNING: Server didn't provide a certificate, continue? (Y/N) ");
	    inp = read_char();
	    if (!( inp == 'y' || inp == 'Y' ))
	    	return 4;
	}
	if ((subjectAltName = x509v3_subjectAltName_oneline(ctrl_conn.ssl, NULL, 0))) {
	    fprintf(ttyout, "[X509v3 Subject Alternative Name: %s]\r\n", subjectAltName);
	    free(subjectAltName);
	}
	if ((issuer = tls_get_issuer_name(ctrl_conn.ssl)))
	    fprintf(ttyout, "[Issuer:  %s]\r\n", issuer);
	fprintf(ttyout, "[Cipher:  %s (%d bits)]\r\n", SSL_get_cipher(ctrl_conn.ssl),
		SSL_get_cipher_bits(ctrl_conn.ssl, NULL));
	if (ctrl_conn.ssl->expand && ctrl_conn.ssl->expand->meth)
	    fprintf(stderr, "Compression: %s\r\n", ctrl_conn.ssl->expand->meth->name);
	if (check_server_name(ctrl_conn.ssl)) {
	    /* the host name on the command line didn't match with the server's
	     * cert, and the user didn't ansver `Y' to the question.
	     */
	    return 5;
	}
	return 0;
    }
    else {   /* TLS connection failed */
	fprintf(ttyout, "SSL_connect() = %d, %s\r\n", err,
		(char *)ERR_error_string(ERR_get_error(), NULL));
    	SSL_shutdown(ctrl_conn.ssl);
	SSL_free(ctrl_conn.ssl);
	ctrl_conn.ssl = NULL;
	ctrl_conn.sock = -1;
	return 6;
    }
}

int tls_connect_data(int s)
{
    int err;

    if (data_conn.ssl) {
	fprintf(ttyout, "Already TLS connected!\r\n");
	return 1;
    }
    data_conn.ssl = SSL_new(ssl_ctx);
    if (!data_conn.ssl) {
	fprintf(ttyout, "SSL_new() %s\r\n",
		(char *)ERR_error_string(ERR_get_error(), NULL));
	return 2;
    }

    /*
     * if we have cleared the control connection back to 
     *  plaintext (with CCC) then we need to pop the 
     *  cloned copy instead of copying the, now missing,
     *  control connection
     * N.B. if both ctrl_conn.ssl and ctrl_ssl_clone are NULL
     *  then something is broken.
     */
    if((NULL == ctrl_conn.ssl) && (NULL != ctrl_ssl_clone))
       {
       SSL_copy_session_id(data_conn.ssl, ctrl_ssl_clone);
       }
    else
       {
       SSL_copy_session_id(data_conn.ssl, ctrl_conn.ssl);
       }
    SSL_set_fd(data_conn.ssl, s);
    data_conn.sock = s;
    
    err = SSL_connect(data_conn.ssl);
    if (err != 1)  {   /* TLS connection failed */
	fprintf(ttyout, "SSL_connect() = %d, %s\r\n", err,
		(char *)ERR_error_string(ERR_get_error(), NULL));
    	SSL_shutdown(data_conn.ssl);
	SSL_free(data_conn.ssl);
	data_conn.ssl = NULL;
	data_conn.sock = -1;
	return 3;
    } else
	return 0;
}

static void ssl_close(SSL *ssl)
{
    if (ssl) {
	if (ssl == data_conn.ssl) {
	    int fd = SSL_get_fd(ssl);
	    if (fd >= 0)
		tls_fputc_fflush(fd);
	    /*if(NULL != ctrl_ssl_clone)
               {
	       SSL_copy_session_id(ctrl_ssl_clone,data_conn.ssl);
               }*/
        }
    	SSL_shutdown(ssl);
	SSL_free(ssl);
	if (ssl == data_conn.ssl) {
	    data_conn.ssl = NULL;
	    data_conn.sock = -1;
	} else if (ssl == ctrl_conn.ssl) {
	    ctrl_conn.ssl = NULL;
	    ctrl_conn.sock = -1;
	}
    }
}

int tls_fclose(FILE *stream)
{
    SSL *ssl = SOCK_TO_SSL(fileno(stream));

    ssl_close(ssl);
    return fclose(stream);
}

int tls_close(int fd)
{
    SSL *ssl = SOCK_TO_SSL(fd);
    
    ssl_close(ssl);
    return close(fd);
}

int tls_shutdown(int s, int how)
{
    /* if s == -1, do a `global' SSL shutdown, else emulate a shutdown(2) */
    if (s == -1) {
	ssl_close(data_conn.ssl);
	ssl_close(ctrl_conn.ssl);
	return 0;
    } else {
	SSL *ssl = SOCK_TO_SSL(s);
	ssl_close(ssl);
	return shutdown(s, how);
    }
}

void tls_free_ssls(void)
{
    ssl_close(data_conn.ssl);
    ssl_close(ctrl_conn.ssl);
}
#endif /* PR_OBSD_FTP */

void tls_cleanup(void)
{
    if (crl_store) {
    	X509_STORE_free(crl_store);
	crl_store = NULL;
    }
#ifdef PR_TELNET
    if (ssl) {
#ifdef TLS_SESSION_FILE_CACHE
	tls_sfc_client_save(ssl);
#endif /* TLS_SESSION_FILE_CACHE */
	SSL_free(ssl);
	ssl = NULL;
    }
#endif /* PR_TELNET */
#ifdef PR_OBSD_FTP
    if (data_conn.ssl) {
	SSL_free(data_conn.ssl);
	data_conn.ssl = NULL;
	data_conn.sock = -1;
    }
    if (ctrl_conn.ssl) {
	SSL_free(ctrl_conn.ssl);
	ctrl_conn.ssl = NULL;
	ctrl_conn.sock = -1;
    }
#endif /* PR_OBSD_FTP */
    if (ssl_ctx) {
	SSL_CTX_free(ssl_ctx);
	ssl_ctx = NULL;
    }
    ERR_free_strings();
    ERR_remove_state(0);
    EVP_cleanup();	/* release the stuff allocated by SSL_library_init() */
    if (tls_cert_file) {
    	free(tls_cert_file);
	tls_cert_file = NULL;
    }
    if (tls_key_file) {
    	free(tls_key_file);
	tls_key_file = NULL;
    }
    if (tls_crl_file) {
    	free(tls_crl_file);
	tls_crl_file = NULL;
    }
    if (tls_crl_dir) {
    	free(tls_crl_dir);
	tls_crl_dir = NULL;
    }
    if (tls_capath_dir) {
    	free(tls_capath_dir);
	tls_capath_dir = NULL;
    }
    if (tls_cafile_file) {
    	free(tls_cafile_file);
	tls_cafile_file = NULL;
    }
    if (tls_hostname) {
    	free(tls_hostname);
	tls_hostname = NULL;
    }
    if (tls_cipher_list) {
    	free(tls_cipher_list);
	tls_cipher_list = NULL;
    }
    if (tls_rand_file)
	RAND_write_file(tls_rand_file);
#ifdef DEBUG_OPENSSL_MEM
    CRYPTO_mem_leaks_fp(stderr);
#endif /* DEBUG_OPENSSL_MEM */
}

static void handle_ssl_error(int error, char *where)
{
    switch (error) {
    	case SSL_ERROR_NONE:
	    return;
	case SSL_ERROR_SSL:
	    fprintf(ttyout, "unhandled SSL_ERROR_SSL in %s\r\n", where);
	    break;
	case SSL_ERROR_WANT_READ:
	    fprintf(ttyout, "unhandled SSL_ERROR_WANT_READ in %s\r\n", where);
	    break;
	case SSL_ERROR_WANT_WRITE:
	    fprintf(ttyout, "unhandled SSL_ERROR_WANT_WRITE in %s\r\n", where);
	    break;
	case SSL_ERROR_WANT_X509_LOOKUP:
	    fprintf(ttyout, "unhandled SSL_ERROR_WANT_X509_LOOKUP in %s\r\n", where);
	    break;
	case SSL_ERROR_SYSCALL:
	    fprintf(ttyout, "unhandled SSL_ERROR_SYSCALL in %s\r\n", where);
	    break;
	case SSL_ERROR_ZERO_RETURN:
	    fprintf(ttyout, "unhandled SSL_ERROR_ZERO_RETURN in %s\r\n", where);
	    break;
	case SSL_ERROR_WANT_CONNECT:
	    fprintf(ttyout, "unhandled SSL_ERROR_WANT_CONNECT in %s\r\n", where);
	    break;
	default:
	    fprintf(ttyout, "unhandled SSL_ERROR %d in %s\r\n", error, where);
	    break;
    }
#ifdef PR_OBSD_FTP
    tls_shutdown(-1, 0);
    tls_cleanup();
    exit(1);
#endif /* PR_OBSD_FTP */
}

static int select_read(int rfd)
/* timeout = 20 seconds */
{
    fd_set rfds;
    struct timeval tv;

    FD_ZERO(&rfds);
    FD_SET(rfd, &rfds);
    tv.tv_sec = 20;
    tv.tv_usec = 0;
    return select(rfd + 1, &rfds, NULL, NULL, &tv);
}

ssize_t tls_read(int fd, void *buf, size_t count)
{
    SSL *s = SOCK_TO_SSL(fd);
    
  retry:
    if (s) {
	ssize_t c = SSL_read(s, buf, count);
	if (c < 0) {
	    int err = SSL_get_error(s, c);
	    /* read(2) returns only the generic error number -1 */
	    c = -1;
	    switch (err) {
		case SSL_ERROR_WANT_READ:
		    /* OpenSSL needs more data from the wire to finish the current block,
		     * so we wait a little while for it. */
		    err = select_read(fd);
		    if (err > 0)
			goto retry;
		    else if (err == 0)
			/* still missing data after timeout, emulate an EINTR and return. */
			errno = EINTR;
		    /* if err < 0, i.e. some error from the select(), everything is already
		     * in place; errno is properly set and this function returns -1. */
		    break;
		case SSL_ERROR_SSL:
		case SSL_ERROR_SYSCALL:
		    /* the SSL connection is screwed up, emulate EOF */
		    c = 0;
		    break;
		default:
		    handle_ssl_error(err, "tls_read()");
		    break;
	    }
	}
	return c;
    } else
	return read(fd, buf, count);
}

int tls_fgetc(FILE *stream)
{
    SSL *s = SOCK_TO_SSL(fileno(stream));

    if (s) {
	unsigned char r;
	int err;
	do
	    err = tls_read(fileno(stream), &r, 1);
	while (err < 0 && errno == EINTR);
	if (err == 1)
	    return (int) r;
	else
	    return EOF;
    } else
	return fgetc(stream);
}

int tls_recv(int fd, void *buf, size_t len, int flags)
{
    SSL *s = SOCK_TO_SSL(fd);
    
    if (s)
	return (int) tls_read(fd, buf, len);
    else
	return recv(fd, buf, len, flags);
}

ssize_t tls_write(int fd, const void *buf, size_t count)
{
    SSL *s = SOCK_TO_SSL(fd);
    
    if (s) {
    	ssize_t c = SSL_write(s, buf, count);
	if (c < 0) {
	    int err = SSL_get_error(s, c);
	    /* write(2) returns only the generic error number -1 */
	    c = -1;
	    switch (err) {
	        case SSL_ERROR_WANT_WRITE:
	    	    /* simulate an EINTR in case OpenSSL wants to write more */
		    errno = EINTR;
		    break;
		case SSL_ERROR_SSL:
		case SSL_ERROR_SYSCALL:
		    /* the SSL connection is screwed up, emulate EPIPE */
		    errno = EPIPE;
		    break;
		default:
		    handle_ssl_error(err, "tls_write()");
		    break;
	    }
	}
	return c;
    }	
    else
	return write(fd, buf, count);
}

int tls_fputs(const char *str, FILE *stream)
{
    SSL *s = SOCK_TO_SSL(fileno(stream));

    if (s) {
	int sent = 0, size, w;
	size = strlen(str);
	do {
	    w = tls_write(fileno(stream), str + sent, size - sent);
	    if (w > 0)
		sent += w;
	    else if (!(w < 0 && errno == EINTR))
		break;	/* other error than EINTR or w == 0 */
        } while (sent != size);
	if (w < 0)
	    return EOF;
	else
	    return w;
    } else
	return fputs(str, stream);
}

void tls_fputc_fflush(int fd)
{
    if (fputc_buflen > 0) {
	tls_write(fd, fputc_buffer, fputc_buflen);
	fputc_buflen = 0;
    }
    return;
}

int tls_fputc(int c, FILE *stream)
{
    SSL *s = SOCK_TO_SSL(fileno(stream));

    if (s) {
	unsigned char uc = c;
	int err = 1;
	do {
            fputc_buffer[fputc_buflen++] = uc;
            if (fputc_buflen >= FPUTC_BUFFERSIZE) {
		err = tls_write(fileno(stream), fputc_buffer, fputc_buflen);
		if (err >= 0) {
		    err = 1;
		}
		fputc_buflen = 0;
	    }
	} while (err < 0 && errno == EINTR);
	if (err == 1)
	    return (int) uc;
	else
	    return EOF;
    } else
	return fputc(c, stream);
}

int tls_send(int fd, const void *msg, size_t len, int flags)
{
    SSL *s = SOCK_TO_SSL(fd);
    
    if (s)
	return (int) tls_write(fd, msg, len);
    else
	return send(fd, msg, len, flags);
}

#ifdef PR_OBSD_FTP
int tls_vfprintf(FILE *stream, const char *format, va_list ap)
{
#define SNP_MAXBUF 1024000
    SSL *ssl = SOCK_TO_SSL(fileno(stream));

    if (ssl) {
	/* here I boldly assume that snprintf() and vsnprintf() uses the same
	 * return value convention. if not, what kind of libc is this? ;-)
	 */
	char sbuf[1024] = { 0 }, *buf = sbuf, *lbuf = NULL;
	int sent = 0, size, ret, w;
	ret = vsnprintf(sbuf, sizeof(sbuf), format, ap);
#ifdef SNPRINTF_OK
	/* this one returns the number of bytes it wants to write in case of overflow */
	if (ret >= sizeof(sbuf) && ret < SNP_MAXBUF) {
	    /* sbuf was too small, use a larger lbuf */
	    lbuf = malloc(ret + 1);
	    if (lbuf) {
		vsnprintf(lbuf, ret + 1, format, ap);
		buf = lbuf;
	    }
	}
#else
# ifdef SNPRINTF_HALFBROKEN
	/* this one returns the number of bytes written (excl. \0) in case of overflow */
#  define SNP_OVERFLOW(x, y) ( x == y ? 1 : 0 )
#  define SNP_NOERROR(x)     ( x < 0 ? 0 : 1 )
# else
#  ifdef SNPRINTF_BROKEN
	/* this one returns -1 in case of overflow */
#   define SNP_OVERFLOW(x, y) ( x < 0 ? 1 : 0 )
#   define SNP_NOERROR(x)     ( 1 )  /* if -1 means overflow, what's the error indication? */
#  else
#   error No valid SNPRINTF_... macro defined!
#  endif /* !SNPRINTF_BROKEN */
# endif /* !SNPRINTF_HALFBROKEN */
	if (SNP_NOERROR(ret) && SNP_OVERFLOW(ret, sizeof(sbuf) - 1)) {
	    /* sbuf was too small, use a larger lbuf */
	    size = sizeof(sbuf);
	    do {
		if ((size *= 2) > SNP_MAXBUF)	/* try to double the size */
		    break;
		if (lbuf) free(lbuf);
		lbuf = malloc(size);
		if (lbuf) {
		    ret = vsnprintf(lbuf, size, format, ap);
		    buf = lbuf;
		} else
		    break;
	    } while (SNP_NOERROR(ret) && SNP_OVERFLOW(ret, size - 1));
	}
#endif /* !SNPRINTF_OK */
	size = strlen(buf);
	do {
	    w = tls_write(fileno(stream), buf + sent, size - sent);
	    if (w > 0)
		sent += w;
	    else if (!(w < 0 && errno == EINTR))
		break;	/* other error than EINTR or w == 0 */
        } while (sent != size);
	if (lbuf) free(lbuf);
	return sent;
    } else
	return vfprintf(stream, format, ap);
}

#ifdef __STDC__
int tls_fprintf(FILE *stream, const char *fmt, ...)
#else
int tls_fprintf(stream, fmt, va_alist)
    FILE *stream;
    char *fmt;
    va_dcl
#endif
{
    va_list ap;
#ifdef __STDC__
    va_start(ap, fmt);
#else
    va_start(ap);
#endif
    return tls_vfprintf(stream, fmt, ap);
}
#endif /* PR_OBSD_FTP */

int tls_fflush(FILE *stream)
{
    if (stream == NULL)
	return fflush(NULL);
    if (SOCK_TO_SSL(fileno(stream)))
	return 0;	/* don't do anything! */
    else
	return fflush(stream);
}

static char *glob_tilde(char *s)
/* very simple ~ expansion */
{
    char *h, *r;

    if (s == NULL)
	return NULL;
    if (*s != '~')
	return s;
    if (!(h = getenv("HOME")))
	return s;
    if (!(r = malloc(strlen(h) + strlen(s))))
	return s;
    sprintf(r, "%s%s", h, s + 1);
    free(s);
    return r;
}

int x509rc_read_filenames(void)
{
    char filename[MAXPATHLEN], s1[256], s2[MAXPATHLEN], s3[MAXPATHLEN],
	 line[sizeof(s1) + sizeof(s2) + sizeof(s3) + 50], format[50], *home = NULL,
	 *p, *host = NULL;
    int rv = 0;
    FILE *file;

    if (x509rc_override)	/* cert already specified on command line */
	return 0;
    if (!tls_hostname)
	return 1;
    host = strdup(tls_hostname);
    if (host == NULL)
	return 2;
    for (p = host; *p; p++)
	*p = tolower(*p);
    home = getenv("HOME");
    if (home == NULL)
	return 3;
    snprintf(filename, sizeof(filename), "%s/.x509rc", home);
    file = fopen(filename, "r");
    if (file == NULL)
	return 4;
    
    /* create the sscanf() format string */
    snprintf(format, sizeof(format), "%%%us %%%us %%%us", (unsigned int) sizeof(s1) - 1,
	     (unsigned int) sizeof(s2) - 1, (unsigned int) sizeof(s3) - 1);

    while (fgets(line, sizeof(line), file)) {
	int c;
	if ((p = strchr(line, '#')))	/* truncate at comment */
	    *p = 0;
	c = sscanf(line, format, &s1, &s2, &s3);
	if (c < 2)
	    continue;
	for (p = s1; *p; p++)
	    *p = tolower(*p);
	/* check for exact host name match */
	if (!strcmp(s1, host)) {
	    if (tls_cert_file)
		free(tls_cert_file);
	    tls_cert_file = glob_tilde(strdup(s2));
	    if (c > 2) {
		if (tls_key_file)
		    free(tls_key_file);
		tls_key_file = glob_tilde(strdup(s3));
	    }
	    goto cleanup;
	}
	/* check for a "prefix" match */
	if (*s1 == '.') {
	    int hlen = strlen(host);
	    int slen = strlen(s1);
	    if (hlen > slen + 1 && !strcmp(s1 + 1, host + hlen - slen + 1)) {
		if (tls_cert_file)
		    free(tls_cert_file);
		tls_cert_file = glob_tilde(strdup(s2));
		if (c > 2) {
		    if (tls_key_file)
			free(tls_key_file);
		    tls_key_file = glob_tilde(strdup(s3));
		}
	    }
	}
    }

  cleanup:
    fclose(file);
    if (host) free(host);
    return rv;
}

void tls_ccc( void )
   {
   int rc;
   int shutcount = 0;

   /*
    * we can only do one CCC, if we do do it, then we need to push the
    * current state of the control connection to allow us to clone
    * it later for data connections
    */
   ctrl_ssl_clone = SSL_new(ssl_ctx);
   SSL_copy_session_id(ctrl_ssl_clone, ctrl_conn.ssl);

   rc = SSL_shutdown(ctrl_conn.ssl);
   /*
    * the rc of 0 from SSL_shutdown means - "call it again"
    *  (this from the doc on www.openssl.org and not the 
    *  man pages :-( )
    * Just put a count around it to make sure we don't hang
    */
   while((0 == rc) && (shutcount < 5))
      {
      rc = SSL_shutdown(ctrl_conn.ssl);
      shutcount++;
      }
   if (rc != 1)
      {
      fprintf(stderr,
	      "SSL shutdown failed after CCC.  Session may be unusable\n");
      }

   /*
    * These just ensure that we don't think that we have SSL on the 
    *  control connection any more
    */
   SSL_free(ctrl_conn.ssl);
   ctrl_conn.ssl = NULL;
   ctrl_conn.sock = -1;

   return;
   }


syntax highlighted by Code2HTML, v. 0.9.1