/*
 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
 * Copyright (c) 1999 University of Maryland
 * All Rights Reserved.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of U.M. not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  U.M. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Authors: the Amanda Development Team.  Its members are listed in a
 * file named AUTHORS, in the root directory of this distribution.
 */

/*
 * $Id$
 *
 * ssl-security.c - security and transport over ssl or a ssl-like command.
 *
 * XXX still need to check for initial keyword on connect so we can skip
 * over shell garbage and other stuff that ssl might want to spew out.
 */

#include "amanda.h"
#include "amutil.h"
#include "event.h"
#include "packet.h"
#include "security.h"
#include "security-util.h"
#include "sockaddr-util.h"
#include "stream.h"
#include "version.h"
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

/*
 * Number of seconds ssl has to start up
 */
#define	CONNECT_TIMEOUT	20

/*
 * Interface functions
 */
static void ssl_accept(const struct security_driver *,
    char *(*)(char *, void *),
    int, int,
    void (*)(security_handle_t *, pkt_t *),
    void *);
static void ssl_connect(const char *,
    char *(*)(char *, void *),
    void (*)(void *, security_handle_t *, security_status_t), void *, void *);
static ssize_t ssl_data_write(void *c, struct iovec *iov, int iovcnt);
static ssize_t ssl_data_write_non_blocking(void *c, struct iovec *iov, int iovcnt);
static ssize_t ssl_data_read(void *c, void *bug, size_t size, int timeout);
static void init_ssl(void);

/*
 * This is our interface to the outside world.
 */
const security_driver_t ssl_security_driver = {
    "SSL",
    ssl_connect,
    ssl_accept,
    sec_get_authenticated_peer_name_hostname,
    sec_close,
    stream_sendpkt,
    stream_recvpkt,
    stream_recvpkt_cancel,
    tcpma_stream_server,
    tcpma_stream_accept,
    tcpma_stream_client,
    tcpma_stream_close,
    tcpma_stream_close_async,
    sec_stream_auth,
    sec_stream_id,
    tcpm_stream_write,
    tcpm_stream_write_async,
    tcpm_stream_read,
    tcpm_stream_read_sync,
    tcpm_stream_read_to_shm_ring,
    tcpm_stream_read_cancel,
    tcpm_stream_pause,
    tcpm_stream_resume,
    tcpm_close_connection,
    NULL,
    NULL,
    ssl_data_write,
    ssl_data_write_non_blocking,
    ssl_data_read
};

static int newhandle = 1;

/*
 * Local functions
 */
static int runssl(struct sec_handle *, in_port_t port,
		  char *src_ip,
                  char *ssl_fingerprint_file, char *ssl_cert_file,
                  char *ssl_key_file, char *ssl_ca_cert_file,
                  char *ssl_cipher_list, int ssl_check_certificate_host);


/*
 * ssl version of a security handle allocator.  Logically sets
 * up a network "connection".
 */
