/* This is heavily bastardized TLS code for SMTP client (POSTFIX) by:
 *	Lutz Jaenicke
 *	BTU Cottbus
 *	Allgemeine Elektrotechnik
 *	Universitaetsplatz 3-4
 *	D-03044 Cottbus, Germany
 *
 * Adaptation to ZMailer is by Matti Aarnio <mea@nic.funet.fi> (c) 1999-2006
 */

#include "smtp.h"

extern int timeout_tcpw;

/* Global variables -- BOO! */

static FILE *vlog = NULL;

/* more further down (in OpenSSL specific code..) */

#ifdef HAVE_STDARG_H
#ifdef __STDC__
void msg_info(SmtpState *SS, char *fmt, ...)
#else /* Not ANSI-C */
void msg_info(SS, fmt)
	SmtpState *SS;
	char *fmt;
#endif
#else
/* VARARGS */
void
msg_info(SS, va_alist)
	SmtpState *SS;
	va_dcl
#endif
{
	va_list	ap;
#ifdef HAVE_STDARG_H
	va_start(ap, fmt);
#else
	char *fmt;
	va_start(ap);
	fmt = va_arg(ap, char *);
#endif

	if (vlog) {
	  fprintf(vlog, "# ");

#ifdef	HAVE_VPRINTF
	  vfprintf(vlog, fmt, ap);
#else	/* !HAVE_VPRINTF */
 ERROR:ERROR:ERROR:No 
#endif	/* HAVE_VPRINTF */

	  fprintf(vlog,"\n");
	  fflush(vlog);

	}
	if (logfp) {
	  fprintf(logfp, "%s#\t", logtag());
#ifdef	HAVE_VPRINTF
	  vfprintf(logfp, fmt, ap);
#else	/* !HAVE_VPRINTF */
 ERROR:ERROR:ERROR:No 
#endif	/* HAVE_VPRINTF */

	  fprintf(logfp,"\n");
	  fflush(logfp);
	}
#if 0
	if (stderr) {
	  fprintf(stderr, "%s#\t", logtag());
#ifdef	HAVE_VPRINTF
	  vfprintf(stderr, fmt, ap);
#else	/* !HAVE_VPRINTF */
 ERROR:ERROR:ERROR:No 
#endif	/* HAVE_VPRINTF */

	  fprintf(stderr,"\n");
	  fflush(stderr);
	}
#endif

	va_end(ap);
}



#ifdef HAVE_OPENSSL

/* Global variables -- BOO! */

static int TLScontext_index = -1;
static int TLSpeername_index = -1;
static int tls_clientengine = 0;

/* Other global static data */
static const char MAIL_TLS_CLNT_CACHE[] = "TLSclntcache";
/*
 * When saving sessions, we want to make sure, that the lenght of
 * the key is somehow limited. When saving client sessions, the hostname
 * is used as key. According to HP-UX 10.20, MAXHOSTNAMELEN=64. Maybe new
 * standards will increase this value, but as this will break
 * compatiblity with existing implementations, we won't see this
 * for long. We therefore choose a limit of 64 bytes.
 * The length of the (TLS) session id can be up to 32 bytes according to
 * RFC2246, so it fits well into the 64bytes limit.
 */
static const int id_maxlength = 32;	/* Max ID length in bytes */

/* Configuration variables */

static int do_dump = 0;

int	tls_scache_timeout = 3600;	/* One hour */
int	tls_use_scache     = 0;

extern int demand_TLS_mode;
extern int tls_available;

const char *tls_random_source;
const char *tls_cipherlist;

const char *tls_CAfile;
const char *tls_CApath;
const char *tls_cert_file;
const char *tls_key_file;
const char *tls_dcert_file;
const char *tls_dkey_file;

int	tls_use_read_ahead = 0;
int	tls_protocol_tlsv1_only = 0;
int     tls_enforce_peername = 0;

int	tls_loglevel = 0;


/* Structure used for random generator seeding.. */
struct _randseed {
	int pid;
	int ppid;
	struct timeval tv;
} tls_randseed;


static char *zdupnstr(const void *p, const int len)
{
	char *dup = malloc(len+1);
	if (!dup) return NULL;
	memcpy(dup, p, len);
	dup[len] = 0; /* return a 0-terminated string */

	return dup;
}


void mail_queue_path(buf, subdir, filename)
     char *buf;
     char *subdir;
     char *filename;
{
  const char *po = getzenv("POSTOFFICE");
  if (!po) po = POSTOFFICE;

  sprintf(buf, "%s/%s/%s", po, subdir, filename);
}


/* skeleton taken from OpenSSL crypto/err/err_prn.c */

static void tls_print_errors(SmtpState *SS)
{
    unsigned long l;
    char    buf[256];
    const char   *file;
    const char   *data;
    int     line;
    int     flags;
    unsigned long es;

    es = CRYPTO_thread_id();
    while ((l = ERR_get_error_line_data(&file, &line, &data, &flags)) != 0) {
      if (flags & ERR_TXT_STRING)
	msg_info(SS, "%lu:%s:%s:%d:%s:", es, ERR_error_string(l, buf),
		 file, line, data);
      else
	msg_info(SS, "%lu:%s:%s:%d:", es, ERR_error_string(l, buf),
		 file, line);
    }
}



/*
 * Function to perform the handshake for SSL_accept(), SSL_connect(),
 * and SSL_shutdown().
 * Call the underlying network_biopair_interop-layer to make sure the
 * write buffer is flushed after every operation (that did not fail with
 * a fatal error).
 */
static int do_tls_operation( SmtpState *SS, int timeout,
			     int (*hsfunc)(SSL *), const char *action )
{
    int status;
    int sslerr;
    int retval = 0;
    int done = 0;

    while (!done) {

	static struct zmpollfd *fds = NULL;
	int fdscount;

	int rc, i;
	int wantread, wantwrit;

    ssl_connect_retry:;

        status = hsfunc(SS->TLS.ssl);
	sslerr = SSL_get_error(SS->TLS.ssl, status);

#if (OPENSSL_VERSION_NUMBER <= 0x0090581fL)
	/*
	 * There is a bug up to and including OpenSSL-0.9.5a:
	 * if an error occurs while checking the peers certificate
	 * due to some certificate error (e.g. as happend with
	 * a RSA-padding error), the error is put  onto the error stack.
	 * If verification is not enforced, this error should be ignored,
	 * but the error-queue is not cleared, so we can find this error
	 * here. The bug has been fixed on May 28, 2000.
	 *
	 * This bug so far has only manifested as
	 * 4800:error:0407006A:rsa routines:RSA_padding_check_PKCS1_type_1:block type is not 01:rsa_pk1.c:100:
	 * 4800:error:04067072:rsa routines:RSA_EAY_PUBLIC_DECRYPT:padding check failed:rsa_eay.c:396:
	 * 4800:error:0D079006:asn1 encoding routines:ASN1_verify:bad get asn1 object call:a_verify.c:109:
	 * so that we specifically test for this error.
	 * We print the errors to the logfile and automatically clear
	 * the error queue. Then we retry to get another error code.
	 * We cannot do better, since we can only retrieve the last
	 * entry of the error-queue without actually cleaning it on
	 * the way.
	 *
	 * This workaround is secure, as verify_result is set to "failed"
	 * anyway.
	 */
	if (sslerr == SSL_ERROR_SSL) {
	  if (ERR_peek_error() == 0x0407006AL) {
	    tls_print_errors(SS); /* Keep information for the logfile */
	    msg_info(SS,"OpenSSL <= 0.9.5a workaround called: certificate errors ignored");
	    sslerr = SSL_get_error(SS->TLS.ssl, status);
	  }
	}
#endif
	switch (sslerr) {

	case SSL_ERROR_WANT_READ:
	    SS->TLS.wantreadwrite = -1;
	    sslerr = EAGAIN;
	    break;
	case SSL_ERROR_WANT_WRITE:
	    SS->TLS.wantreadwrite =  1;
	    sslerr = EAGAIN;
	    break;

	case SSL_ERROR_WANT_X509_LOOKUP:
	    goto ssl_connect_retry;
	    break;

	case SSL_ERROR_NONE: /* successfull completition.. */
	    retval = status;
	    break; /* But do flush writes and reads at first.. */

	default: /* and all else.. */
	    retval = status;
	    goto ssl_operation_done;
	}

	wantread = (BIO_should_read(SSL_get_rbio(SS->TLS.ssl)));
	wantwrit = (BIO_should_write(SSL_get_wbio(SS->TLS.ssl)));

	if (!wantread && !wantwrit) {
	  /* Not proper retry by read or write! */

	ssl_connect_error_bailout:;

	  msg_info(SS, "SSL_%s error %d/%d", action, status, sslerr);
	  tls_print_errors(SS);

	  return (-1);
	}

	i = SSL_get_fd(SS->TLS.ssl);

	fdscount = 0;

	if (wantread)
	  zmpoll_addfd(&fds, &fdscount, i, -1, NULL);

	if (wantwrit)
	  zmpoll_addfd(&fds, &fdscount, -1, i, NULL);

	rc = zmpoll( fds, fdscount, timeout * 1000 );
	sslerr = errno;

	if (rc == 0) {
	  /* TIMEOUT! */
	  sslerr = ETIMEDOUT;
	  goto ssl_connect_error_bailout;
	}

	if (rc < 0) {
	  if (sslerr == EINTR || sslerr == EAGAIN)
	    continue;

	  /* Bug time ?! */
	  goto ssl_connect_error_bailout;
	}
	/* Default is then success for either read, or write.. */
    }

 ssl_operation_done:;


    return retval;
}



 /*
  * Set up the cert things on the CLIENT side. We do need both the
  * private key (in key_file) and the cert (in cert_file).
  * Both files may be identical.
  *
  * This function is taken from OpenSSL apps/s_cb.c
  */

