/* * 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 */