diff options
Diffstat (limited to 'src/tls/tls_bio_ops.c')
-rw-r--r-- | src/tls/tls_bio_ops.c | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/src/tls/tls_bio_ops.c b/src/tls/tls_bio_ops.c new file mode 100644 index 0000000..9b66195 --- /dev/null +++ b/src/tls/tls_bio_ops.c @@ -0,0 +1,296 @@ +/*++ +/* 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 VSTREAM-style 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 +/* A result value > 0 means successful completion. +/* +/* A result value < 0 means that the requested operation did +/* not complete due to TLS protocol failure, system call +/* failure, or for any reason described under "in addition" +/* below. +/* +/* A result value of 0 from tls_bio_shutdown() means that the +/* operation is in progress. A result value of 0 from other +/* tls_bio_ops(3) operations means that the remote party either +/* closed the network connection or that it sent a TLS shutdown +/* request. +/* +/* Upon return from the tls_bio_ops(3) routines the global +/* errno value is non-zero when the requested operation did not +/* complete due to system call failure. +/* +/* In addition, the result value is set to -1, and the global +/* errno value is set to ETIMEDOUT, when some network read/write +/* operation did not complete within the time limit. +/* 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> +#include <sys/time.h> + +#ifndef timersub +/* res = a - b */ +#define timersub(a, b, res) do { \ + (res)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (res)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((res)->tv_usec < 0) { \ + (res)->tv_sec--; \ + (res)->tv_usec += 1000000; \ + } \ + } while (0) +#endif + +#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 enable_deadline; + struct timeval time_left; /* amount of time left */ + struct timeval time_deadline; /* time of deadline */ + struct timeval time_now; /* time after SSL_mumble() call */ + + /* + * Compensation for interface mis-match: With VSTREAMs, timeout <= 0 + * means wait forever; with the read/write_wait() calls below, we need to + * specify timeout < 0 instead. + * + * Safety: no time limit means no deadline. + */ + if (timeout <= 0) { + timeout = -1; + enable_deadline = 0; + } + + /* + * Deadline management is simpler than with VSTREAMs, because we don't + * need to decrement a per-stream time limit. We just work within the + * budget that is available for this tls_bio() call. + */ + else { + enable_deadline = + vstream_fstat(TLScontext->stream, VSTREAM_FLAG_DEADLINE); + if (enable_deadline) { + GETTIMEOFDAY(&time_deadline); + time_deadline.tv_sec += timeout; + } + } + + /* + * If necessary, retry the SSL handshake or read/write operation after + * handling any pending network I/O. + */ + for (;;) { + + /* + * Flush the per-thread SSL error queue. Otherwise, errors from other + * code that also uses TLS may confuse SSL_get_error(3). + */ + ERR_clear_error(); + + 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); + + /* + * Correspondence between SSL_ERROR_* error codes and tls_bio_(read, + * write, accept, connect, shutdown) return values (for brevity: + * retval). + * + * SSL_ERROR_NONE corresponds with retval > 0. With SSL_(read, write) + * this is the number of plaintext bytes sent or received. With + * SSL_(accept, connect, shutdown) this means that the operation was + * completed successfully. + * + * SSL_ERROR_WANT_(WRITE, READ) start a new loop iteration, or force + * (retval = -1, errno = ETIMEDOUT) when the time limit is exceeded. + * + * All other SSL_ERROR_* cases correspond with retval <= 0. With + * SSL_(read, write, accept, connect) retval == 0 means that the + * remote party either closed the network connection or that it + * requested TLS shutdown; with SSL_shutdown() retval == 0 means that + * our own shutdown request is in progress. With all operations + * retval < 0 means that there was an error. In the latter case, + * SSL_ERROR_SYSCALL means that error details are returned via the + * errno value. + * + * 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_WANT_WRITE: + case SSL_ERROR_WANT_READ: + if (enable_deadline) { + GETTIMEOFDAY(&time_now); + timersub(&time_deadline, &time_now, &time_left); + timeout = time_left.tv_sec + (time_left.tv_usec > 0); + if (timeout <= 0) { + errno = ETIMEDOUT; + return (-1); + } + } + if (err == SSL_ERROR_WANT_WRITE) { + if (write_wait(fd, timeout) < 0) + return (-1); /* timeout error */ + } else { + if (read_wait(fd, timeout) < 0) + return (-1); /* timeout error */ + } + break; + + /* + * Unhandled cases: SSL_ERROR_WANT_(ACCEPT, CONNECT, X509_LOOKUP) + * etc. Historically, Postfix silently treated these as ordinary + * I/O errors so we don't really know how common they are. For + * now, we just log a warning. + */ + default: + msg_warn("%s: unexpected SSL_ERROR code %d", myname, err); + /* FALLTHROUGH */ + + /* + * 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 */ + case SSL_ERROR_ZERO_RETURN: + case SSL_ERROR_NONE: + errno = 0; /* avoid bogus warnings */ + /* FALLTHROUGH */ + case SSL_ERROR_SYSCALL: + return (status); + } + } +} + +#endif |