static void
ssl_connect(
    const char *hostname,
    char *	(*conf_fn)(char *, void *),
    void	(*fn)(void *, security_handle_t *, security_status_t),
    void *	arg,
    void *	datap)
{
    struct sec_handle *rh;
    int result;
    char *canonname;
    char *service;
    in_port_t port;
    char *src_ip = NULL;
    char *ssl_dir = NULL;
    char *ssl_fingerprint_file = NULL;
    char *ssl_cert_file = NULL;
    char *ssl_key_file = NULL;
    char *ssl_ca_cert_file = NULL;
    char *ssl_cipher_list = NULL;
    int   ssl_check_certificate_host = 1;

    assert(fn != NULL);
    assert(hostname != NULL);

    auth_debug(1, _("ssl: ssl_connect: %s\n"), hostname);

    rh = g_new0(struct sec_handle, 1);
    security_handleinit(&rh->sech, &ssl_security_driver);
    rh->dle_hostname = g_strdup(hostname);
    rh->hostname = NULL;
    rh->rs = NULL;
    rh->ev_timeout = NULL;
    rh->rc = NULL;

    result = resolve_hostname(hostname, 0, NULL, &canonname);
    if(result != 0) {
	g_debug(_("resolve_hostname(%s): %s"), hostname, gai_strerror(result));
	security_seterror(&rh->sech, _("resolve_hostname(%s): %s\n"), hostname,
			  gai_strerror(result));
	(*fn)(arg, &rh->sech, S_ERROR);
	return;
    }
    if (canonname == NULL) {
	g_debug(_("resolve_hostname(%s) did not return a canonical name"), hostname);
	security_seterror(&rh->sech,
	        _("resolve_hostname(%s) did not return a canonical name\n"), hostname);
	(*fn)(arg, &rh->sech, S_ERROR);
       return;
    }

    rh->hostname = canonname;	/* will be replaced */
    canonname = NULL; /* steal reference */
    rh->rs = tcpma_stream_client(rh, newhandle++);
    rh->rc->recv_security_ok = &bsd_recv_security_ok;
    rh->rc->prefix_packet = &bsd_prefix_packet;
    rh->rc->need_priv_port = 0;

    if (rh->rs == NULL)
	goto error;

    amfree(rh->hostname);
    rh->hostname = g_strdup(rh->rs->rc->hostname);

    ssl_dir = getconf_str(CNF_SSL_DIR);
    if (conf_fn) {
	service = conf_fn("client_port", datap);
	if (!service || strlen(service) <= 1)
	    service = AMANDA_SERVICE_NAME;
	g_debug("Connecting to service '%s'", service);
	src_ip = conf_fn("src_ip", datap);
	ssl_fingerprint_file = g_strdup(conf_fn("ssl_fingerprint_file", datap));
	ssl_cert_file        = g_strdup(conf_fn("ssl_cert_file", datap));
	ssl_key_file         = g_strdup(conf_fn("ssl_key_file", datap));
	ssl_ca_cert_file     = g_strdup(conf_fn("ssl_ca_cert_file", datap));
	ssl_cipher_list      = conf_fn("ssl_cipher_list", datap);
	ssl_check_certificate_host =
			    atoi(conf_fn("ssl_check_certificate_host", datap));
    } else {
	service = AMANDA_SERVICE_NAME;
    }

    if (ssl_dir) {
	if (!ssl_cert_file || ssl_cert_file == '\0') {
	    ssl_cert_file = g_strdup_printf("%s/me/crt.pem", ssl_dir);
	}
	if (!ssl_key_file || ssl_key_file == '\0') {
	    ssl_key_file = g_strdup_printf("%s/me/private/key.pem", ssl_dir);
	}
	if (!ssl_ca_cert_file || ssl_ca_cert_file == '\0') {
	    ssl_ca_cert_file = g_strdup_printf("%s/CA/crt.pem", ssl_dir);
	}
	if (!ssl_fingerprint_file || ssl_fingerprint_file == '\0') {
	    struct stat  statbuf;
	    ssl_fingerprint_file = g_strdup_printf("%s/remote/%s/fingerprint", ssl_dir, rh->hostname);
	    if (stat(ssl_fingerprint_file, &statbuf) == -1) {
		g_free(ssl_fingerprint_file);
		ssl_fingerprint_file = NULL;
	    }
	}
    }

    port = find_port_for_service(service, "tcp");
    if (port == 0) {
	security_seterror(&rh->sech, _("%s/tcp unknown protocol"), service);
	goto error;
    }

    /*
     * We need to open a new connection.
     */
    if(rh->rc->read == -1) {
	if (runssl(rh, port, src_ip, ssl_fingerprint_file,
                   ssl_cert_file, ssl_key_file,
		   ssl_ca_cert_file, ssl_cipher_list,
		   ssl_check_certificate_host) < 0)
	    goto error;
	rh->rc->refcnt++;
    }

    g_free(ssl_fingerprint_file);
    g_free(ssl_cert_file);
    g_free(ssl_key_file);
    g_free(ssl_ca_cert_file);
    /*
     * The socket will be opened async so hosts that are down won't
     * block everything.  We need to register a write event
     * so we will know when the socket comes alive.
     *
     * Overload rh->rs->ev_read to provide a write event handle.
     * We also register a timeout.
     */
    g_mutex_lock(security_mutex);
    rh->fn.connect = fn;
    rh->arg = arg;
    rh->rs->rc->ev_write = event_create((event_id_t)(rh->rs->rc->write),
	EV_WRITEFD, sec_connect_callback, rh);
    rh->ev_timeout = event_create(CONNECT_TIMEOUT, EV_TIME,
	sec_connect_timeout, rh);
    event_activate(rh->rs->rc->ev_write);
    event_activate(rh->ev_timeout);
    g_mutex_unlock(security_mutex);

    return;

error:
    (*fn)(arg, &rh->sech, S_ERROR);
}