static int set_cert_stuff(SmtpState *SS,
			  const char *cert_file, const char *key_file)
{
    SSL_CTX * ctx = SS->TLS.ctx;

    if (cert_file && *cert_file) {

	if (SSL_CTX_use_certificate_file(ctx, cert_file,
					 SSL_FILETYPE_PEM) <= 0) {
	    msg_info(NULL, "unable to get certificate from '%s'", cert_file);
	    tls_print_errors(SS);
	    return (0);
	}
	if (!key_file || !*key_file )
	    key_file = cert_file;
	if (SSL_CTX_use_PrivateKey_file(ctx, key_file,
					SSL_FILETYPE_PEM) <= 0) {
	    msg_info(NULL, "unable to get private key from '%s'", key_file);
	    tls_print_errors(SS);
	    return (0);
	}
	/* Now we know that a key and cert have been set against
         * the SSL context */
	if (!SSL_CTX_check_private_key(ctx)) {
	    msg_info(NULL, "Private key does not match the certificate public key");
	    return (0);
	}
    }
    return (1);
}


/* taken from OpenSSL apps/s_cb.c */

static RSA *tmp_rsa_cb(SSL * s, int export, int keylength)
{
    static RSA *rsa_tmp = NULL;

    if (rsa_tmp == NULL) {
	rsa_tmp = RSA_generate_key(keylength, RSA_F4, NULL, NULL);
    }
    return (rsa_tmp);
}


/*
 * Skeleton taken from OpenSSL apps/s_cb.c
 *
 * The verify_callback is called several times (directly or indirectly) from
 * crypto/x509/x509_vfy.c. It is called as a last check for several issues,
 * so this verify_callback() has the famous "last word". If it does return "0",
 * the handshake is immediately shut down and the connection fails.
 *
 * Postfix/TLS has two modes, the "use" mode and the "enforce" mode:
 *
 * In the "use" mode we never want the connection to fail just because there is
 * something wrong with the certificate (as we would have sent happily without
 * TLS).  Therefore the return value is always "1".
 *
 * In the "enforce" mode we can shut down the connection as soon as possible.
 * In server mode TLS itself may be enforced (e.g. to protect passwords),
 * but certificates are optional. In this case the handshake must not fail
 * if we are unhappy with the certificate and return "1" in any case.
 * Only if a certificate is required the certificate must pass the verification
 * and failure to do so will result in immediate termination (return 0).
 * In the client mode the decision is made with respect to the peername
 * enforcement. If we strictly enforce the matching of the expected peername
 * the verification must fail immediatly on verification errors. We can also
 * immediatly check the expected peername, as it is the CommonName at level 0.
 * In all other cases, the problem is logged, so the SSL_get_verify_result()
 * will inform about the verification failure, but the handshake (and SMTP
 * connection will continue).
 *
 * The only error condition not handled inside the OpenSSL-Library is the
 * case of a too-long certificate chain, so we check inside verify_callback().
 * We only take care of this problem, if "ok = 1", because otherwise the
 * verification already failed because of another problem and we don't want
 * to overwrite the other error message. And if the verification failed,
 * there is no such thing as "more failed", "most failed"... :-)
 */


static int verify_callback(int ok, X509_STORE_CTX * ctx)
{
    char    buf[4000];
    char   *peername_left;
    X509   *err_cert;
    int     err;
    int     depth;
    int     verify_depth;
    int     hostname_matched;
    SSL    *con;
    SmtpState *SS;

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

    con = X509_STORE_CTX_get_ex_data(ctx,
				     SSL_get_ex_data_X509_STORE_CTX_idx());

    SS = SSL_get_ex_data(con, TLScontext_index);


    X509_NAME_oneline(X509_get_subject_name(err_cert), buf, sizeof(buf));

    if (tls_loglevel >= 1)
      msg_info(SS, "Peer cert verify depth=%d %s", depth, buf);

    verify_depth = SSL_get_verify_depth(con);

    if (ok && (verify_depth >= 0) && (depth > verify_depth)) {
	ok = 0;
	err = X509_V_ERR_CERT_CHAIN_TOO_LONG;
	X509_STORE_CTX_set_error(ctx, err);
    }
    if (!ok) {
      msg_info(SS, "verify error:num=%d:%s", err,
	       X509_verify_cert_error_string(err));
    }

    if (ok && (depth == 0) && SS->TLS.sslmode) {
	/*
	 * Check out the name certified against the hostname expected.
	 * In case it does not match, print an information about the result.
	 * If a matching is enforced, bump out with a verification error
	 * immediately.
	 */
	buf[0] = '\0';
	if (!X509_NAME_get_text_by_NID(X509_get_subject_name(err_cert),
				       NID_commonName, buf, sizeof(buf))) {
	  msg_info(SS,"Could not parse server's subject CN");
	  tls_print_errors(SS);
	}

	hostname_matched = 0;
	if (cistrcmp(SS->TLS.peername_save, buf) == 0)
	    hostname_matched = 1;
	else if ((strlen(buf) > 2) &&
		 (buf[0] == '*') && (buf[1] == '.')) {
	    /*
	     * Allow wildcard certificate matching.
	     * The proposed rules in RFCs (2818: HTTP/TLS,
	     * 2830: LDAP/TLS) are different, RFC2874
	     * does not specify a rule, so here the strict
	     * rule is applied.
	     * An asterisk '*' is allowed as the leftmost
	     * component and may replace the left most part
	     * of the hostname. Matching is done by removing
	     * '*.' from the wildcard name and the `name.`
	     * from the peername and compare what is left.
	     */
	    peername_left = strchr(SS->TLS.peername_save, '.');
	    if (peername_left) {
		if (cistrcmp(peername_left + 1, buf + 2) == 0)
		    hostname_matched = 1;
	    }
	}

	if (!hostname_matched) {
	  msg_info(SS,"Peer verification: CommonName in certificate does not match: '%s' != '%s'", buf, SS->TLS.peername_save);
	  if (SS->TLS.enforce_verify_errors && SS->TLS.enforce_CN) {
	    err = X509_V_ERR_CERT_REJECTED;
	    X509_STORE_CTX_set_error(ctx, err);
	    msg_info(SS,"Verify failure: Hostname mismatch");
	    ok = 0;
	  }
	}
    }

    switch (ctx->error) {
    case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
	X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert),
			  buf, sizeof(buf));
	msg_info(SS, "issuer= %s", buf);
	break;
    case X509_V_ERR_CERT_NOT_YET_VALID:
    case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
	msg_info(SS, "cert not yet valid");
	break;
    case X509_V_ERR_CERT_HAS_EXPIRED:
    case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
	msg_info(SS, "cert has expired");
	break;
    }
    if (tls_loglevel >= 1)
	msg_info(SS, "verify return:%d", ok);
    if (SS->TLS.enforce_verify_errors)
	return (ok); 
    else
	return (1);
}

/* taken from OpenSSL apps/s_cb.c */

static void apps_ssl_info_callback(const SSL * s, int where, int ret)
{
    char   *str;
    int     w;
    void *SS = NULL; /* FIXME! use context-saved ptr.. */

    w = where & ~SSL_ST_MASK;

    if (w & SSL_ST_CONNECT)
	str = "SSL_connect";
    else if (w & SSL_ST_ACCEPT)
	str = "SSL_accept";
    else
	str = "undefined";

    if (where & SSL_CB_LOOP) {
	if (tls_loglevel >= 2)
	    msg_info(SS, "%s:%s", str, SSL_state_string_long(s));
    } else if (where & SSL_CB_ALERT) {
	str = (where & SSL_CB_READ) ? "read" : "write";
	if (tls_loglevel >= 2 ||
	    ((ret & 0xff) != SSL3_AD_CLOSE_NOTIFY))
	msg_info(SS, "SSL3 alert %s:%s:%s", str,
		 SSL_alert_type_string_long(ret),
		 SSL_alert_desc_string_long(ret));
    } else if (where & SSL_CB_EXIT) {
	if (ret == 0)
	    msg_info(SS, "%s:failed in %s",
		     str, SSL_state_string_long(s));
	else if (ret < 0) {
	    msg_info(SS, "%s:error in %s",
		     str, SSL_state_string_long(s));
	}
    }
}

