/*
* Copyright (c) 2003-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.
*/
/*
** This code is based on Lutz Jaenicke's TLS patch for postfix.
** http://www.aet.tu-cottbus.de/personen/jaenicke/pfixtls/
*/
#include "sm/generic.h"
SM_RCSID("@(#)$Id: tlsbio.c,v 1.26 2007/01/20 05:03:31 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/io.h"
#include "sm/tls.h"
#include "sm/tlsbio.h"
#if MTA_USE_TLS
static read_F sm_tlsbio_read;
static write_F sm_tlsbio_write;
static open_F sm_tlsbio_open;
static close_F sm_tlsbio_close;
/* only for communication between tls_open() and sm_tlsbio_open() */
struct tls_info_S
{
SSL *ti_con; /* SSL connection */
sm_file_T *ti_fp; /* underlying file */
tlsl_ctx_P ti_log_ctx;
};
typedef struct tls_info_S tls_info_T, *tls_info_P;
/*
** Define a maxlength for certificate onelines. The length is checked by
** all routines when copying.
*/
#define CCERT_BUFSIZ 256
struct tlsbio_ctx_S
{
SSL *tbc_con; /* SSL connection */
BIO *tbc_internal_bio; /* application/TLS side of pair */
BIO *tbc_network_bio; /* network side of pair */
sm_file_T *tbc_fp; /* underlying file */
tlsl_ctx_P tbc_log_ctx;
#if 0
/* this belongs in the session context */
char tbc_peer_subject[CCERT_BUFSIZ];
char tbc_peer_issuer[CCERT_BUFSIZ];
char tbc_peer_CN[CCERT_BUFSIZ];
char tbc_issuer_CN[CCERT_BUFSIZ];
uchar tbc_md[EVP_MAX_MD_SIZE];
char tbc_fingerprint[EVP_MAX_MD_SIZE * 3];
char tbc_peername_save[129];
int tbc_enforce_verify_errors;
int tbc_enforce_CN;
#endif /* 0 */
};
typedef struct tlsbio_ctx_S tlsbio_ctx_T, *tlsbio_ctx_P;
/*
* DESCRIPTION: Keeping control of the network interface using BIO-pairs.
*
* When the TLS layer is active, all input/output must be filtered through
* it. On the other hand to handle timeout conditions, full control over
* the network socket must be kept. This rules out the "normal way" of
* connecting the TLS layer directly to the socket.
* The TLS layer is realized with a BIO-pair:
*
* application | TLS-engine
* | |
* +--------> SSL_operations()
* | /\ ||
* | || \/
* | BIO-pair (internal_bio)
* +--------< BIO-pair (network_bio)
* | |
* socket |
*
* The normal application operations connect to the SSL operations to send
* and retrieve (cleartext) data. Inside the TLS-engine the data are converted
* to/from TLS protocol. The TLS functionality itself is only connected to
* the internal_bio and hence only has status information about this internal
* interface.
* Thus, if the SSL_operations() return successfully (SSL_ERROR_NONE) or want
* to read (SSL_ERROR_WANT_READ) there may as well be data inside the buffering
* BIO-pair. So whenever an SSL_operation() returns without a fatal error,
* the BIO-pair internal buffer must be flushed to the network.
* NOTE: This is especially true in the SSL_ERROR_WANT_READ case: the TLS-layer
* might want to read handshake data, that will never come since its own
* written data will only reach the peer after flushing the buffer!
*
* The BIO-pair buffer size has been set to 8192 bytes, this is an arbitrary
* value that can hold more data than the typical PMTU, so that it does
* not force the generation of packets smaller than necessary.
* It is also larger than the default VSTREAM_BUFSIZE (4096, see vstream.h),
* so that large write operations could be handled within one call.
* The internal buffer in the network/network_bio handling layer has been
* set to the same value, since this seems to be reasonable. The code is
* however able to handle arbitrary values smaller or larger than the
* buffer size in the BIO-pair.
*
** Note: can we get the PMTU size and set this accordingly?
*/
const ssize_t BIO_bufsiz = 8192;
#define NETLAYER_BUFFERSIZE 8192
#if NETLAYER_BUFFERSIZE >= INT_MAX
ERROR _NETLAYER_BUFFERSIZE >= _INT_MAX
#endif
/*
* The interface layer between network and BIO-pair. The BIO-pair buffers
* the data to/from the TLS layer. Hence, at any time, there may be data
* in the buffer that must be written to the network. This writing has
* highest priority because the handshake might fail otherwise.
* Only then a read_request can be satisfied.
*/
static int
network_biopair_interop(sm_file_T *fp, BIO *network_bio)
{
int want_write;
ssize_t num_write;
int write_pos;
int from_bio;
int want_read;
ssize_t num_read;
int to_bio;
sm_ret_T ret;
char buffer[NETLAYER_BUFFERSIZE];
while ((want_write = BIO_ctrl_pending(network_bio)) > 0)
{
if (want_write > (int)sizeof(buffer))
want_write = (int)sizeof(buffer);
from_bio = BIO_read(network_bio, buffer, want_write);
/*
* Write the complete contents of the buffer. Since TLS performs
* underlying handshaking, we cannot afford to leave the buffer
* unflushed, as we could run into a deadlock trap (the peer
* waiting for a final byte and we already waiting for his reply
* in read position).
*/
write_pos = 0;
do
{
ret = sm_io_write(fp, buffer + write_pos,
from_bio - write_pos, &num_write);
if (sm_is_err(ret))
return ret;
if (num_write <= 0)
return sm_error_perm(SM_EM_TLS, EIO);
write_pos += num_write;
} while (write_pos < from_bio);
}
while ((want_read = BIO_ctrl_get_read_request(network_bio)) > 0)
{
if (want_read > (int)sizeof(buffer))
want_read = (int)sizeof(buffer);
ret = sm_io_read(fp, buffer, want_read, &num_read);
if (sm_is_err(ret))
return ret;
if (num_read <= 0)
return sm_error_perm(SM_EM_TLS, EIO);
to_bio = BIO_write(network_bio, buffer, num_read);
if (to_bio != num_read)
return sm_error_perm(SM_EM_TLS, EIO);
}
return 0;
}
/*
** change format to match standard?
*/
static void
tls_print_errors(tlsl_ctx_P tlsl_ctx)
{
ulong l, es;
int line, flags;
const char *file, *data;
char buf[256];
es = CRYPTO_thread_id();
while ((l = ERR_get_error_line_data(&file, &line, &data, &flags)) != 0)
{
if (flags & ERR_TXT_STRING)
sm_log_write(tlsl_ctx->tlsl_lctx,
TL_LCAT_IO, TL_LMOD_IO,
SM_LOG_WARN, 10,
"%lu:%s:%s:%d:%s", es,
ERR_error_string(l, buf), file, line, data);
else
sm_log_write(tlsl_ctx->tlsl_lctx,
TL_LCAT_IO, TL_LMOD_IO,
SM_LOG_WARN, 10,
"%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() and perform the SSL_read(), SSL_write() operations.
* 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).
*/
/*
XXX Problems:
How to determine "bytes" (read/written)?
Is it always the total number or an error?
uchar *buf doesn't match const uchar *buf for write... use a macro
or duplicate the code (see sendmail 8 version)?
turn this into a macro?
../../mta/libmta/tlsbio.c:353: warning: passing arg 5 of `do_tls_operation'
discards qualifiers from pointer target type
*/
int
do_tls_operation(sm_file_T *fp, hsfunc_F *hsfunc, rfunc_F *rfunc, wfunc_F *wfunc, uchar *buf, int num, ssize_t *bytes)
{
int status;
int err;
sm_ret_T ret;
int biop_retval;
bool done;
tlsbio_ctx_T *tlsbio_ctx;
SM_REQUIRE(bytes != NULL);
*bytes = 0;
done = false;
ret = SM_SUCCESS;
tlsbio_ctx = (tlsbio_ctx_T *) f_cookie(*fp);
while (!done)
{
if (hsfunc)
status = hsfunc(tlsbio_ctx->tbc_con);
else if (rfunc)
status = rfunc(tlsbio_ctx->tbc_con, buf, num);
else
status = wfunc(tlsbio_ctx->tbc_con, (const void *) buf, num);
err = SSL_get_error(tlsbio_ctx->tbc_con, status);
switch (err)
{
case SSL_ERROR_NONE: /* success */
*bytes = status;
done = true; /* no break, flush buffer before leaving */
/* FALLTHROUGH */
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_READ:
biop_retval = network_biopair_interop(tlsbio_ctx->tbc_fp,
tlsbio_ctx->tbc_network_bio);
if (biop_retval < 0) {
if (sm_is_err(biop_retval))
return biop_retval;
return (-1); /* fatal network error */
}
break;
/* connection was closed cleanly */
case SSL_ERROR_ZERO_RETURN:
/* FALLTHROUGH */
/* some error */
case SSL_ERROR_SYSCALL:
ret = sm_error_perm(SM_EM_TLS, SM_SSL_ERROR_SYSCALL);
case SSL_ERROR_SSL:
if (SM_SUCCESS == ret)
ret = sm_error_perm(SM_EM_TLS, SM_SSL_ERROR_SSL);
default:
if (SM_SUCCESS == ret)
ret = sm_error_perm(SM_EM_TLS, SM_SSL_ERROR_GENERIC);
tls_print_errors(tlsbio_ctx->tbc_log_ctx);
done = true;
}
}
return ret;
}
static sm_ret_T
sm_tlsbio_read(sm_file_T *fp, uchar *buf, size_t n, ssize_t *bytesread)
{
return do_tls_operation(fp, NULL, SSL_read, NULL, buf, n, bytesread);
}
static sm_ret_T
sm_tlsbio_write(sm_file_T *fp, const uchar *buf, size_t n, ssize_t *byteswritten)
{
return do_tls_operation(fp, NULL, NULL, SSL_write, (char *) buf, n,
byteswritten);
}
/*
** SM_TLSBIO_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).
*/
/* ARGSUSED2 */
static int
sm_tlsbio_getinfo(sm_file_T *fp, int what, void *valp)
{
tlsbio_ctx_P tlsbio_ctx;
SM_REQUIRE(fp != NULL);
tlsbio_ctx = (tlsbio_ctx_P) f_cookie(*fp);
switch (what)
{
case SM_IO_WHAT_FD:
if (tlsbio_ctx->tbc_fp == NULL)
return -1;
return f_fd(*(tlsbio_ctx->tbc_fp));
/* for stdio fileno() compatability */
case SM_IO_IS_READABLE:
return SSL_pending(tlsbio_ctx->tbc_con) > 0;
default:
return -1;
}
}
/*
** SM_TLSBIO_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
** newfp -- new fp
**
** Returns:
** 0 on success
*/
static sm_ret_T
sm_tlsbio_open(sm_file_T *fp, const void *info, int flags, va_list ap)
{
tlsbio_ctx_P tlsbio_ctx;
tls_info_P ti;
SM_REQUIRE(fp != NULL);
SM_REQUIRE(info != NULL);
ti = (tls_info_P) info;
tlsbio_ctx = (tlsbio_ctx_P) sm_zalloc(sizeof(*tlsbio_ctx));
if (tlsbio_ctx == NULL)
return sm_error_temp(SM_EM_TLS, ENOMEM);
tlsbio_ctx->tbc_fp = ti->ti_fp;
tlsbio_ctx->tbc_con = ti->ti_con;
tlsbio_ctx->tbc_log_ctx = ti->ti_log_ctx;
/* The TLS connection is realized by a BIO_pair, so obtain the pair */
if (!BIO_new_bio_pair(&tlsbio_ctx->tbc_internal_bio, BIO_bufsiz,
&tlsbio_ctx->tbc_network_bio, BIO_bufsiz))
goto error;
/*
** Connect the SSL-connection with the application side
** of the BIO-pair for reading and writing.
*/
SSL_set_bio(tlsbio_ctx->tbc_con, tlsbio_ctx->tbc_internal_bio,
tlsbio_ctx->tbc_internal_bio);
fp->f_cookie = tlsbio_ctx;
return SM_SUCCESS;
error:
SM_FREE(tlsbio_ctx);
return sm_error_temp(SM_EM_TLS, SM_E_UNEXPECTED);
/* XXX Better error! (check OpenSSL error stack?) */
}
/*
** SM_TLSBIO_CLOSE -- close the tls specific parts of the tls file pointer
**
** Parameters:
** fp -- the file pointer to close
** flags -- ignored
**
** Returns:
** 0 on success
*/
static sm_ret_T
sm_tlsbio_close(sm_file_T *fp, int flags)
{
int r;
ssize_t d;
tlsbio_ctx_P tlsbio_ctx;
if (fp == NULL)
return SM_SUCCESS; /* really? */
tlsbio_ctx = (tlsbio_ctx_P) f_cookie(*fp);
if (tlsbio_ctx == NULL)
return SM_SUCCESS;
if (tlsbio_ctx->tbc_con != NULL) {
/*
** 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.
*/
r = do_tls_operation(fp, SSL_shutdown, NULL, NULL, NULL, 0, &d);
if (r == 0)
r = do_tls_operation(fp, SSL_shutdown, NULL, NULL, NULL, 0, &d);
/*
** 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.
*/
SSL_free(tlsbio_ctx->tbc_con);
BIO_free(tlsbio_ctx->tbc_network_bio);
}
if (tlsbio_ctx->tbc_fp != NULL) {
sm_io_close(tlsbio_ctx->tbc_fp, flags);
tlsbio_ctx->tbc_fp = NULL;
}
sm_free_size(tlsbio_ctx, sizeof(*tlsbio_ctx));
tlsbio_ctx = NULL;
return SM_SUCCESS;
}
/*
** TLS_OPEN -- create tls file type and open in and out file pointers
** for sendmail to read from and write to.
**
** Parameters:
** fp -- lower level file
** con -- the SSL connection pointer
** tlsl_ctx -- TLS library context
** newfp -- (pointer to) new fp (output)
**
** Returns:
*/
sm_ret_T
tls_open(sm_file_T *fp, SSL *con, tlsl_ctx_P tlsl_ctx, sm_file_T **newfp)
{
tls_info_T info;
sm_stream_T tls_vector =
SM_STREAM_STRUCT(sm_tlsbio_open, sm_tlsbio_close,
sm_tlsbio_read, sm_tlsbio_write, NULL, NULL, NULL,
sm_tlsbio_getinfo, NULL);
SM_REQUIRE(con != NULL);
SM_REQUIRE(fp != NULL);
SM_REQUIRE(newfp != NULL);
info.ti_fp = fp;
info.ti_con = con;
info.ti_log_ctx = tlsl_ctx;
return sm_io_open(&tls_vector, &info, SM_IO_RDWR, newfp);
}
#endif /* MTA_USE_TLS */
syntax highlighted by Code2HTML, v. 0.9.1