summaryrefslogtreecommitdiffstats
path: root/sendmail/src/sfsasl.c
diff options
context:
space:
mode:
Diffstat (limited to 'sendmail/src/sfsasl.c')
-rw-r--r--sendmail/src/sfsasl.c952
1 files changed, 952 insertions, 0 deletions
diff --git a/sendmail/src/sfsasl.c b/sendmail/src/sfsasl.c
new file mode 100644
index 0000000..216d87e
--- /dev/null
+++ b/sendmail/src/sfsasl.c
@@ -0,0 +1,952 @@
+/*
+ * Copyright (c) 1999-2006 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: sfsasl.c,v 8.115 2006/04/18 21:34:07 ca Exp $")
+#include <stdlib.h>
+#include <sendmail.h>
+#include <sm/time.h>
+#include <errno.h>
+
+/* allow to disable error handling code just in case... */
+#ifndef DEAL_WITH_ERROR_SSL
+# define DEAL_WITH_ERROR_SSL 1
+#endif /* ! DEAL_WITH_ERROR_SSL */
+
+#if SASL
+# include "sfsasl.h"
+
+/* Structure used by the "sasl" file type */
+struct sasl_obj
+{
+ SM_FILE_T *fp;
+ sasl_conn_t *conn;
+};
+
+struct sasl_info
+{
+ SM_FILE_T *fp;
+ sasl_conn_t *conn;
+};
+
+/*
+** SASL_GETINFO - returns requested information about a "sasl" file
+** descriptor.
+**
+** Parameters:
+** fp -- the file descriptor
+** what -- the type of information requested
+** valp -- the thang to return the information in
+**
+** Returns:
+** -1 for unknown requests
+** >=0 on success with valp filled in (if possible).
+*/
+
+static int sasl_getinfo __P((SM_FILE_T *, int, void *));
+
+static int
+sasl_getinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ struct sasl_obj *so = (struct sasl_obj *) fp->f_cookie;
+
+ switch (what)
+ {
+ case SM_IO_WHAT_FD:
+ if (so->fp == NULL)
+ return -1;
+ return so->fp->f_file; /* for stdio fileno() compatability */
+
+ case SM_IO_IS_READABLE:
+ if (so->fp == NULL)
+ return 0;
+
+ /* get info from underlying file */
+ return sm_io_getinfo(so->fp, what, valp);
+
+ default:
+ return -1;
+ }
+}
+
+/*
+** SASL_OPEN -- creates the sasl specific information for opening a
+** file of the sasl type.
+**
+** Parameters:
+** fp -- the file pointer associated with the new open
+** info -- contains the sasl connection information pointer and
+** the original SM_FILE_T that holds the open
+** flags -- ignored
+** rpool -- ignored
+**
+** Returns:
+** 0 on success
+*/
+
+static int sasl_open __P((SM_FILE_T *, const void *, int, const void *));
+
+/* ARGSUSED2 */
+static int
+sasl_open(fp, info, flags, rpool)
+ SM_FILE_T *fp;
+ const void *info;
+ int flags;
+ const void *rpool;
+{
+ struct sasl_obj *so;
+ struct sasl_info *si = (struct sasl_info *) info;
+
+ so = (struct sasl_obj *) sm_malloc(sizeof(struct sasl_obj));
+ if (so == NULL)
+ {
+ errno = ENOMEM;
+ return -1;
+ }
+ so->fp = si->fp;
+ so->conn = si->conn;
+
+ /*
+ ** The underlying 'fp' is set to SM_IO_NOW so that the entire
+ ** encoded string is written in one chunk. Otherwise there is
+ ** the possibility that it may appear illegal, bogus or
+ ** mangled to the other side of the connection.
+ ** We will read or write through 'fp' since it is the opaque
+ ** connection for the communications. We need to treat it this
+ ** way in case the encoded string is to be sent down a TLS
+ ** connection rather than, say, sm_io's stdio.
+ */
+
+ (void) sm_io_setvbuf(so->fp, SM_TIME_DEFAULT, NULL, SM_IO_NOW, 0);
+ fp->f_cookie = so;
+ return 0;
+}
+
+/*
+** SASL_CLOSE -- close the sasl specific parts of the sasl file pointer
+**
+** Parameters:
+** fp -- the file pointer to close
+**
+** Returns:
+** 0 on success
+*/
+
+static int sasl_close __P((SM_FILE_T *));
+
+static int
+sasl_close(fp)
+ SM_FILE_T *fp;
+{
+ struct sasl_obj *so;
+
+ so = (struct sasl_obj *) fp->f_cookie;
+ if (so == NULL)
+ return 0;
+ if (so->fp != NULL)
+ {
+ sm_io_close(so->fp, SM_TIME_DEFAULT);
+ so->fp = NULL;
+ }
+ sm_free(so);
+ so = NULL;
+ return 0;
+}
+
+/* how to deallocate a buffer allocated by SASL */
+extern void sm_sasl_free __P((void *));
+# define SASL_DEALLOC(b) sm_sasl_free(b)
+
+/*
+** SASL_READ -- read encrypted information and decrypt it for the caller
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- the location to place the decrypted information
+** size -- the number of bytes to read after decryption
+**
+** Results:
+** -1 on error
+** otherwise the number of bytes read
+*/
+
+static ssize_t sasl_read __P((SM_FILE_T *, char *, size_t));
+
+static ssize_t
+sasl_read(fp, buf, size)
+ SM_FILE_T *fp;
+ char *buf;
+ size_t size;
+{
+ int result;
+ ssize_t len;
+# if SASL >= 20000
+ static const char *outbuf = NULL;
+# else /* SASL >= 20000 */
+ static char *outbuf = NULL;
+# endif /* SASL >= 20000 */
+ static unsigned int outlen = 0;
+ static unsigned int offset = 0;
+ struct sasl_obj *so = (struct sasl_obj *) fp->f_cookie;
+
+ /*
+ ** sasl_decode() may require more data than a single read() returns.
+ ** Hence we have to put a loop around the decoding.
+ ** This also requires that we may have to split up the returned
+ ** data since it might be larger than the allowed size.
+ ** Therefore we use a static pointer and return portions of it
+ ** if necessary.
+ ** XXX Note: This function is not thread-safe nor can it be used
+ ** on more than one file. A correct implementation would store
+ ** this data in fp->f_cookie.
+ */
+
+# if SASL >= 20000
+ while (outlen == 0)
+# else /* SASL >= 20000 */
+ while (outbuf == NULL && outlen == 0)
+# endif /* SASL >= 20000 */
+ {
+ len = sm_io_read(so->fp, SM_TIME_DEFAULT, buf, size);
+ if (len <= 0)
+ return len;
+ result = sasl_decode(so->conn, buf,
+ (unsigned int) len, &outbuf, &outlen);
+ if (result != SASL_OK)
+ {
+ if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "AUTH: sasl_decode error=%d", result);
+ outbuf = NULL;
+ offset = 0;
+ outlen = 0;
+ return -1;
+ }
+ }
+
+ if (outbuf == NULL)
+ {
+ /* be paranoid: outbuf == NULL but outlen != 0 */
+ syserr("@sasl_read failure: outbuf == NULL but outlen != 0");
+ /* NOTREACHED */
+ }
+ if (outlen - offset > size)
+ {
+ /* return another part of the buffer */
+ (void) memcpy(buf, outbuf + offset, size);
+ offset += size;
+ len = size;
+ }
+ else
+ {
+ /* return the rest of the buffer */
+ len = outlen - offset;
+ (void) memcpy(buf, outbuf + offset, (size_t) len);
+# if SASL < 20000
+ SASL_DEALLOC(outbuf);
+# endif /* SASL < 20000 */
+ outbuf = NULL;
+ offset = 0;
+ outlen = 0;
+ }
+ return len;
+}
+
+/*
+** SASL_WRITE -- write information out after encrypting it
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- holds the data to be encrypted and written
+** size -- the number of bytes to have encrypted and written
+**
+** Returns:
+** -1 on error
+** otherwise number of bytes written
+*/
+
+static ssize_t sasl_write __P((SM_FILE_T *, const char *, size_t));
+
+static ssize_t
+sasl_write(fp, buf, size)
+ SM_FILE_T *fp;
+ const char *buf;
+ size_t size;
+{
+ int result;
+# if SASL >= 20000
+ const char *outbuf;
+# else /* SASL >= 20000 */
+ char *outbuf;
+# endif /* SASL >= 20000 */
+ unsigned int outlen, *maxencode;
+ size_t ret = 0, total = 0;
+ struct sasl_obj *so = (struct sasl_obj *) fp->f_cookie;
+
+ /*
+ ** Fetch the maximum input buffer size for sasl_encode().
+ ** This can be less than the size set in attemptauth()
+ ** due to a negotation with the other side, e.g.,
+ ** Cyrus IMAP lmtp program sets maxbuf=4096,
+ ** digestmd5 substracts 25 and hence we'll get 4071
+ ** instead of 8192 (MAXOUTLEN).
+ ** Hack (for now): simply reduce the size, callers are (must be)
+ ** able to deal with that and invoke sasl_write() again with
+ ** the rest of the data.
+ ** Note: it would be better to store this value in the context
+ ** after the negotiation.
+ */
+
+ result = sasl_getprop(so->conn, SASL_MAXOUTBUF,
+ (const void **) &maxencode);
+ if (result == SASL_OK && size > *maxencode && *maxencode > 0)
+ size = *maxencode;
+
+ result = sasl_encode(so->conn, buf,
+ (unsigned int) size, &outbuf, &outlen);
+
+ if (result != SASL_OK)
+ {
+ if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "AUTH: sasl_encode error=%d", result);
+ return -1;
+ }
+
+ if (outbuf != NULL)
+ {
+ while (outlen > 0)
+ {
+ errno = 0;
+ /* XXX result == 0? */
+ ret = sm_io_write(so->fp, SM_TIME_DEFAULT,
+ &outbuf[total], outlen);
+ if (ret <= 0)
+ return ret;
+ outlen -= ret;
+ total += ret;
+ }
+# if SASL < 20000
+ SASL_DEALLOC(outbuf);
+# endif /* SASL < 20000 */
+ }
+ return size;
+}
+
+/*
+** SFDCSASL -- create sasl file type and open in and out file pointers
+** for sendmail to read from and write to.
+**
+** Parameters:
+** fin -- the sm_io file encrypted data to be read from
+** fout -- the sm_io file encrypted data to be written to
+** conn -- the sasl connection pointer
+** tmo -- timeout
+**
+** Returns:
+** -1 on error
+** 0 on success
+**
+** Side effects:
+** The arguments "fin" and "fout" are replaced with the new
+** SM_FILE_T pointers.
+*/
+
+int
+sfdcsasl(fin, fout, conn, tmo)
+ SM_FILE_T **fin;
+ SM_FILE_T **fout;
+ sasl_conn_t *conn;
+ int tmo;
+{
+ SM_FILE_T *newin, *newout;
+ SM_FILE_T SM_IO_SET_TYPE(sasl_vector, "sasl", sasl_open, sasl_close,
+ sasl_read, sasl_write, NULL, sasl_getinfo, NULL,
+ SM_TIME_DEFAULT);
+ struct sasl_info info;
+
+ if (conn == NULL)
+ {
+ /* no need to do anything */
+ return 0;
+ }
+
+ SM_IO_INIT_TYPE(sasl_vector, "sasl", sasl_open, sasl_close,
+ sasl_read, sasl_write, NULL, sasl_getinfo, NULL,
+ SM_TIME_DEFAULT);
+ info.fp = *fin;
+ info.conn = conn;
+ newin = sm_io_open(&sasl_vector, SM_TIME_DEFAULT, &info,
+ SM_IO_RDONLY_B, NULL);
+
+ if (newin == NULL)
+ return -1;
+
+ info.fp = *fout;
+ info.conn = conn;
+ newout = sm_io_open(&sasl_vector, SM_TIME_DEFAULT, &info,
+ SM_IO_WRONLY_B, NULL);
+
+ if (newout == NULL)
+ {
+ (void) sm_io_close(newin, SM_TIME_DEFAULT);
+ return -1;
+ }
+ sm_io_automode(newin, newout);
+
+ sm_io_setinfo(*fin, SM_IO_WHAT_TIMEOUT, &tmo);
+ sm_io_setinfo(*fout, SM_IO_WHAT_TIMEOUT, &tmo);
+
+ *fin = newin;
+ *fout = newout;
+ return 0;
+}
+#endif /* SASL */
+
+#if STARTTLS
+# include "sfsasl.h"
+# include <openssl/err.h>
+
+/* Structure used by the "tls" file type */
+struct tls_obj
+{
+ SM_FILE_T *fp;
+ SSL *con;
+};
+
+struct tls_info
+{
+ SM_FILE_T *fp;
+ SSL *con;
+};
+
+/*
+** TLS_GETINFO - returns requested information about a "tls" file
+** descriptor.
+**
+** Parameters:
+** fp -- the file descriptor
+** what -- the type of information requested
+** valp -- the thang to return the information in (unused)
+**
+** Returns:
+** -1 for unknown requests
+** >=0 on success with valp filled in (if possible).
+*/
+
+static int tls_getinfo __P((SM_FILE_T *, int, void *));
+
+/* ARGSUSED2 */
+static int
+tls_getinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ struct tls_obj *so = (struct tls_obj *) fp->f_cookie;
+
+ switch (what)
+ {
+ case SM_IO_WHAT_FD:
+ if (so->fp == NULL)
+ return -1;
+ return so->fp->f_file; /* for stdio fileno() compatability */
+
+ case SM_IO_IS_READABLE:
+ return SSL_pending(so->con) > 0;
+
+ default:
+ return -1;
+ }
+}
+
+/*
+** TLS_OPEN -- creates the tls specific information for opening a
+** file of the tls type.
+**
+** Parameters:
+** fp -- the file pointer associated with the new open
+** info -- the sm_io file pointer holding the open and the
+** TLS encryption connection to be read from or written to
+** flags -- ignored
+** rpool -- ignored
+**
+** Returns:
+** 0 on success
+*/
+
+static int tls_open __P((SM_FILE_T *, const void *, int, const void *));
+
+/* ARGSUSED2 */
+static int
+tls_open(fp, info, flags, rpool)
+ SM_FILE_T *fp;
+ const void *info;
+ int flags;
+ const void *rpool;
+{
+ struct tls_obj *so;
+ struct tls_info *ti = (struct tls_info *) info;
+
+ so = (struct tls_obj *) sm_malloc(sizeof(struct tls_obj));
+ if (so == NULL)
+ {
+ errno = ENOMEM;
+ return -1;
+ }
+ so->fp = ti->fp;
+ so->con = ti->con;
+
+ /*
+ ** We try to get the "raw" file descriptor that TLS uses to
+ ** do the actual read/write with. This is to allow us control
+ ** over the file descriptor being a blocking or non-blocking type.
+ ** Under the covers TLS handles the change and this allows us
+ ** to do timeouts with sm_io.
+ */
+
+ fp->f_file = sm_io_getinfo(so->fp, SM_IO_WHAT_FD, NULL);
+ (void) sm_io_setvbuf(so->fp, SM_TIME_DEFAULT, NULL, SM_IO_NOW, 0);
+ fp->f_cookie = so;
+ return 0;
+}
+
+/*
+** TLS_CLOSE -- close the tls specific parts of the tls file pointer
+**
+** Parameters:
+** fp -- the file pointer to close
+**
+** Returns:
+** 0 on success
+*/
+
+static int tls_close __P((SM_FILE_T *));
+
+static int
+tls_close(fp)
+ SM_FILE_T *fp;
+{
+ struct tls_obj *so;
+
+ so = (struct tls_obj *) fp->f_cookie;
+ if (so == NULL)
+ return 0;
+ if (so->fp != NULL)
+ {
+ sm_io_close(so->fp, SM_TIME_DEFAULT);
+ so->fp = NULL;
+ }
+ sm_free(so);
+ so = NULL;
+ return 0;
+}
+
+/* maximum number of retries for TLS related I/O due to handshakes */
+# define MAX_TLS_IOS 4
+
+/*
+** TLS_RETRY -- check whether a failed SSL operation can be retried
+**
+** Parameters:
+** ssl -- TLS structure
+** rfd -- read fd
+** wfd -- write fd
+** tlsstart -- start time of TLS operation
+** timeout -- timeout for TLS operation
+** err -- SSL error
+** where -- description of operation
+**
+** Results:
+** >0 on success
+** 0 on timeout
+** <0 on error
+*/
+
+int
+tls_retry(ssl, rfd, wfd, tlsstart, timeout, err, where)
+ SSL *ssl;
+ int rfd;
+ int wfd;
+ time_t tlsstart;
+ int timeout;
+ int err;
+ const char *where;
+{
+ int ret;
+ time_t left;
+ time_t now = curtime();
+ struct timeval tv;
+
+ ret = -1;
+
+ /*
+ ** For SSL_ERROR_WANT_{READ,WRITE}:
+ ** There is not a complete SSL record available yet
+ ** or there is only a partial SSL record removed from
+ ** the network (socket) buffer into the SSL buffer.
+ ** The SSL_connect will only succeed when a full
+ ** SSL record is available (assuming a "real" error
+ ** doesn't happen). To handle when a "real" error
+ ** does happen the select is set for exceptions too.
+ ** The connection may be re-negotiated during this time
+ ** so both read and write "want errors" need to be handled.
+ ** A select() exception loops back so that a proper SSL
+ ** error message can be gotten.
+ */
+
+ left = timeout - (now - tlsstart);
+ if (left <= 0)
+ return 0; /* timeout */
+ tv.tv_sec = left;
+ tv.tv_usec = 0;
+
+ if (LogLevel > 14)
+ {
+ sm_syslog(LOG_INFO, NOQID,
+ "STARTTLS=%s, info: fds=%d/%d, err=%d",
+ where, rfd, wfd, err);
+ }
+
+ if (FD_SETSIZE > 0 &&
+ ((err == SSL_ERROR_WANT_READ && rfd >= FD_SETSIZE) ||
+ (err == SSL_ERROR_WANT_WRITE && wfd >= FD_SETSIZE)))
+ {
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS=%s, error: fd %d/%d too large",
+ where, rfd, wfd);
+ if (LogLevel > 8)
+ tlslogerr(where);
+ }
+ errno = EINVAL;
+ }
+ else if (err == SSL_ERROR_WANT_READ)
+ {
+ fd_set ssl_maskr, ssl_maskx;
+
+ FD_ZERO(&ssl_maskr);
+ FD_SET(rfd, &ssl_maskr);
+ FD_ZERO(&ssl_maskx);
+ FD_SET(rfd, &ssl_maskx);
+ do
+ {
+ ret = select(rfd + 1, &ssl_maskr, NULL, &ssl_maskx,
+ &tv);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0 && errno > 0)
+ ret = -errno;
+ }
+ else if (err == SSL_ERROR_WANT_WRITE)
+ {
+ fd_set ssl_maskw, ssl_maskx;
+
+ FD_ZERO(&ssl_maskw);
+ FD_SET(wfd, &ssl_maskw);
+ FD_ZERO(&ssl_maskx);
+ FD_SET(rfd, &ssl_maskx);
+ do
+ {
+ ret = select(wfd + 1, NULL, &ssl_maskw, &ssl_maskx,
+ &tv);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0 && errno > 0)
+ ret = -errno;
+ }
+ return ret;
+}
+
+/* errno to force refill() etc to stop (see IS_IO_ERROR()) */
+#ifdef ETIMEDOUT
+# define SM_ERR_TIMEOUT ETIMEDOUT
+#else /* ETIMEDOUT */
+# define SM_ERR_TIMEOUT EIO
+#endif /* ETIMEDOUT */
+
+/*
+** TLS_READ -- read secured information for the caller
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- the location to place the data
+** size -- the number of bytes to read from connection
+**
+** Results:
+** -1 on error
+** otherwise the number of bytes read
+*/
+
+static ssize_t tls_read __P((SM_FILE_T *, char *, size_t));
+
+static ssize_t
+tls_read(fp, buf, size)
+ SM_FILE_T *fp;
+ char *buf;
+ size_t size;
+{
+ int r, rfd, wfd, try, ssl_err;
+ struct tls_obj *so = (struct tls_obj *) fp->f_cookie;
+ time_t tlsstart;
+ char *err;
+
+ try = 99;
+ err = NULL;
+ tlsstart = curtime();
+
+ retry:
+ r = SSL_read(so->con, (char *) buf, size);
+
+ if (r > 0)
+ return r;
+
+ err = NULL;
+ switch (ssl_err = SSL_get_error(so->con, r))
+ {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ err = "read W BLOCK";
+ /* FALLTHROUGH */
+ case SSL_ERROR_WANT_READ:
+ if (err == NULL)
+ err = "read R BLOCK";
+ rfd = SSL_get_rfd(so->con);
+ wfd = SSL_get_wfd(so->con);
+ try = tls_retry(so->con, rfd, wfd, tlsstart,
+ TimeOuts.to_datablock, ssl_err, "read");
+ if (try > 0)
+ goto retry;
+ errno = SM_ERR_TIMEOUT;
+ break;
+
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ err = "write X BLOCK";
+ break;
+ case SSL_ERROR_SYSCALL:
+ if (r == 0 && errno == 0) /* out of protocol EOF found */
+ break;
+ err = "syscall error";
+/*
+ get_last_socket_error());
+*/
+ break;
+ case SSL_ERROR_SSL:
+#if DEAL_WITH_ERROR_SSL
+ if (r == 0 && errno == 0) /* out of protocol EOF found */
+ break;
+#endif /* DEAL_WITH_ERROR_SSL */
+ err = "generic SSL error";
+ if (LogLevel > 9)
+ tlslogerr("read");
+
+#if DEAL_WITH_ERROR_SSL
+ /* avoid repeated calls? */
+ if (r == 0)
+ r = -1;
+#endif /* DEAL_WITH_ERROR_SSL */
+ break;
+ }
+ if (err != NULL)
+ {
+ int save_errno;
+
+ save_errno = (errno == 0) ? EIO : errno;
+ if (try == 0 && save_errno == SM_ERR_TIMEOUT)
+ {
+ if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: read error=timeout");
+ }
+ else if (LogLevel > 8)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: read error=%s (%d), errno=%d, get_error=%s, retry=%d, ssl_err=%d",
+ err, r, errno,
+ ERR_error_string(ERR_get_error(), NULL), try,
+ ssl_err);
+ else if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: read error=%s (%d), retry=%d, ssl_err=%d",
+ err, r, errno, try, ssl_err);
+ errno = save_errno;
+ }
+ return r;
+}
+
+/*
+** TLS_WRITE -- write information out through secure connection
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- holds the data to be securely written
+** size -- the number of bytes to write
+**
+** Returns:
+** -1 on error
+** otherwise number of bytes written
+*/
+
+static ssize_t tls_write __P((SM_FILE_T *, const char *, size_t));
+
+static ssize_t
+tls_write(fp, buf, size)
+ SM_FILE_T *fp;
+ const char *buf;
+ size_t size;
+{
+ int r, rfd, wfd, try, ssl_err;
+ struct tls_obj *so = (struct tls_obj *) fp->f_cookie;
+ time_t tlsstart;
+ char *err;
+
+ try = 99;
+ err = NULL;
+ tlsstart = curtime();
+
+ retry:
+ r = SSL_write(so->con, (char *) buf, size);
+
+ if (r > 0)
+ return r;
+ err = NULL;
+ switch (ssl_err = SSL_get_error(so->con, r))
+ {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ err = "read W BLOCK";
+ /* FALLTHROUGH */
+ case SSL_ERROR_WANT_READ:
+ if (err == NULL)
+ err = "read R BLOCK";
+ rfd = SSL_get_rfd(so->con);
+ wfd = SSL_get_wfd(so->con);
+ try = tls_retry(so->con, rfd, wfd, tlsstart,
+ DATA_PROGRESS_TIMEOUT, ssl_err, "write");
+ if (try > 0)
+ goto retry;
+ errno = SM_ERR_TIMEOUT;
+ break;
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ err = "write X BLOCK";
+ break;
+ case SSL_ERROR_SYSCALL:
+ if (r == 0 && errno == 0) /* out of protocol EOF found */
+ break;
+ err = "syscall error";
+/*
+ get_last_socket_error());
+*/
+ break;
+ case SSL_ERROR_SSL:
+ err = "generic SSL error";
+/*
+ ERR_GET_REASON(ERR_peek_error()));
+*/
+ if (LogLevel > 9)
+ tlslogerr("write");
+
+#if DEAL_WITH_ERROR_SSL
+ /* avoid repeated calls? */
+ if (r == 0)
+ r = -1;
+#endif /* DEAL_WITH_ERROR_SSL */
+ break;
+ }
+ if (err != NULL)
+ {
+ int save_errno;
+
+ save_errno = (errno == 0) ? EIO : errno;
+ if (try == 0 && save_errno == SM_ERR_TIMEOUT)
+ {
+ if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: write error=timeout");
+ }
+ else if (LogLevel > 8)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: write error=%s (%d), errno=%d, get_error=%s, retry=%d, ssl_err=%d",
+ err, r, errno,
+ ERR_error_string(ERR_get_error(), NULL), try,
+ ssl_err);
+ else if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: write error=%s (%d), errno=%d, retry=%d, ssl_err=%d",
+ err, r, errno, try, ssl_err);
+ errno = save_errno;
+ }
+ return r;
+}
+
+/*
+** SFDCTLS -- create tls file type and open in and out file pointers
+** for sendmail to read from and write to.
+**
+** Parameters:
+** fin -- data input source being replaced
+** fout -- data output source being replaced
+** con -- the tls connection pointer
+**
+** Returns:
+** -1 on error
+** 0 on success
+**
+** Side effects:
+** The arguments "fin" and "fout" are replaced with the new
+** SM_FILE_T pointers.
+** The original "fin" and "fout" are preserved in the tls file
+** type but are not actually used because of the design of TLS.
+*/
+
+int
+sfdctls(fin, fout, con)
+ SM_FILE_T **fin;
+ SM_FILE_T **fout;
+ SSL *con;
+{
+ SM_FILE_T *tlsin, *tlsout;
+ SM_FILE_T SM_IO_SET_TYPE(tls_vector, "tls", tls_open, tls_close,
+ tls_read, tls_write, NULL, tls_getinfo, NULL,
+ SM_TIME_FOREVER);
+ struct tls_info info;
+
+ SM_ASSERT(con != NULL);
+
+ SM_IO_INIT_TYPE(tls_vector, "tls", tls_open, tls_close,
+ tls_read, tls_write, NULL, tls_getinfo, NULL,
+ SM_TIME_FOREVER);
+ info.fp = *fin;
+ info.con = con;
+ tlsin = sm_io_open(&tls_vector, SM_TIME_DEFAULT, &info, SM_IO_RDONLY_B,
+ NULL);
+ if (tlsin == NULL)
+ return -1;
+
+ info.fp = *fout;
+ tlsout = sm_io_open(&tls_vector, SM_TIME_DEFAULT, &info, SM_IO_WRONLY_B,
+ NULL);
+ if (tlsout == NULL)
+ {
+ (void) sm_io_close(tlsin, SM_TIME_DEFAULT);
+ return -1;
+ }
+ sm_io_automode(tlsin, tlsout);
+
+ *fin = tlsin;
+ *fout = tlsout;
+ return 0;
+}
+#endif /* STARTTLS */
OpenPOWER on IntegriCloud