/*
 * taken from OpenSSL crypto/bio/b_dump.c, modified to save a lot of strcpy
 * and strcat by Matti Aarnio.
 */

#define TRUNCATE
#define DUMP_WIDTH	16

int tls_dump(SmtpState *SS, const char *s, int len)
{
    int     ret = 0;
    char    buf[160 + 1];
    char    *ss;
    int     i;
    int     j;
    int     rows;
    int     trunc;
    unsigned char ch;

    trunc = 0;

#ifdef TRUNCATE
    for (; (len > 0) && ((s[len - 1] == ' ') || (s[len - 1] == '\0')); len--)
	trunc++;
#endif

    rows = (len / DUMP_WIDTH);
    if ((rows * DUMP_WIDTH) < len)
	rows++;

    for (i = 0; i < rows; i++) {
	buf[0] = '\0';				/* start with empty string */
	ss = buf;

	sprintf(ss, "%04x ", i * DUMP_WIDTH);
	ss += strlen(ss);
	for (j = 0; j < DUMP_WIDTH; j++) {
	    if (((i * DUMP_WIDTH) + j) >= len) {
		strcpy(ss, "   ");
	    } else {
		ch = ((unsigned char) *((char *) (s) + i * DUMP_WIDTH + j))
		    & 0xff;
		sprintf(ss, "%02x%c", ch, j == 7 ? '|' : ' ');
		ss += 3;
	    }
	}
	ss += strlen(ss);
	*ss++ = ' ';
	for (j = 0; j < DUMP_WIDTH; j++) {
	    if (((i * DUMP_WIDTH) + j) >= len)
		break;
	    ch = ((unsigned char) *((char *) (s) + i * DUMP_WIDTH + j)) & 0xff;
	    *ss++ = (((ch >= ' ') && (ch <= '~')) ? ch : '.');
	    if (j == 7) *ss++ = ' ';
	}
	*ss = 0;
	/* 
	 * if this is the last call then update the ddt_dump thing so that
         * we will move the selection point in the debug window
         */
	msg_info(SS, "%s", buf);
	ret += strlen(buf);
    }
#ifdef TRUNCATE
    if (trunc > 0) {
	sprintf(buf, "%04x - <SPACES/NULS>", len + trunc);
	msg_info(SS, "%s", buf);
	ret += strlen(buf);
    }
#endif
    return (ret);
}



/* taken from OpenSSL apps/s_cb.c */

static long bio_dump_cb(BIO * bio, int cmd, const char *argp, int argi,
			long argl, long ret)
{
    void * SS = NULL; /* FIXME: use stored state local thingie ??? */
    if (!do_dump)
	return (ret);

    if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) {
	msg_info(SS, "read from %08X [%08lX] (%d bytes => %ld (0x%X))",
		 bio, argp, argi, ret, ret);
	tls_dump(NULL, argp, (int) ret);
	return (ret);
    } else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) {
	msg_info(SS, "write to %08X [%08lX] (%d bytes => %ld (0x%X))",
		 bio, argp, argi, ret, ret);
	tls_dump(NULL, argp, (int) ret);
    }
    return (ret);
}


/*
 * We need space to save the peername into the SSL_SESSION, as we must
 * look up the external database for client sessions by peername, not
 * by session id. We therefore allocate place for the peername string,
 * when a new SSL_SESSION is generated. It is filled later.
 */
static int new_peername_func(void *parent, void *ptr,
			     CRYPTO_EX_DATA *ad,
			     int idx, long argl, void *argp)
{
    char *peername;

    peername = (char *)malloc(id_maxlength + 1);
    if (!peername)
	return 0;
    peername[0] = '\0'; 	/* initialize */
    return CRYPTO_set_ex_data(ad, idx, peername);
}

/*
 * When the SSL_SESSION is removed again, we must free the memory
 * to avoid leaks.
 */
static void free_peername_func(void *parent, void *ptr,
			       CRYPTO_EX_DATA *ad,
			       int idx, long argl, void *argp)
{
    free(CRYPTO_get_ex_data(ad, idx));
}

/*
 * Duplicate application data, when a SSL_SESSION is duplicated
 */
static int dup_peername_func(CRYPTO_EX_DATA *to,
			     CRYPTO_EX_DATA *from,
			     void *from_d, int idx,
			     long argl, void *argp)
{
    char *peername_old, *peername_new;

    peername_old = CRYPTO_get_ex_data(from, idx);
    peername_new = CRYPTO_get_ex_data(to, idx);
    if (!peername_old || !peername_new)
	return 0;
    memcpy(peername_new, peername_old, id_maxlength + 1);
    return 1;
}


#if 0 /* some day */
static SSL_SESSION *load_clnt_session(const char *hostname,
				      int enforce_peername)
{
    SSL_SESSION *session = NULL;
    int n;
    int uselength;
    int length;
    int hex_length;
    const char *session_hex;
    pfixtls_scache_info_t scache_info;
    unsigned char nibble, *data, *sess_data;
    char *idstring[ID_MAXLENGTH + 1]; /* ALLOCA!! */

    length = strlen(hostname); 
    if (length > id_maxlength)
	uselength = id_maxlength;	/* Limit length of ID */
    else
	uselength = length;

    for(n=0 ; n < uselength ; n++)
	idstring[n] = tolower(hostname[n]);
    idstring[uselength] = '\0';
    if (var_smtp_tls_loglevel >= 3)
	msg_info("Trying to reload Session from disc: %s", idstring);

    session_hex = dict_get(scache_db, idstring);
    if (session_hex) {
	hex_length = strlen(session_hex);
	data = (unsigned char *)mymalloc(hex_length / 2);
	if (!data) {
	    msg_info("could not allocate memory for session reload");
	    return(NULL);
	}

	memset(data, 0, hex_length / 2);
	for (n = 0; n < hex_length; n++) {
	    if ((session_hex[n] >= '0') && (session_hex[n] <= '9'))
		nibble = session_hex[n] - '0';
	    else
		nibble = session_hex[n] - 'A' + 10;
	    if (n % 2)
		data[n / 2] |= nibble;
	    else
		data[n / 2] |= (nibble << 4);
	}

	/*
	 * First check the version numbers, since wrong session data might
	 * hit us hard (SEGFAULT). We also have to check for expiry.
	 * When we enforce_peername, we may find an old session, that was
	 * saved when enforcement was not set. In this case the session will
	 * be removed and a fresh session will be negotiated.
	 */
	memcpy(&scache_info, data, sizeof(pfixtls_scache_info_t));
	if ((scache_info.scache_db_version != scache_db_version) ||
	    (scache_info.openssl_version != openssl_version) ||
	    (scache_info.timestamp + var_smtpd_tls_scache_timeout < time(NULL)))
	    dict_del(scache_db, idstring);
	else if (enforce_peername && (!scache_info.enforce_peername))
	    dict_del(scache_db, idstring);
	else {
	    sess_data = data + sizeof(pfixtls_scache_info_t);
	    session = d2i_SSL_SESSION(NULL, &sess_data,
				      hex_length / 2 - sizeof(time_t));
	    strncpy(SSL_SESSION_get_ex_data(session, TLSpeername_index),
		    idstring, id_maxlength + 1);
	    if (!session)
		pfixtls_print_errors();
	}
	free((char *)data);
    }

    if (session && (var_smtp_tls_loglevel >= 3))
        msg_info("Successfully reloaded session from disc");

    return (session);
}


static void create_client_lookup_id(char *idstring, char *hostname)
{
    int n, len, uselength;

    len = strlen(hostname);
    if (len > id_maxlength)
	uselength = id_maxlength;	/* Limit length of ID */
    else
	uselength = len;

    for (n = 0 ; n < uselength ; n++)
	idstring[n] = tolower(hostname[n]);
    idstring[uselength] = '\0';
}

/*
 * Save a new session to the external cache
 */