static char *validate_fingerprints(X509 *cert, char *ssl_fingerprint_file);

static char *
validate_fingerprints(
    X509 *cert,
    char *ssl_fingerprint_file)
{
    FILE *fingers;
    char fingerprint[32768];
    char *errmsg = NULL;

    unsigned char  md5[EVP_MAX_MD_SIZE + 1];
    unsigned int   len_md5;
    const EVP_MD  *evp_md5;
    char *md5_fingerprint;
    unsigned char  sha1[EVP_MAX_MD_SIZE + 1];
    unsigned int   len_sha1;
    const EVP_MD  *evp_sha1;
    char *sha1_fingerprint;
    char *fp;
    unsigned int   i;

    const char *md5_const  = "MD5 Fingerprint=";
    const char *sha1_const = "SHA1 Fingerprint=";
    const size_t md5_const_len = strlen(md5_const);
    const size_t sha1_const_len = strlen(sha1_const);

    if (ssl_fingerprint_file == NULL) {
	g_debug("No fingerprint_file set");
	return NULL;
    }

    evp_md5 = EVP_get_digestbyname("MD5");
    if (!evp_md5) {
	auth_debug(1, _("EVP_get_digestbyname(MD5) failed"));
    }
    if (!X509_digest(cert, evp_md5, md5, &len_md5)) {
	auth_debug(1, _("cannot get MD5 digest"));
    }

    md5_fingerprint  = malloc(len_md5*3 + 1);
    fp = md5_fingerprint;
    for (i=0; i < len_md5; i++) {
	snprintf(fp, 4, "%02X:", (unsigned) md5[i]);
	fp+=3;
    }
    /* remove latest ':' */
    fp --;
    *fp = '\0';
    auth_debug(1, _("md5: %s\n"), md5_fingerprint);

    evp_sha1 = EVP_get_digestbyname("SHA1");
    if (!evp_sha1) {
	auth_debug(1, _("EVP_get_digestbyname(SHA1) failed"));
    }
    if (!X509_digest(cert, evp_sha1, sha1, &len_sha1)) {
	auth_debug(1, _("cannot get SHA1 digest"));
    }

    sha1_fingerprint  = malloc(len_sha1*3 + 1);
    fp = sha1_fingerprint;
    for (i=0; i < len_sha1; i++) {
	snprintf(fp, 4, "%02X:", (unsigned) sha1[i]);
	fp+=3;
    }
    /* remove latest ':' */
    fp --;
    *fp = '\0';
    auth_debug(1, _("sha1: %s\n"), sha1_fingerprint);

    fingers = fopen(ssl_fingerprint_file, "r");
    if (!fingers) {
	errmsg = g_strdup_printf("Failed open of %s: %s",
				 ssl_fingerprint_file, strerror(errno));
	g_debug("%s", errmsg);
	g_free(md5_fingerprint);
	g_free(sha1_fingerprint);
	return errmsg;
    }

    while (fgets(fingerprint, 32768, fingers) != NULL) {
	int len = strlen(fingerprint)-1;
	if (len > 0 && fingerprint[len] == '\n')
	    fingerprint[len] = '\0';
	if (strncmp(md5_const, fingerprint, md5_const_len) == 0) {
	    if (strcmp(md5_fingerprint, fingerprint+md5_const_len) == 0) {
		g_debug("MD5 fingerprint '%s' match", md5_fingerprint);
		g_free(md5_fingerprint);
		g_free(sha1_fingerprint);
		fclose(fingers);
		return NULL;
	    }
	} else if (strncmp(sha1_const, fingerprint, sha1_const_len) == 0) {
	    if (strcmp(sha1_fingerprint, fingerprint+sha1_const_len) == 0) {
		g_debug("SHA1 fingerprint '%s' match", sha1_fingerprint);
		g_free(md5_fingerprint);
		g_free(sha1_fingerprint);
		fclose(fingers);
		return NULL;
	    }
	}
	auth_debug(1, _("Fingerprint '%s' doesn't match\n"), fingerprint);
    }
    g_free(md5_fingerprint);
    g_free(sha1_fingerprint);
    fclose(fingers);
    return g_strdup_printf("No fingerprint match");;
}

