/*++
/* NAME
/*	tls_bio_ops 3
/* SUMMARY
/*	TLS network basic I/O management
/* SYNOPSIS
/*	#define TLS_INTERNAL
/*	#include <tls.h>
/*
/*	int tls_bio_connect(fd, timeout, context)
/*	int	fd;
/*	int	timeout;
/*	TLS_SESS_STATE *context;
/*
/*	int tls_bio_accept(fd, timeout, context)
/*	int	fd;
/*	int	timeout;
/*	TLS_SESS_STATE *context;
/*
/*	int tls_bio_shutdown(fd, timeout, context)
/*	int	fd;
/*	int	timeout;
/*	TLS_SESS_STATE *context;
/*
/*	int tls_bio_read(fd, buf, len, timeout, context)
/*	int	fd;
/*	void	*buf;
/*	int	len;
/*	int	timeout;
/*	TLS_SESS_STATE *context;
/*
/*	int tls_bio_write(fd, buf, len, timeout, context)
/*	int	fd;
/*	void	*buf;
/*	int	len;
/*	int	timeout;
/*	TLS_SESS_STATE *context;
/* DESCRIPTION
/*	This module enforces timeouts on non-blocking I/O while
/*	performing TLS handshake or input/output operations.
/*
/*	The Postfix VSTREAM read/write routines invoke the
/*	tls_bio_read/write routines to send and receive plain-text
/*	data.  In addition, this module provides tls_bio_connect/accept
/*	routines that trigger the initial TLS handshake.  The
/*	tls_bio_xxx routines invoke the corresponding SSL routines
/*	that translate the requests into TLS protocol messages.
/*
/*	Whenever an SSL operation indicates that network input (or
/*	output) needs to happen, the tls_bio_xxx routines wait for
/*	the network to become readable (or writable) within the
/*	timeout limit, then retry the SSL operation. This works
/*	because the network socket is in non-blocking mode.
/*
/*	tls_bio_connect() performs the SSL_connect() operation.
/*
/*	tls_bio_accept() performs the SSL_accept() operation.
/*
/*	tls_bio_shutdown() performs the SSL_shutdown() operation.
/*
/*	tls_bio_read() performs the SSL_read() operation.
/*
/*	tls_bio_write() performs the SSL_write() operation.
/*
/*	Arguments:
/* .IP fd
/*	Network socket.
/* .IP buf
/*	Read/write buffer.
/* .IP len
/*	Read/write request size.
/* .IP timeout
/*	Read/write timeout.
/* .IP TLScontext
/*	TLS session state.
/* DIAGNOSTICS
/*	The result value is -1 in case of a network read/write
/*	error, otherwise it is the result value of the TLS operation.
/* LICENSE
/* .ad
/* .fi
/*	This software is free. You can do with it whatever you want.
/*	The original author kindly requests that you acknowledge
/*	the use of his software.
/* AUTHOR(S)
/*	Originally written by:
/*	Lutz Jaenicke
/*	BTU Cottbus
/*	Allgemeine Elektrotechnik
/*	Universitaetsplatz 3-4
/*	D-03044 Cottbus, Germany
/*
/*	Updated by:
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*
/*	Victor Duchovni
/*	Morgan Stanley
/*--*/

/* System library. */

#include <sys_defs.h>

#ifdef USE_TLS

/* Utility library. */

#include <msg.h>
#include <iostuff.h>

/* TLS library. */

#define TLS_INTERNAL
#include <tls.h>

/* tls_bio - perform SSL input/output operation with extreme prejudice */

int     tls_bio(int fd, int timeout, TLS_SESS_STATE *TLScontext,
		        int (*hsfunc) (SSL *),
		        int (*rfunc) (SSL *, void *, int),
		        int (*wfunc) (SSL *, const void *, int),
		        void *buf, int num)
{
    const char *myname = "tls_bio";
    int     status;
    int     err;
    int     retval = 0;
    int     done;

    /*
     * If necessary, retry the SSL handshake or read/write operation after
     * handling any pending network I/O.
     */
    for (done = 0; done == 0; /* void */ ) {
	if (hsfunc)
	    status = hsfunc(TLScontext->con);
	else if (rfunc)
	    status = rfunc(TLScontext->con, buf, num);
	else if (wfunc)
	    status = wfunc(TLScontext->con, buf, num);
	else
	    msg_panic("%s: nothing to do here", myname);
	err = SSL_get_error(TLScontext->con, 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 (err == SSL_ERROR_SSL) {
	    if (ERR_peek_error() == 0x0407006AL) {
		tls_print_errors();
		msg_info("OpenSSL <= 0.9.5a workaround called: certificate errors ignored");
		err = SSL_get_error(TLScontext->con, status);
	    }
	}
#endif

	/*
	 * Find out if we must retry the operation and/or if there is pending
	 * network I/O.
	 * 
	 * XXX If we're the first to invoke SSL_shutdown(), then the operation
	 * isn't really complete when the call returns. We could hide that
	 * anomaly here and repeat the call.
	 */
	switch (err) {
	case SSL_ERROR_NONE:			/* success */
	    retval = status;
	    done = 1;
	    break;
	case SSL_ERROR_WANT_WRITE:
	    if (write_wait(fd, timeout) < 0)
		return (-1);			/* timeout error */
	    break;
	case SSL_ERROR_WANT_READ:
	    if (read_wait(fd, timeout) < 0)
		return (-1);			/* timeout error */
	    break;

	    /*
	     * With tls_timed_read() and tls_timed_write() the caller is the
	     * VSTREAM library module which is unaware of TLS, so we log the
	     * TLS error stack here. In a better world, each VSTREAM I/O
	     * object would provide an error reporting method in addition to
	     * the timed_read and timed_write methods, so that we would not
	     * need to have ad-hoc code like this.
	     */
	case SSL_ERROR_SSL:
	    if (rfunc || wfunc)
		tls_print_errors();
	    /* FALLTHROUGH */
	default:
	    retval = status;
	    done = 1;
	    break;
	}
    }
    return (retval);
}

#endif