static int new_session_cb(SSL *ssl, SSL_SESSION *session)
{
  char idstring[2 * ID_MAXLENGTH + 1]; /* ALLOCA! */
    int n;
    int dsize;
    int len;
    unsigned char *data, *sess_data;
    pfixtls_scache_info_t scache_info;
    char *hexdata, *hostname;
    SmtpState *SS;

    if (tls_clientengine) {
        TLScontext = SSL_get_ex_data(ssl, TLScontext_index);
	hostname = SS->TLS.peername_save;
	create_client_lookup_id(idstring, hostname);
	strncpy(SSL_SESSION_get_ex_data(session, TLSpeername_index),
		hostname, id_maxlength + 1);
	/*
	 * Remember, whether peername matching was enforced when the session
	 * was created. If later enforce mode is enabled, we do not want to
	 * reuse a session that was not sufficiently checked.
	 */
	scache_info.enforce_peername =
		(SS->TLS.enforce_verify_errors && SS->TLS.enforce_CN);

	if (var_smtp_tls_loglevel >= 3)
	    msg_info(SS,"Trying to save session for hostID to disc: %s", idstring);

#if (OPENSSL_VERSION_NUMBER < 0x00906011L) || (OPENSSL_VERSION_NUMBER == 0x00907000L)
	    /*
	     * Ugly Hack: OpenSSL before 0.9.6a does not store the verify
	     * result in sessions for the client side.
	     * We modify the session directly which is version specific,
	     * but this bug is version specific, too.
	     *
	     * READ: 0-09-06-01-1 = 0-9-6-a-beta1: all versions before
	     * beta1 have this bug, it has been fixed during development
	     * of 0.9.6a. The development version of 0.9.7 can have this
	     * bug, too. It has been fixed on 2000/11/29.
	     */
	    session->verify_result = SSL_get_verify_result(SS->TLS.con);
#endif

    } else {
	create_server_lookup_id(idstring, session);
	if (var_smtpd_tls_loglevel >= 3)
	    msg_info(SS,"Trying to save Session to disc: %s", idstring);
    }


    /*
     * Get the session and convert it into some "database" useable form.
     * First, get the length of the session to allocate the memory.
     */
    dsize = i2d_SSL_SESSION(session, NULL);
    if (dsize < 0) {
	msg_info(SS,"Could not access session");
	return 0;
    }
    data = (unsigned char *)mymalloc(dsize + sizeof(pfixtls_scache_info_t));
    if (!data) {
	msg_info(SS,"could not allocate memory for SSL session");
	return 0;
    }

    /*
     * OpenSSL is not robust against wrong session data (might SEGFAULT),
     * so we secure it against version ids (session cache structure as well
     * as OpenSSL version).
     */
    scache_info.scache_db_version = scache_db_version;
    scache_info.openssl_version = openssl_version;

    /*
     * Put a timestamp, so that expiration can be checked without
     * analyzing the session data itself. (We would need OpenSSL funtions,
     * since the SSL_SESSION is a private structure.)
     */
    scache_info.timestamp = time(NULL);

    memcpy(data, &scache_info, sizeof(pfixtls_scache_info_t));
    sess_data = data + sizeof(pfixtls_scache_info_t);

    /*
     * Now, obtain the session. Unfortunately, it is binary and dict_update
     * cannot handle binary data (it could contain '\0' in it) directly.
     * To save memory we could use base64 encoding. To make handling easier,
     * we simply use hex format.
     */
    len = i2d_SSL_SESSION(session, &sess_data);
    len += sizeof(pfixtls_scache_info_t);

    hexdata = (char *)mymalloc(2 * len + 1);

    if (!hexdata) {
	msg_info(SS,"could not allocate memory for SSL session (HEX)");
	free((char *)data);
	return 0;
    }
    for (n = 0; n < len; n++) {
	hexdata[n * 2] = hexcodes[(data[n] & 0xf0) >> 4];
	hexdata[(n * 2) + 1] = hexcodes[(data[n] & 0x0f)];
    }
    hexdata[len * 2] = '\0';

    /*
     * The session id is a hex string, all uppercase. We are using SDBM as
     * compiled into Postfix with 8kB maximum entry size, so we set a limit
     * when caching. If the session is not cached, we have to renegotiate,
     * not more, not less. For a real session, this limit should never be
     * met
     */
    if (strlen(idstring) + strlen(hexdata) < 8000)
      dict_put(scache_db, idstring, hexdata);

    free(hexdata);
    free((char *)data);
    return (1);
}
#endif



static int tls_randseeder(const char *source)
{
	int rand_bytes;
	unsigned char buffer[255];

	int var_tls_rand_bytes = 255;
	
	/*
	 * Access the external sources for random seed. We may not be able to
	 * access them again if we are sent to chroot jail, so we must leave
	 * dev: and egd: type sources open.
	 */

	if (source && *source) {
	  if (!strncmp(source, "dev:", 4)) {

	    /*
	     * Source is a random device
	     */
	    int fd = open(source + 4, 0, 0);
	    if (fd < 0)     return -2;
	    if (var_tls_rand_bytes > 255)
	      var_tls_rand_bytes = 255;
	    rand_bytes = read(fd, buffer, var_tls_rand_bytes);
	    close(fd);

	    RAND_seed(buffer, rand_bytes);

	  } else if (!strncmp(source, "egd:", 4)) {
	    /*
	     * Source is a EGD compatible socket
	     */
	    struct sockaddr_un un;
	    int rc;
	    int fd = socket(PF_UNIX, SOCK_STREAM, 0);

	    if (fd < 0) return -1; /* URGH.. */

	    memset(&un, 0, sizeof(un));
	    un.sun_family = AF_UNIX;
	    strncpy(un.sun_path, source+4, sizeof(un.sun_path));
	    un.sun_path[sizeof(un.sun_path)-1] = 0;
	    for (;;) {
	      rc = connect(fd, (struct sockaddr *)&un, sizeof(un));
	      if (rc < 0 && (errno == EWOULDBLOCK || errno == EINTR || errno == EINPROGRESS))
		continue;
	      break;
	    }

	    if (rc < 0) {
	      close(fd);
	      return -2;
	    }
	    if (var_tls_rand_bytes > 255)
	      var_tls_rand_bytes = 255;

	    buffer[0] = 1;
	    buffer[1] = var_tls_rand_bytes;

	    if (write(fd, buffer, 2) != 2) {
	      close(fd);
	      return -3;
	    }

	    if (read(fd, buffer, 1) != 1) {
	      close(fd);
	      return -4;
	    }

	    rand_bytes = buffer[0];
	    rc = read(fd, buffer, rand_bytes);
	    close(fd);

	    if (rc != rand_bytes)
	      return -5;

	    RAND_seed(buffer, rand_bytes);

	  } else {
	    rand_bytes = RAND_load_file(source, var_tls_rand_bytes);
	  }
	} else
	  return -99; /* Bad call! */

	return 0; /* Success.. */
}


 /*
  * This is the setup routine for the SSL client. As smtpd might be called
  * more than once, we only want to do the initialization one time.
  *
  * The skeleton of this function is taken from OpenSSL apps/s_client.c.
  */