/*
 * Setup to handle new incoming connections
 */
static void
ssl_accept(
    const struct security_driver *driver,
    char *	(*conf_fn)(char *, void *),
    int		in,
    int		out,
    void	(*fn)(security_handle_t *, pkt_t *),
    void       *datap)
{
    sockaddr_union sin;
    socklen_t_equiv len = sizeof(struct sockaddr);
    struct tcp_conn *rc;
    char hostname[NI_MAXHOST];
    int result;
    char *errmsg = NULL;
    int   err;
    X509 *remote_cert;
    char *str;
    X509_NAME *x509_name;
    char *cert_hostname;
    SSL_CTX            *ctx;
    SSL                *ssl;
    int loc;
    char *ssl_dir = getconf_str(CNF_SSL_DIR);
    char *ssl_fingerprint_file = conf_fn("ssl_fingerprint_file", datap);
    char *ssl_cert_file        = conf_fn("ssl_cert_file", datap);
    char *ssl_key_file         = conf_fn("ssl_key_file", datap);
    char *ssl_ca_cert_file     = conf_fn("ssl_ca_cert_file", datap);
    char *ssl_cipher_list      = conf_fn("ssl_cipher_list", datap);
    int   ssl_check_host       = atoi(conf_fn("ssl_check_host", datap));
    int   ssl_check_certificate_host = atoi(conf_fn("ssl_check_certificate_host", datap));

    if (getpeername(in, (struct sockaddr *)&sin, &len) < 0) {
	g_debug(_("getpeername returned: %s"), strerror(errno));
	return;
    }
    if ((result = getnameinfo((struct sockaddr *)&sin, len,
			      hostname, NI_MAXHOST, NULL, 0, 0) != 0)) {
	g_debug(_("getnameinfo failed: %s"),
		  gai_strerror(result));
	return;
    }

    if (ssl_check_host && check_name_give_sockaddr(hostname,
				 (struct sockaddr *)&sin, &errmsg) < 0) {
	amfree(errmsg);
	return;
    }

    if (ssl_dir) {
	if (!ssl_cert_file || ssl_cert_file == '\0') {
	    ssl_cert_file = g_strdup_printf("%s/me/crt.pem", ssl_dir);
	}
	if (!ssl_key_file || ssl_key_file == '\0') {
	    ssl_key_file = g_strdup_printf("%s/me/private/key.pem", ssl_dir);
	}
	if (!ssl_ca_cert_file || ssl_ca_cert_file == '\0') {
	    ssl_ca_cert_file = g_strdup_printf("%s/CA/crt.pem", ssl_dir);
	}
    }

    if (!ssl_cert_file) {
	g_debug(_("ssl-cert-file must be set in amanda-remote.conf"));
	return;
    }

    if (!ssl_key_file) {
	g_debug(_("ssl-key-file must be set in amanda-remote.conf"));
	return;
    }

    if (!ssl_ca_cert_file) {
	g_debug(_("ssl_ca_cert_file must be set in amanda-remote.conf"));
	return;
    }

    len = sizeof(sin);
    init_ssl();

    /* Create a SSL_CTX structure */
#if OPENSSL_VERSION_NUMBER < 0x10100000L
    ctx = SSL_CTX_new(SSLv3_server_method());
#else
    ctx = SSL_CTX_new(TLS_server_method());
#endif
    if (!ctx) {
	g_debug(_("SSL_CTX_new failed: %s"),
		 ERR_error_string(ERR_get_error(), NULL));
	return;
    }
    SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);

    if (ssl_cipher_list) {
	g_debug("Set ssl_cipher_list to %s", ssl_cipher_list);
	if (SSL_CTX_set_cipher_list(ctx, ssl_cipher_list) == 0) {
	    g_debug(_("SSL_CTX_set_cipher_list failed: %s"),
		     ERR_error_string(ERR_get_error(), NULL));
	    return;
	}
    }

    /* Load the me certificate into the SSL_CTX structure */
    g_debug(_("Loading ssl-cert-file certificate %s"), ssl_cert_file);
    if (SSL_CTX_use_certificate_file(ctx, ssl_cert_file,
				     SSL_FILETYPE_PEM) <= 0) {
	g_debug(_("Load ssl-cert-file failed: %s"),
		 ERR_error_string(ERR_get_error(), NULL));
	return;
    }

    /* Load the private-key corresponding to the me certificate */
    g_debug(_("Loading ssl-key-file private-key %s"), ssl_key_file);
    if (SSL_CTX_use_PrivateKey_file(ctx, ssl_key_file,
				    SSL_FILETYPE_PEM) <= 0) {
	g_debug(_("Load ssl-key-file failed: %s"),
		 ERR_error_string(ERR_get_error(), NULL));
	return;
    }

    if (ssl_ca_cert_file) {
        /* Load the RSA CA certificate into the SSL_CTX structure */
	g_debug(_("Loading ssl-ca-cert-file ca certificate %s"),
		 ssl_ca_cert_file);
        if (!SSL_CTX_load_verify_locations(ctx, ssl_ca_cert_file, NULL)) {
	    g_debug(_("Load ssl-ca-cert-file failed: %s"),
		     ERR_error_string(ERR_get_error(), NULL));
	    return;
        }

	/* Set to require peer (remote) certificate verification */
	SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

	/* Set the verification depth to 1 */
	SSL_CTX_set_verify_depth(ctx,1);
    }

    ssl = SSL_new(ctx);
    if (!ssl) {
	g_debug(_("SSL_new failed: %s"),
		 ERR_error_string(ERR_get_error(), NULL));
	return;
    }
    SSL_set_accept_state(ssl);

    /* Assign the socket into the SSL structure (SSL and socket without BIO) */
    SSL_set_fd(ssl, in);

    /* Perform SSL Handshake on the SSL me */
    err = SSL_accept(ssl);
    if (err == -1) {
	g_debug(_("SSL_accept failed: %s"),
		 ERR_error_string(ERR_get_error(), NULL));
	return;
    }

    /* Get the me's certificate (optional) */
    remote_cert = SSL_get_peer_certificate (ssl);

    if (remote_cert == NULL) {
	g_debug(_("remote doesn't sent a certificate"));
	return;
    }

    x509_name = X509_get_subject_name(remote_cert);
    str = X509_NAME_oneline(X509_get_subject_name(remote_cert), 0, 0);
    auth_debug(1, _("\t subject: %s\n"), str);
    amfree (str);

    str = X509_NAME_oneline(X509_get_issuer_name(remote_cert), 0, 0);
    auth_debug(1, _("\t issuer: %s\n"), str);
    amfree(str);

    loc = -1;
    loc = X509_NAME_get_index_by_NID(x509_name, NID_commonName, loc);
    if (loc != -1) {
	X509_NAME_ENTRY *x509_entry = X509_NAME_get_entry(x509_name, loc);
	ASN1_STRING *asn1_string = X509_NAME_ENTRY_get_data(x509_entry);
	cert_hostname = (char *)ASN1_STRING_data(asn1_string);
	auth_debug(1, "common_name: %s\n", cert_hostname);

	if (ssl_check_certificate_host &&
	    check_name_give_sockaddr((char*)cert_hostname,
			 (struct sockaddr *)&sin, &errmsg) < 0) {
	    g_debug("Common name of certicate (%s) doesn't resolv to IP (%s)", cert_hostname, str_sockaddr(&sin));
	    amfree(errmsg);
	    X509_free(remote_cert);
	    return;
	}
    } else {
	g_debug("Certificate have no common name");
	X509_free(remote_cert);
	return;
    }

    if (ssl_dir) {
	if (!ssl_fingerprint_file || ssl_fingerprint_file == '\0') {
	    struct stat  statbuf;
	    ssl_fingerprint_file = g_strdup_printf("%s/remote/%s/fingerprint", ssl_dir, cert_hostname);
	    if (stat(ssl_fingerprint_file, &statbuf) == -1) {
		g_free(ssl_fingerprint_file);
		ssl_fingerprint_file = NULL;
	    }
	}
    }

    if (ssl_fingerprint_file) {
        g_debug(_("Loading ssl-fingerprint-file %s"), ssl_fingerprint_file);
	str = validate_fingerprints(remote_cert, ssl_fingerprint_file);
	if (str) {
	    g_debug("%s", str);
	    amfree(str);
	    X509_free(remote_cert);
	    return;
	}
    }
    X509_free(remote_cert);

    rc = sec_tcp_conn_get(NULL, hostname, 0);
    rc->recv_security_ok = &bsd_recv_security_ok;
    rc->prefix_packet = &bsd_prefix_packet;
    rc->need_priv_port = 0;
    copy_sockaddr(&rc->peer, &sin);
    rc->read = in;
    rc->write = out;
    rc->accept_fn = fn;
    rc->driver = driver;
    rc->conf_fn = conf_fn;
    rc->datap = datap;
    rc->ctx = ctx;
    rc->ssl = ssl;
    strncpy(rc->hostname, cert_hostname, sizeof(rc->hostname)-1);

    g_debug(_("SSL_cipher: %s"), SSL_get_cipher(rc->ssl));

    sec_tcp_conn_read(rc);
}

