9bd497b835
MFC after: 4 days
975 lines
21 KiB
C
975 lines
21 KiB
C
/*
|
|
* Copyright (c) 1999-2006, 2008 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.118 2008/07/22 15:12:48 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 negotiation 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 */
|
|
|
|
/*
|
|
** SET_TLS_RD_TMO -- read secured information for the caller
|
|
**
|
|
** Parameters:
|
|
** rd_tmo -- read timeout
|
|
**
|
|
** Results:
|
|
** none
|
|
** This is a hack: there is no way to pass it in
|
|
*/
|
|
|
|
static int tls_rd_tmo = -1;
|
|
|
|
void
|
|
set_tls_rd_tmo(rd_tmo)
|
|
int rd_tmo;
|
|
{
|
|
tls_rd_tmo = rd_tmo;
|
|
}
|
|
|
|
/*
|
|
** 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,
|
|
(tls_rd_tmo < 0) ? TimeOuts.to_datablock
|
|
: tls_rd_tmo,
|
|
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 */
|