int     tls_init_clientengine(SS, cfgpath)
     SmtpState *SS;
     char *cfgpath;
{
	int     off = 0;
	int     verify_flags = SSL_VERIFY_NONE;
	char	  buf[1024], *n, *a1;

	int	linenum = 0;
	FILE   *fp;
	unsigned char *s;

	const char   *CApath;
	const char   *CAfile;
	const char   *c_cert_file;
	const char   *c_key_file;
	const char   *c_dcert_file;
	const char   *c_dkey_file;


	vlog = SS->verboselog;

	if (tls_clientengine) return 0;		/* already running */

	if (SS->TLS.sslmode)
	  return (0);				/* already running */

	fp = fopen(cfgpath,"r");
	if (!fp) {
	  msg_info(SS, "Can't read TLS config file: '%s'",cfgpath);
	  return -1;
	}
	while (!feof(fp) && !ferror(fp)) {
	  if (!fgets(buf, sizeof(buf), fp))
	    break;
	  ++linenum;
	  s = (void*) strchr(buf, '\n');
	  if (s) *s = 0;
	  s = (void*) buf;
	  
#define SKIPSPACE(Y) while (*Y == ' ' || *Y == '\t' || *Y == '\n') ++Y
#define SKIPTEXT(Y ) while (*Y && !(*Y == ' ' || *Y == '\t' || *Y == '\n')) ++Y
	  
	  SKIPSPACE(s);
	  if (!*s || *s == '#' || *s == ';')
	    continue; /* First non-whitespace char is comment start (or EOL) */
	  
	  SKIPSPACE(s);
	  n = (char *)s;
	  SKIPTEXT(s);
	  if (*s) *s++ = 0;
	  
	  SKIPSPACE(s);
	  a1 = (char *)s;
	  SKIPTEXT(s);
	  if (*s) *s++ = 0;
	  

	  if        (strcasecmp(n, "tls-cert-file") == 0 && a1) {
	    tls_cert_file = strdup(a1);
	  } else if (strcasecmp(n, "tls-key-file") == 0  && a1) {
	    tls_key_file = strdup(a1);
	  } else if (strcasecmp(n, "tls-dcert-file") == 0 && a1) {
	    tls_dcert_file = strdup(a1);
	  } else if (strcasecmp(n, "tls-dkey-file") == 0  && a1) {
	    tls_dkey_file = strdup(a1);
	  } else if (strcasecmp(n, "tls-CAfile") == 0     && a1) {
	    tls_CAfile = strdup(a1);
	  } else if (strcasecmp(n, "tls-CApath") == 0     && a1) {
	    tls_CApath = strdup(a1);
	  } else if (strcasecmp(n, "tls-loglevel") == 0   && a1) {
	    tls_loglevel = atol(a1);
	    if (tls_loglevel < 0) tls_loglevel = 0;
	    if (tls_loglevel > 4) tls_loglevel = 4;
	  } else if (strcasecmp(n, "tls-strict-rfc2487") == 0) {
	    tls_protocol_tlsv1_only = 1;
	  } else if (strcasecmp(n, "tls-use-scache") == 0) {
	    tls_use_scache = 1;
	  } else if (strcasecmp(n, "tls-random-source") == 0 && a1) {
	    tls_random_source = strdup(a1);
	  } else if (strcasecmp(n, "tls-cipher-list") == 0 && a1) {
	    tls_cipherlist = strdup(a1);
	  } else if (strcasecmp(n, "tls-scache-timeout") == 0 && a1) {
	    tls_scache_timeout = atol(a1);
	    if (tls_loglevel < 0) tls_loglevel = 0;
	    if (tls_loglevel > 4) tls_loglevel = 4;
	  } else if (strcasecmp(n, "demand-tls-mode") == 0) {
	    demand_TLS_mode = 1;
	  } else if (strcasecmp(n, "no-tls-readahead") == 0) {
	    tls_use_read_ahead = 0;
	  } else {
	    sfprintf(sfstderr,"# TLS config file, line %d verb: '%s' unknown or missing parameters!\n",
		     linenum, n);
	  }
	}
	fclose(fp);


	/*
	 * Initialize the OpenSSL library by the book!
	 * To start with, we must initialize the algorithms.
	 * We want cleartext error messages instead of just error codes, so we
	 * load the error_strings.
	 */ 
	SSL_load_error_strings();
	SSLeay_add_ssl_algorithms();

#if (OPENSSL_VERSION_NUMBER < 0x00905100L)
	/*
	 * Side effect, call a non-existing function to disable TLS usage with
	 * an outdated OpenSSL version. There is a security reason
	 * (verify_result is not stored with the session data).
	 */
	needs_openssl_095_or_later();
#endif

	/* Lets try to do RAND-pool initing.. */
	/* We don't loop, we bail-out from loop-contruction
	   enclosed alternate codes */


	/*
	 * Initialize the PRNG Pseudo Random Number Generator with some seed.
	 */
	tls_randseed.pid  = getpid();
	tls_randseed.ppid = getppid();
	gettimeofday(&tls_randseed.tv, NULL);
	RAND_seed(&tls_randseed, sizeof(tls_randseed));


	/*
	 * Access the external sources for random seed. We will only query
	 * them once, this should be sufficient and we will stir our entropy
	 * by using the prng-exchange file anyway.
	 * For reliability, we don't consider failure to access the additional
	 * source fatal, as we can run happily without it (considering that we
	 * still have the exchange-file). We also don't care how much entropy
	 * we get back, as we must run anyway. We simply stir in the buffer
	 * regardless how many bytes are actually in it.
	 */
	while ( 1 ) {

	  /* Parametrized version ? */
	  if (tls_random_source && tls_randseeder(tls_random_source) >= 0)
	    break;
	  
	  /* How about  /dev/urandom  ?  */
	  if (tls_randseeder("dev:/dev/urandom") >= 0) break;

	  /* How about  EGD at /var/run/egd-seed  ?  */
	  if (tls_randseeder("egd:/var/run/egd-pool") >= 0) break;

	  break;
	}

	/*
	 * Some more seeding...
	 */
	tls_randseed.pid = getpid();
	tls_randseed.ppid = getppid();
	gettimeofday(&tls_randseed.tv, NULL);
	RAND_seed(&tls_randseed, sizeof(tls_randseed));


	if (tls_loglevel >= 2)
	  msg_info(SS, "starting TLS engine");

	/*
	 * The SSL/TLS speficications require the client to send
	 * a message in the oldest specification it understands with
	 * the highest level it understands in the message.
	 * RFC2487 is only specified for TLSv1, but we want to be
	 * as compatible as possible, so we will start off with
	 * a SSLv2 greeting allowing the best we can offer: TLSv1.
	 * We can restrict this with the options setting later, anyhow.
	 */

	if (tls_protocol_tlsv1_only)
	  SS->TLS.ctx = SSL_CTX_new(TLSv1_client_method());
	else
	  SS->TLS.ctx = SSL_CTX_new(SSLv23_client_method());

	if (! SS->TLS.ctx) {
	  tls_print_errors(SS);
	  return (-1);
	}

	/*
	 * Here we might set SSL_OP_NO_SSLv2, SSL_OP_NO_SSLv3, SSL_OP_NO_TLSv1.
	 * Of course, the last one would not make sense, since RFC2487 is only
	 * defined for TLS, but we donīt know what is out there. So leave 
	 * things completely open, as of today.
	 */
	off = SSL_OP_ALL;		/* Work around all known bugs */
	if (tls_protocol_tlsv1_only)
	  off |= (SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);
	SSL_CTX_set_options(SS->TLS.ctx, off);

	/*
	 * Set the info_callback, that will print out messages during
	 * communication on demand.
	 */
	SSL_CTX_set_info_callback(SS->TLS.ctx, apps_ssl_info_callback);

	/*
	 * Set the list of ciphers, if explicitely given; otherwise the
	 * (reasonable) default list is kept.
	 */
	if (tls_cipherlist) {
	  if (SSL_CTX_set_cipher_list(SS->TLS.ctx, tls_cipherlist) == 0) {
	    tls_print_errors(SS);
	    return (-1);
	  }
	}

  
	/*
	 * Now we must add the necessary certificate stuff: A client key,
	 * a client certificate, and the CA certificates for both the client
	 * cert and the verification of server certificates.
	 * In fact, we do not need a client certificate,  so the certificates
	 * are only loaded (and checked), if supplied. A clever client would
	 * handle multiple client certificates and decide based on the list
	 * of acceptable CAs, sent by the server, which certificate to submit.
	 * OpenSSL does however not do this and also has no callback hoods to
	 * easily realize it.
	 *
	 * As provided by OpenSSL we support two types of CA certificate
	 * handling:  One possibility is to add all CA certificates to
	 * one large CAfile,  the other possibility is a directory pointed
	 * to by CApath, containing seperate files for each CA pointed on
	 * by softlinks named by the hash values of the certificate.
	 * The first alternative has the advantage, that the file is opened and
	 * read at startup time, so that you donīt have the hassle to maintain
	 * another copy of the CApath directory for chroot-jail. On the other
	 * hand, the file is not really readable.
	 */ 

	if (!tls_CAfile || *tls_CAfile == 0)
	  CAfile = NULL;
	else
	  CAfile = tls_CAfile;
	if (!tls_CApath || *tls_CApath == 0)
	  CApath = NULL;
	else
	  CApath = tls_CApath;

	if (CAfile || CApath)
	  if ((!SSL_CTX_load_verify_locations(SS->TLS.ctx, CAfile, CApath)) ||
	      (!SSL_CTX_set_default_verify_paths(SS->TLS.ctx))) {
	    msg_info(SS, "TLS engine: cannot load CA data");
	    tls_print_errors(SS);
	    return (-1);
	  }
	
	if (!tls_cert_file || *tls_cert_file == 0)
	  c_cert_file = NULL;
	else
	  c_cert_file = tls_cert_file;
	if (!tls_key_file || *tls_key_file == 0)
	  c_key_file = NULL;
	else
	  c_key_file = tls_key_file;

	if (c_cert_file || c_key_file) {
	  if (!set_cert_stuff(SS, c_cert_file, c_key_file)) {
	    msg_info(SS, "TLS engine: cannot load cert/key data");
	    tls_print_errors(SS);
	    return (-1);
	  }
	}

	if (!tls_dcert_file || *tls_dcert_file == 0)
	  c_dcert_file = NULL;
	else
	  c_dcert_file = tls_dcert_file;
	if (!tls_dkey_file || *tls_dkey_file == 0)
	  c_dkey_file = NULL;
	else
	  c_dkey_file = tls_dkey_file;

	if (c_dcert_file || c_dkey_file) {
	  if (!set_cert_stuff(SS, c_dcert_file, c_dkey_file)) {
	    msg_info(SS, "TLS engine: cannot load dcert/dkey data");
	    tls_print_errors(SS);
	    return (-1);
	  }
	}

	/*
	 * Sometimes a temporary RSA key might be needed by the OpenSSL
	 * library. The OpenSSL doc indicates, that this might happen when
	 * export ciphers are in use. We have to provide one, so well, we
	 * just do it.
	 */

	SSL_CTX_set_tmp_rsa_callback(SS->TLS.ctx, tmp_rsa_cb);

	/*
	 * Finally, the setup for the server certificate checking, done
	 * "by the book".
	 */

	SSL_CTX_set_verify(SS->TLS.ctx, verify_flags, verify_callback);

#if 0
	/*
	 * Initialize the session cache. We only want external caching to
	 * synchronize between server sessions, so we set it to a minimum value
	 * of 1. If the external cache is disabled, we wonīt cache at all.
	 *
	 * In case of the client, there is no callback used in OpenSSL,
	 * so we must call the session cache functions manually during
	 * the process.
	 */
	SSL_CTX_sess_set_cache_size(SS->TLS.ctx, 1);
	SSL_CTX_set_timeout(SS->TLS.ctx, tls_scache_timeout);
#endif

#if 0
	/*
	 * The session cache is realized by an external database file, that
	 * must be opened before going to chroot jail. Since the session cache
	 * data can become quite large, "[n]dbm" cannot be used as it has a
	 * size limit that is by far to small.
	 */
	if (*var_smtp_tls_scache_db) {
	  /*
	   * Insert a test against other dbms here, otherwise while writing
	   * a session (content to large), we will receive a fatal error!
	   */
	  if (strncmp(var_smtp_tls_scache_db, "sdbm:", 5))
	    msg_warn("Only sdbm: type allowed for %s",
		     var_smtp_tls_scache_db);
	  else
	    scache_db = dict_open(var_smtp_tls_scache_db, O_RDWR,
				  ( DICT_FLAG_DUP_REPLACE | DICT_FLAG_LOCK |
				    DICT_FLAG_SYNC_UPDATE ));
	  if (!scache_db)
	    msg_warn("Could not open session cache %s",
		     var_smtp_tls_scache_db);
	  /*
	   * It is practical to have OpenSSL automatically save newly created
	   * sessions for us by callback. Therefore we have to enable the
	   * internal session cache for the client side. Disable automatic
	   * clearing, as smtp has limited lifetime anyway and we can call
	   * the cleanup routine at will.
	   */
	  SSL_CTX_set_session_cache_mode(ctx,
					 ( SSL_SESS_CACHE_CLIENT |
					   SSL_SESS_CACHE_NO_AUTO_CLEAR ));
	  SSL_CTX_sess_set_new_cb(ctx, new_session_cb);
	}
#endif

	/*
	 * Finally create the global index to access TLScontext information
	 * inside verify_callback.
	 */
	TLScontext_index = SSL_get_ex_new_index(0, "TLScontext ex_data index",
						NULL, NULL, NULL);
	TLSpeername_index = SSL_SESSION_get_ex_new_index(0,
							 "TLSpeername ex_data index",
							 new_peername_func,
							 dup_peername_func,
							 free_peername_func);

	tls_clientengine = 1;


	return (0);
}

 /*
  * This is the actual startup routine for the connection. We expect
  * that the buffers are flushed and the "220 Ready to start TLS" was
  * received by us, so that we can immediately can start the TLS
  * handshake process.
  */