/*
 * Open a ssl connection to the host listed in rc->hostname
 * Returns negative on error, with an errmsg in rc->errmsg.
 */
static int
runssl(
    struct sec_handle *	rh,
    in_port_t port,
    char *src_ip,
    char *ssl_fingerprint_file,
    char *ssl_cert_file,
    char *ssl_key_file,
    char *ssl_ca_cert_file,
    char *ssl_cipher_list,
    int   ssl_check_certificate_host)
{
    int		     my_socket;
    in_port_t	     my_port;
    struct tcp_conn *rc = rh->rc;
    int              err;
    X509            *remote_cert;
    sockaddr_union   sin;
    socklen_t_equiv  len;
    char            *stream_msg = NULL;

    if (!ssl_key_file) {
	security_seterror(&rh->sech, _("ssl-key-file must be set"));
	return -1;
    }

    if (!ssl_cert_file) {
	security_seterror(&rh->sech, _("ssl-cert-file must be set"));
	return -1;
    }

    my_socket = stream_client(src_ip,
				  rc->hostname,
				  port,
				  STREAM_BUFSIZE,
				  STREAM_BUFSIZE,
				  &my_port,
				  0, &stream_msg);

    if (stream_msg) {
	security_seterror(&rh->sech, "%s", stream_msg);
	g_free(stream_msg);
	return -1;
    }

    if (my_socket < 0) {
	security_seterror(&rh->sech, "%s", strerror(errno));
	return -1;
    }

    rc->read = rc->write = my_socket;

    len = sizeof(sin);
    if (getpeername(my_socket, (struct sockaddr *)&sin, &len) < 0) {
	security_seterror(&rh->sech, _("getpeername returned: %s\n"), strerror(errno));
	return -1;
    }
    copy_sockaddr(&rc->peer, &sin);

    init_ssl();

    /* Create an SSL_CTX structure */
#if OPENSSL_VERSION_NUMBER < 0x10100000L
    rc->ctx = SSL_CTX_new(SSLv3_client_method());
#else
    rc->ctx = SSL_CTX_new(TLS_client_method());
#endif
    if (!rc->ctx) {
	security_seterror(&rh->sech, "%s",
			  ERR_error_string(ERR_get_error(), NULL));
	return -1;
    }
    SSL_CTX_set_mode(rc->ctx, SSL_MODE_AUTO_RETRY);

    if (ssl_cipher_list) {
	g_debug("Set ssl_cipher_list to %s", ssl_cipher_list);
	if (SSL_CTX_set_cipher_list(rc->ctx, ssl_cipher_list) == 0) {
	    security_seterror(&rh->sech, "%s",
		              ERR_error_string(ERR_get_error(), NULL));
	    return -1;
	}
    }

    /* Load the private-key corresponding to the remote certificate */
    g_debug("Loading ssl-key-file private-key %s", ssl_key_file);
    if (SSL_CTX_use_PrivateKey_file(rc->ctx, ssl_key_file,
				    SSL_FILETYPE_PEM) <= 0) {
	security_seterror(&rh->sech, "%s",
			  ERR_error_string(ERR_get_error(), NULL));
	return -1;
    }

    /* Load the me certificate into the SSL_CTX structure */
    g_debug("Loading ssl-cert-file certificate %s", ssl_cert_file);
    if (SSL_CTX_use_certificate_file(rc->ctx, ssl_cert_file,
				     SSL_FILETYPE_PEM) <= 0) {
	security_seterror(&rh->sech, "%s",
			  ERR_error_string(ERR_get_error(), NULL));
	return -1;
    }

    /* Check if the remote certificate and private-key matches */
    if (ssl_cert_file) {
	if (!SSL_CTX_check_private_key(rc->ctx)) {
	security_seterror(&rh->sech,
		_("Private key does not match the certificate public key"));
	return -1;
	}
    }

    if (ssl_ca_cert_file) {
        /* Load the RSA CA certificate into the SSL_CTX structure */
        /* This will allow this remote to verify the me's     */
        /* certificate.                                           */
	g_debug("Loading ssl-ca-cert-file ca %s", ssl_ca_cert_file);
        if (!SSL_CTX_load_verify_locations(rc->ctx, ssl_ca_cert_file, NULL)) {
	    security_seterror(&rh->sech, "%s",
			      ERR_error_string(ERR_get_error(), NULL));
	    return -1;
        }
    } else {
	g_debug(_("no ssl-ca-cert-file defined"));
    }

    /* Set flag in context to require peer (me) certificate */
    /* verification */
    if (ssl_ca_cert_file) {
	g_debug("Enabling certification verification");
	SSL_CTX_set_verify(rc->ctx, SSL_VERIFY_PEER, NULL);
	SSL_CTX_set_verify_depth(rc->ctx, 1);
    } else {
	g_debug("Not enabling certification verification");
    }

    /* ----------------------------------------------- */
    rc->ssl = SSL_new(rc->ctx);
    if (!rc->ssl) {
	security_seterror(&rh->sech, _("SSL_new failed: %s"),
			  ERR_error_string(ERR_get_error(), NULL));
	return -1;
    }
    SSL_set_connect_state(rc->ssl);

    /* Assign the socket into the SSL structure (SSL and socket without BIO) */
    SSL_set_fd(rc->ssl, my_socket);

    /* Perform SSL Handshake on the SSL remote */
    err = SSL_connect(rc->ssl);
    if (err == -1) {
	security_seterror(&rh->sech, _("SSL_connect failed: %s"),
			  ERR_error_string(ERR_get_error(), NULL));
	return -1;
    }

    /* Get the me's certificate (optional) */
    remote_cert = SSL_get_peer_certificate(rc->ssl);

    if (remote_cert == NULL) {
	security_seterror(&rh->sech, _("server have no certificate"));
	return -1;
    } else {
        char *str;

        str = X509_NAME_oneline(X509_get_subject_name(remote_cert), 0, 0);
        auth_debug(1, _("\t subject: %s\n"), str);
        amfree (str);

        str = X509_NAME_oneline(X509_get_issuer_name(remote_cert), 0, 0);
        auth_debug(1, _("\t issuer: %s\n"), str);
        amfree(str);

	if (ssl_check_certificate_host) {
	    int   loc = -1;
	    char *errmsg = NULL;
	    X509_NAME *x509_name = X509_get_subject_name(remote_cert);

	    loc = X509_NAME_get_index_by_NID(x509_name, NID_commonName, loc);
	    if (loc != -1) {
		X509_NAME_ENTRY *x509_entry = X509_NAME_get_entry(x509_name, loc);
		ASN1_STRING *asn1_string = X509_NAME_ENTRY_get_data(x509_entry);
		char *cert_hostname =  (char *)ASN1_STRING_data(asn1_string);
		auth_debug(1, "common_name: %s\n", cert_hostname);

		if (check_name_give_sockaddr((char*)cert_hostname,
				 (struct sockaddr *)&rc->peer, &errmsg) < 0) {
		    security_seterror(&rh->sech,
		       _("Common name of certicate (%s) doesn't resolv to IP (%s): %s"),
		       cert_hostname, str_sockaddr(&rc->peer), errmsg);
		    amfree(errmsg);
		    return -1;
		}
		auth_debug(1,
		         _("Certificate common name (%s) resolve to IP (%s)\n"),
			 cert_hostname, str_sockaddr(&rc->peer));
	    } else {
		security_seterror(&rh->sech,
				  _("Certificate have no common name"));
		g_debug("Certificate have no common name");
		return -1;
	    }
	}

/*
	if (ssl_dir) {
	    if (!ssl_fingerprint_file || ssl_fingerprint_file == '\0') {
		struct stat  statbuf;
		ssl_fingerprint_file = g_strdup_printf("%s/remote/%s/fingerprint", ssl_dir, cert_hostname);
		if (stat(ssl_fingerprint_file, &statbuf) == -1) {
		    g_free(ssl_fingerprint_file);
		    ssl_fingerprint_file = NULL;
		}
	    }
	}
*/

	if (ssl_fingerprint_file) {
            g_debug(_("run_ssl: Loading ssl-fingerprint-file %s"), ssl_fingerprint_file);
	    str = validate_fingerprints(remote_cert, ssl_fingerprint_file);
	    if (str) {
		security_seterror(&rh->sech, "%s", str);
		amfree(str);
		return -1;
	    }
	}
	X509_free (remote_cert);
    }

    g_debug(_("SSL_cipher: %s"), SSL_get_cipher(rc->ssl));

    return 0;
}