int     tls_start_clienttls(SS, peername) /* XX: enforce-peername ? */
     SmtpState *SS;
     const char *peername;
{
    int     sts;
    SSL_SESSION *session, *old_session;
    SSL_CIPHER *cipher;
    X509   *peer;
    int     verify_flags;
    char cbuf[4000];

    vlog = SS->verboselog;	/* Grr.. global for BIO dump.	*/

    if (!tls_available) {	/* should never happen		*/
	msg_info(SS, "tls_engine not running");
	alarm(0);
	return (-1);
    }
    if (tls_loglevel >= 1)
	msg_info(SS, "setting up TLS connection");

    /*
     * If necessary, setup a new SSL structure for a connection.
     * We keep old ones on closure, so it might not be always
     * necessary. We however reset the old one, just in case.
     */

    if (SS->TLS.ssl != NULL) {
      SSL_clear(SS->TLS.ssl);
    } else {
      SS->TLS.ssl = SSL_new(SS->TLS.ctx);
      if (! SS->TLS.ssl) {
	msg_info(SS, "Could not allocate 'con' with SSL_new()");
	tls_print_errors(SS);
	alarm(0);
	return (-1);
      }
    }

    /*
     * Add the location of TLS-context to the SSL to later
     * retrieve the information inside the verify_callback().
     */

    if (!SSL_set_ex_data(SS->TLS.ssl, TLScontext_index, SS)) {
      msg_info(SS, "Could not set application data for 'SS->TLS.ssl'");
      tls_print_errors(SS);
      tls_stop_clienttls(SS, 1);
      return (-1);
    }



    old_session = NULL;	/* make sure no old info is kept */

    /*
     * Set the verification parameters to be checked in verify_callback().
     */
    if (tls_enforce_peername) {
      verify_flags = SSL_VERIFY_PEER;
      SS->TLS.enforce_verify_errors = 1;
      SS->TLS.enforce_CN = 1;
      SSL_set_verify(SS->TLS.ssl, verify_flags, verify_callback);
    } else {
      SS->TLS.enforce_verify_errors = 0;
      SS->TLS.enforce_CN = 0;
    }

    /*
     * Now, connect the filedescripter set earlier to the SSL connection
     * (this is for clean UNIX environment, for example windows "sockets"
     *  need somewhat different approach with customized BIO_METHODs.)
     */
    if (!SSL_set_fd(SS->TLS.ssl, sffileno(SS->smtpfp))) {
	msg_info(SS, "SSL_set_fd failed");
	tls_print_errors(SS);
	alarm(0);
	return (-1);
    }


    /*
     * Find out the hashed HostID for the client cache and try to
     * load the session from the cache.
     */
    SS->TLS.peername_save = strdup(peername);

#if 0 /* some day.. */
    if (scache_db) {
      old_session = load_clnt_session(peername, enforce_peername);
      if (old_session) {
	SSL_set_session(SS->TLS.con, old_session);
#if (OPENSSL_VERSION_NUMBER < 0x00906011L) || (OPENSSL_VERSION_NUMBER == 0x00907000L)
	/*
	 * Ugly Hack: OpenSSL before 0.9.6a does not store the verify
	 * result in sessions for the client side.
	 * We modify the session directly which is version specific,
	 * but this bug is version specific, too.
	 *
	 * READ: 0-09-06-01-1 = 0-9-6-a-beta1: all versions before
	 * beta1 have this bug, it has been fixed during development
	 * of 0.9.6a. The development version of 0.9.7 can have this
	 * bug, too. It has been fixed on 2000/11/29.
	 */
	SSL_set_verify_result(SS->TLS.con, old_session->verify_result);
#endif
	   
      }
    }
#endif

#if 0 /* Some day... */
    /*
     * Before really starting anything, try to seed the PRNG a little bit
     * more.
     */
    pfixtls_stir_seed();
    pfixtls_exchange_seed();
#endif

#if 0
    /*
     * Initialize the SSL connection to connect state. This should not be
     * necessary anymore since 0.9.3, but the call is still in the library
     * and maintaining compatibility never hurts.
     */
    SSL_set_connect_state(SS->TLS.ssl);
#endif

    /*
     * If the debug level selected is high enough, all of the data is
     * dumped: 3 will dump the SSL negotiation, 4 will dump everything.
     *
     * We do have an SSL_set_fd() and now suddenly a BIO_ routine is called?
     * Well there is a BIO below the SSL routines that is automatically
     * created for us, so we can use it for debugging purposes.
     */
    if (tls_loglevel >= 3)
	BIO_set_callback(SSL_get_rbio(SS->TLS.ssl), bio_dump_cb);

    /* Dump the negotiation for loglevels 3 and 4 */
    if (tls_loglevel >= 3)
	do_dump = 1;


    /*
     * Now we expect the negotiation to begin.
     * This whole process is like a black box for us.
     * We totally have to rely on the routines build
     * into the OpenSSL library.
     * The only thing we can do we already have done
     * by choosing our own callback certificate verification.
     *
     * Error handling:
     * If the SSL handhake fails, we print out an error message
     * and remove everything that might be there.
     * A session has to be removed anyway, because RFC2246 requires it. 
     */

    sts = do_tls_operation(SS, timeout_tcpw, SSL_connect, "connect");
    if (sts <= 0) {
      session = SSL_get_session(SS->TLS.ssl);
      if (session) {
	SSL_CTX_remove_session(SS->TLS.ctx, session);
	if (tls_loglevel >= 2)
	  msg_info(SS, "SSL session removed");
      }
      if (old_session && (!SSL_session_reused(SS->TLS.ssl)))
	SSL_SESSION_free(old_session); /* Must also be removed */
      
      tls_stop_clienttls(SS, 1);
      
      alarm(0);
    }

    if (!SSL_session_reused(SS->TLS.ssl)) {
      SSL_SESSION_free(old_session);	/* Remove unused session */
    } else if (tls_loglevel >= 3)
      msg_info(SS,"Reusing old session");


    /* Only loglevel==4 dumps everything */
    if (tls_loglevel < 4)
	do_dump = 0;

    /*
     * Lets see, whether a peer certificate is available and what is
     * the actual information. We want to save it for later use.
     */
    peer = SSL_get_peer_certificate(SS->TLS.ssl);
    if (peer != NULL) {
	if (SSL_get_verify_result(SS->TLS.ssl) == X509_V_OK)
	    SS->TLS.peer_verified = 1;

	cbuf[0] = 0;
	if (!X509_NAME_get_text_by_NID(X509_get_subject_name(peer),
				       NID_commonName,
				       cbuf, sizeof(cbuf))) {
	  msg_info(SS,"Could not parse server's subject CN");
	  tls_print_errors(SS);
	} else
	  SS->TLS.peer_CN = strdup(cbuf);

	cbuf[0] = 0;
	if (!X509_NAME_oneline(X509_get_subject_name(peer),
			       cbuf, sizeof(cbuf))) {
	  msg_info(SS,"Could not parse server's subject CN into oneline");
	  tls_print_errors(SS);
	} else
	  SS->TLS.peer_CN1 = strdup(cbuf);


	cbuf[0] = '\0';
	if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peer),
				       NID_commonName, cbuf, sizeof(cbuf))) {
	  msg_info(SS,"Could not parse server's issuer CN");
	  tls_print_errors(SS);
	}
	if (!cbuf[0]) {
	  /* No issuer CN field, use Organization instead */
	  if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peer),
					 NID_organizationName, 
					 cbuf, sizeof(cbuf))) {
	    msg_info(SS,"Could not parse server's issuer Organization");
	    tls_print_errors(SS);
	  }
	}
	if (cbuf[0])
	  SS->TLS.issuer_CN = strdup(cbuf);


	cbuf[0] = 0;
	if (!X509_NAME_oneline(X509_get_issuer_name(peer),
			       cbuf, sizeof(cbuf))) {
	  msg_info(SS,"Could not parse server's issuer CN into oneline");
	  tls_print_errors(SS);
	} else
	  SS->TLS.issuer_CN1 = strdup(cbuf);

	{
	  ASN1_TIME *tm1, *tm2;
	  tm1 = X509_get_notBefore(peer);
	  tm2 = X509_get_notAfter(peer);

	  SS->TLS.notBefore = zdupnstr(tm1->data, tm1->length);
	  SS->TLS.notAfter  = zdupnstr(tm2->data, tm2->length);
	}


	if (tls_loglevel >= 1) {
	    if (SS->TLS.peer_verified)
	      msg_info(SS,"Verified: subject_CN=%s, issuer=%s",
		       SS->TLS.peer_CN1 ? SS->TLS.peer_CN1 : "",
		       SS->TLS.issuer_CN1 ? SS->TLS.issuer_CN1 : "");
	    else
	      msg_info(SS,"Unverified: subject_CN=%s, issuer=%s",
		       SS->TLS.peer_CN1 ? SS->TLS.peer_CN1 : "",
		       SS->TLS.issuer_CN1 ? SS->TLS.issuer_CN1 : "");
	}
	X509_free(peer);
    }


    /*
     * Finally, collect information about protocol and cipher for logging
     */ 
    SS->TLS.protocol       = SSL_get_version(SS->TLS.ssl);
    cipher = SSL_get_current_cipher(SS->TLS.ssl);
    SS->TLS.cipher_name    = SSL_CIPHER_get_name(cipher);
    SS->TLS.cipher_usebits = SSL_CIPHER_get_bits(cipher,
						 &SS->TLS.cipher_algbits);

    msg_info(SS, "TLS connection established: %s with cipher %s (%d/%d bits)",
	     SS->TLS.protocol, SS->TLS.cipher_name,
	     SS->TLS.cipher_usebits, SS->TLS.cipher_algbits);

    if (tls_use_read_ahead)
      SSL_set_read_ahead(SS->TLS.ssl, 1); /* Improves performance */

    /* Mark the mode! */
    SS->TLS.sslmode = 1;

    return (0);
}

/*
 * Shut down the TLS connection, that does mean:
 * remove all the information and reset the flags!
 * This is needed if the actual running smtp is to
 * be restarted. We do not give back any value, as
 * there is nothing to be reported.
 * Since our session cache is external, we will remove
 * the session from memory in any case. The SSL_CTX_flush_sessions
 * might be redundant here, I however want to make sure nothing is left.
 * RFC2246 requires us to remove sessions if something went wrong, as
 * indicated by the "failure" value,so we remove it from the external
 * cache, too.
 */
int     tls_stop_clienttls(SS, failure)
     SmtpState *SS;
     int failure;
{
	SSL_SESSION *session;
	int retval;

	vlog = SS->verboselog;

	if (SS->TLS.sslmode) {

	  session = SSL_get_session(SS->TLS.ssl);

	  /*
	   * Perform SSL_shutdown() twice, as the first attempt may
	   * return too early: it will only send out the shutdown
	   * alert but it will not wait for the peer's shutdown alert.
	   * Therefore, when we are the first party to send the alert,
	   * we must call SSL_shutdown() again.
	   * On failure we don't want to resume the session, so we will
	   * not perform SSL_shutdown() and the session will be removed
	   * as being bad.
	   */
	  if (!failure) {
	    retval = do_tls_operation(SS, timeout_tcpw,
				      SSL_shutdown, "shutdown");
	    if (retval == 0)
	      retval = do_tls_operation(SS, timeout_tcpw,
					SSL_shutdown, "shutdown");
	  }

	  /*
	   * Free the SSL structure and the BIOs.
	   * Warning: the internal_bio is connected to
	   * the SSL structure and is automatically freed
	   * with it. Do not free it again (core dump)!!
	   * Only free the network_bio.
	   */

#if 0
	  pfixtls_stir_seed();
	  pfixtls_exchange_seed();
#endif

	  if (SS->TLS.ssl) SSL_free(SS->TLS.ssl);
	  SS->TLS.ssl = NULL;

	  SSL_CTX_flush_sessions(SS->TLS.ctx, time(NULL));

	  SS->TLS.peer_verified  = 0;
	  SS->TLS.protocol       = NULL;
	  SS->TLS.cipher_name    = NULL;
	  SS->TLS.cipher_usebits = 0;
	  SS->TLS.cipher_algbits = 0;

#define ZCONDFREE(var) if (var) free((void*)(var))

	  ZCONDFREE(SS->TLS.peername_save);
	  ZCONDFREE(SS->TLS.peer_subject); /* server only ??? */
	  ZCONDFREE(SS->TLS.peer_issuer);
	  ZCONDFREE(SS->TLS.peer_fingerprint);
	  ZCONDFREE(SS->TLS.peer_CN);
	  ZCONDFREE(SS->TLS.peer_CN1);
	  ZCONDFREE(SS->TLS.issuer_CN);
	  ZCONDFREE(SS->TLS.issuer_CN1);
	  ZCONDFREE(SS->TLS.notBefore);
	  ZCONDFREE(SS->TLS.notAfter);
	}

	return (0);
}
#endif /* - HAVE_OPENSSL */


/* About timeouts the RFC 1123 recommends:
     - Initial 220: 5 minutes
     - MAIL, RCPT : 5 minutes
     - DATA initialization (until "354.."): 2 minutes
     - While writing data, a block
       at the time: 3 minutes  (How large a block ?)
     - From "." to "250 OK": 10 minutes
       (We use 60 minutes here - sendmail's default)
 */

extern int timeout;  /* That is the global setting.. */


/*
 * We have moved buffered writes to Sfio_t discipline function
 * smtp_sfwrite(), and now we can do timeouting properly in it..
 *
 * The write socket is ALWAYS in non-blocking mode!
 */