static ssize_t
ssl_data_write(
    void         *c,
    struct iovec *iov,
    int           iovcnt)
{
    struct tcp_conn *rc = c;
    int              i;
    int              size;

    size = 0;
    for (i=0; i < iovcnt; i++) {
	size += SSL_write(rc->ssl, iov[i].iov_base, iov[i].iov_len); /* JLM check ERROR */
    }
    return size;
}

static ssize_t
ssl_data_write_non_blocking(
    void         *c,
    struct iovec *iov,
    int           iovcnt)
{
    struct tcp_conn *rc = c;
    int              i;
    int              size;
    int              r;

    int flags = fcntl(rc->write, F_GETFL, 0);
    flags = fcntl(rc->write, F_SETFL, flags|O_NONBLOCK);

    while(iovcnt>0 && iov->iov_len == 0) {
        iov++;
        iovcnt--;
    }

    size = 0;
    for (i=0; i < iovcnt; i++) {
	r = SSL_write(rc->ssl, iov[i].iov_base, iov[i].iov_len);
	if (r < 0) {
	    return size; /* JLM check ERROR */
	} else if (r == 0) {
	    return size; /* JLM check ERROR */
	} else if ((unsigned int)r < iov[i].iov_len) {
	    iov[i].iov_len -= r;
	    size += r;
	    return size;
	} else {
	    iov[i].iov_len = 0;
	    size += r;
	}
    }
    return size;
}

static ssize_t
ssl_data_read(
    void    *c,
    void    *buf,
    size_t   size,
    int      timeout G_GNUC_UNUSED)
{
    struct tcp_conn *rc = c;

    return SSL_read(rc->ssl, buf, size);
}

static void
init_ssl(void)
{
    static int init_done = 0;

    if (init_done == 0) {
	/* Load encryption & hashing algorithms for the SSL program */
	SSL_library_init();

	/* Load the error strings for SSL & CRYPTO APIs */
	SSL_load_error_strings();

	init_done = 1;
    }
}