ssize_t smtp_sfwrite(sfp, vp, len, discp)
     Sfio_t *sfp;
     const void * vp;
     size_t len;
     Sfdisc_t *discp;
{
	struct smtpdisc *sd = (struct smtpdisc *)discp;
	SmtpState *SS = (SmtpState *)sd->SS;

	const char * p = (const char *)vp;
	int r, rr, e, i;

	vlog = SS->verboselog;

	/* If we have an errno status on a socket, it is extremely
	   persistent!  Absolutely no writes are allowed from now
	   on into this socket, and all write attempts will yield
	   the same errno value. */

	if (SS->lasterrno != 0) {
	  errno = SS->lasterrno;
	  return -1;
	}

	rr = -1; /* No successfull write */
	e = errno; /* Whatever the previous one was.. */

#if 1 /* Remove after debug tests */
	if (SS->verboselog)
	  fprintf(SS->verboselog,
		  " smtp_sfwrite() to write %d bytes\n", (int)len);
#endif
	
	if (sferror(sfp)) {  /* Don't even consider writing,
				if the stream has error status..
				Oddly this means the upper layers
				of sfio have failed somehow, and
				it has not affected the  "lasterrno"
				on SmtpState .. */

	  SS->lasterrno = errno = EIO;
	  return -1;
	}

	if (sffileno(sfp) < 0) { 	/* Write-FD killed!
					   (a sanity thing)
					   (One of those things
					   that should never happen..) */

	  SS->lasterrno = errno = EBADF;
	  return -1;
	}

	/* If 'len' is zero, return zero.. */
	/* (I have a feeling such writes are sometimes asked for..) */
	errno = 0;
	if (len == 0) return 0;

	errno = e;

	while (len > 0 && !sferror(sfp) && sffileno(sfp) >= 0) {

#ifdef HAVE_OPENSSL
	  if (SS->TLS.sslmode) {
	    r = SSL_write(SS->TLS.ssl, p, len);
	    e = SSL_get_error(SS->TLS.ssl, r);
	    switch (e) {
	    case SSL_ERROR_NONE:
	      SS->TLS.wantreadwrite =  0;
	      e = 0;
	      break;
	    case SSL_ERROR_WANT_READ:
	      SS->TLS.wantreadwrite = -1;
	      e = EAGAIN;
	      break;
	    case SSL_ERROR_WANT_WRITE:
	      SS->TLS.wantreadwrite = 1;
	      e = EAGAIN;
	      break;
	    case SSL_ERROR_SSL:
	      e = EPROTO;
	    default:
	      SS->TLS.wantreadwrite = 0;
	      break;
	    }
	  } else
#endif /* - HAVE_OPENSSL */
	    {
	      r = write(sffileno(sfp), p, len);
	      e = errno;
	    }

	  if (r >= 0) {
	    if (rr < 0) rr = 0;	/* something successfull. init this!   */
	    rr  += r;		/* Accumulate writeout accounting      */
	    p   += r;		/* move pointer			       */
	    len -= r;		/* count down the length to be written */
	    continue;
	  }

	  /* Hmm..  Write bounced for some reason */
	  switch (e) {
	  case EINTR:
	  case EAGAIN:
#ifdef EWOULDBLOCK
#if EWOULDBLOCK != EAGAIN
	  case EWOULDBLOCK:
#endif
#endif
	    {
	      /* Write blocked, lets select (and sleep) for write.. */
	      static struct zmpollfd *fds = NULL;
	      int fdscount = 0;
	      struct timeval tv, t0;

#if 1 /* Remove after debug tests */
	      if (SS->verboselog)
		gettimeofday(&t0, NULL);
#endif

	      i = sffileno(sfp);

#ifdef HAVE_OPENSSL
	      if (SS->TLS.sslmode) {
		if (SS->TLS.wantreadwrite < 0)
		  zmpoll_addfd(&fds, &fdscount, i, -1, NULL); /* WANT READ */
		else if (SS->TLS.wantreadwrite > 0)
		  zmpoll_addfd(&fds, &fdscount, -1, i, NULL); /* WANT WRITE */
		else
		  ; /* FIXME! What???  No reading, nor writing ??? */
	      } else
#endif /* - HAVE_OPENSSL */
		{
		  zmpoll_addfd(&fds, &fdscount, -1, i, NULL); /* WANT WRITE */
		}

	      tv.tv_sec = timeout_tcpw;

	      errno = 0;
	      r = zmpoll(fds, fdscount, tv.tv_sec * 1000);
	      e = errno;

#if 1 /* Remove after debug tests */
	      if (SS->verboselog) {
		struct timeval t2;

		gettimeofday(&t2, NULL);

		t2.tv_usec -= t0.tv_usec;
		if (t2.tv_usec < 0) {
		  t2.tv_usec += 1000000;
		  t2.tv_sec  -= 1;
		}
		t2.tv_sec -= t0.tv_sec;
		fprintf(SS->verboselog,
			" smtp_sfwrite() did select; rc=%d errno=%d len=%d dt=%d.%06d\n",
			r, e, (int)len, (int)t2.tv_sec, (int)t2.tv_usec);
	      }
#endif

	      if (r > 0)
		/* Ready to write! */
		break;

	      if (r == 0) {
		/* TIMEOUT!  Uarrgh!! */
		gotalarm = 1; 
		sfp->flags |= SF_ERROR; /* Ensure the error treatment.. */

		/* Actually KILL the outbound stream here! */

		if (sffileno(SS->smtpfp) >= 0) {
		  /* Error on write stream, write is thus from now on
		     FORBIDDEN!  We do a write direction shutdown on
		     the socket, and only listen for replies from now on... */
#ifdef HAVE_OPENSSL
		  if (SS->TLS.sslmode) {
		    /* SSL mode on, kill it completely... */
		    close(sffileno(SS->smtpfp));
		  } else
#endif /* - HAVE_OPENSSL */
		    shutdown(sffileno(SS->smtpfp), 1);
		  /* Absolutely NO SFIO SYNC AT THIS POINT! */
		  zsfsetfd(SS->smtpfp, -1);
		  SS->writeclosed = 1;
		  if (SS->verboselog)
		    fprintf(SS->verboselog,
			    "   ...  TIMEOUT! Shut-down of write direction!\n");
		}

		e = ETIMEDOUT;


zsyslog((LOG_ERR,
	 "%s: ERROR: SMTP socket write timeout; leftover=%d; IP=[%s] mx=%d/%d\n",
	 SS->taspoolid, (int)len, SS->ipaddress, SS->firstmx, SS->mxcount));

		break;
	      }

	      /* Khrm... */
	      /* Select error status is sent out */
	    }
	    break;
	  default:
	    /* Any other errno.. */
	    break;
	  }

	  /* If STILL a error, break out -- will retry EINTR at  Sfio  library. */
	  if (r < 0) break;

	} /* End of while(len > 0) loop */

	if (rr < 0) SS->lasterrno = e;
	errno = e;
	return rr;
}

/*
 *  Our callers are doing all timeout processing all by themselves.
 *  Indeed this call is always executed on a non-blocking socket!
 */
int smtp_nbread(SS, buf, spc)
     SmtpState *SS;
     void *buf;
     int spc;
{
	int r, e;
	int infd = SS->smtpfd;


#if 0
	/* Flush outputs just in case the remote is stuck waiting
	   for us.. */
	if (SS->smtpfp && sffileno(SS->smtpfp) >= 0) sfsync(SS->smtpfp);
#endif

	vlog = SS->verboselog;

#ifdef HAVE_OPENSSL
	if (SS->TLS.sslmode) {
	  r = SSL_read(SS->TLS.ssl, buf, spc);
	  e = SSL_get_error(SS->TLS.ssl, r);
	  switch (e) {
	  case SSL_ERROR_NONE:
	    SS->TLS.wantreadwrite =  0;
	    e = 0;
	    break;
	  case SSL_ERROR_WANT_READ:
	    SS->TLS.wantreadwrite = -1;
	    e = EAGAIN;
	    break;
	  case SSL_ERROR_WANT_WRITE:
	    SS->TLS.wantreadwrite =  1;
	    e = EAGAIN;
	    break;
	  case SSL_ERROR_SSL:
	    e = EPROTO;
	  default:
	    SS->TLS.wantreadwrite =  0;
	    break;
	  }
	  if (tls_loglevel >= 3) {
	    msg_info(SS,"smtp_nbread() rc=%d errno=%d", r, e);
	    if (r > 0) tls_dump(SS, buf, r);
	  }
	} else
#endif /* - HAVE_OPENSSL */
	  {
	    /* Normal read(2) */
	    r = read(infd, buf, spc);
	    e = errno;
#if 0
	    msg_info(SS,"smtp_nbread() rc=%d errno=%d", r, e);
	    if (r > 0) tls_dump(SS, buf, r);
#endif
	  }
  
	errno = e;
	return r;
}

int zsfsetfd(fp, fd)
     Sfio_t *fp;
     int fd;
{
  /* This is *NOT* the SFIO's sfsetfd() -- we do no sfsync() at any point.. */
  fp->file = fd;
  return fd;
}
