ssl-proxy-gnutls.c revision 89a126810703c666309310d0f3189e9834d70b5b
5a580c3a38ced62d4bcc95b8ac7c4f2935b5d294Timo Sirainen/* Copyright (c) 2002-2007 Dovecot authors, see the included COPYING file */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include "common.h"
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include "ioloop.h"
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include "network.h"
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include "hash.h"
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include "ssl-proxy.h"
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#ifdef HAVE_GNUTLS
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#error broken currently
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include <stdio.h>
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include <stdlib.h>
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include <unistd.h>
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include <fcntl.h>
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include <gcrypt.h>
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include <gnutls/gnutls.h>
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstruct ssl_proxy {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch int refcount;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch gnutls_session session;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct ip_addr ip;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch int fd_ssl, fd_plain;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct io *io_ssl, *io_plain;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch int io_ssl_dir;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch unsigned char outbuf_plain[1024];
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch unsigned int outbuf_pos_plain;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch size_t send_left_ssl, send_left_plain;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch};
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschconst int protocol_priority[] =
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
7384b4e78eaab44693c985192276e31322155e32Stephan Boschconst int kx_priority[] =
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch { GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, 0 };
7384b4e78eaab44693c985192276e31322155e32Stephan Boschconst int cipher_priority[] =
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch { GNUTLS_CIPHER_RIJNDAEL_CBC, GNUTLS_CIPHER_3DES_CBC,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch GNUTLS_CIPHER_ARCFOUR_128, GNUTLS_CIPHER_ARCFOUR_40, 0 };
7384b4e78eaab44693c985192276e31322155e32Stephan Boschconst int comp_priority[] =
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch { GNUTLS_COMP_LZO, GNUTLS_COMP_ZLIB, GNUTLS_COMP_NULL, 0 };
7384b4e78eaab44693c985192276e31322155e32Stephan Boschconst int mac_priority[] =
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch { GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0 };
7384b4e78eaab44693c985192276e31322155e32Stephan Boschconst int cert_type_priority[] =
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch { GNUTLS_CRT_X509, 0 };
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic struct hash_table *ssl_proxies;
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic gnutls_certificate_credentials x509_cred;
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic gnutls_dh_params dh_params;
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic gnutls_rsa_params rsa_params;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic void ssl_input(struct ssl_proxy *proxy);
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic void plain_input(struct ssl_proxy *proxy);
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic bool ssl_proxy_destroy(struct ssl_proxy *proxy);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic const char *get_alert_text(struct ssl_proxy *proxy)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return gnutls_alert_get_name(gnutls_alert_get(proxy->session));
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch}
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic int handle_ssl_error(struct ssl_proxy *proxy, int error)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (!gnutls_error_is_fatal(error)) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (!verbose_ssl)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (error == GNUTLS_E_WARNING_ALERT_RECEIVED) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_warning("Received SSL warning alert: %s [%s]",
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch get_alert_text(proxy),
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch net_ip2addr(&proxy->ip));
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch } else {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_warning("Non-fatal SSL error: %s: %s",
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch get_alert_text(proxy),
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch net_ip2addr(&proxy->ip));
b99130e4cf4af4e6b103b949456222f3a2dff424Timo Sirainen }
b99130e4cf4af4e6b103b949456222f3a2dff424Timo Sirainen return 0;
b99130e4cf4af4e6b103b949456222f3a2dff424Timo Sirainen }
b99130e4cf4af4e6b103b949456222f3a2dff424Timo Sirainen
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (verbose_ssl) {
56d1345c43bbd28c36b7faa85e4163bd9e874290Timo Sirainen /* fatal error occurred */
30d917bcd48d70af0371baf27571cc198d621a62Timo Sirainen if (error == GNUTLS_E_FATAL_ALERT_RECEIVED) {
9d0aee99a8c80d71137aa9b8c216cc203bec7a9aTimo Sirainen i_warning("Received SSL fatal alert: %s [%s]",
9d0aee99a8c80d71137aa9b8c216cc203bec7a9aTimo Sirainen get_alert_text(proxy),
35e962a9186b4e9b2001628c1d7b55c24b33ce84Timo Sirainen net_ip2addr(&proxy->ip));
35e962a9186b4e9b2001628c1d7b55c24b33ce84Timo Sirainen } else {
35e962a9186b4e9b2001628c1d7b55c24b33ce84Timo Sirainen i_warning("Error reading from SSL client: %s [%s]",
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch gnutls_strerror(error),
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch net_ip2addr(&proxy->ip));
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch gnutls_alert_send_appropriate(proxy->session, error);
ad03049781fc14807248007d524be4daf06c3ee2Stephan Bosch ssl_proxy_destroy(proxy);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return -1;
feba5e502b2131c9a1c766b7ef9ff041dbf71d1dStephan Bosch}
a8c4e79ff50fac21b05a7368b052583d410ca15cTimo Sirainen
a8c4e79ff50fac21b05a7368b052583d410ca15cTimo Sirainenstatic int proxy_recv_ssl(struct ssl_proxy *proxy, void *data, size_t size)
70505f4839520ac67895992621c97d2480c22e7fTimo Sirainen{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch int rcvd;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch rcvd = gnutls_record_recv(proxy->session, data, size);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (rcvd > 0)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return rcvd;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (rcvd == 0 || rcvd == GNUTLS_E_UNEXPECTED_PACKET_LENGTH) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* disconnected, either by nicely telling us that we'll
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch close the connection, or by simply killing the
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch connection which gives us the packet length error. */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch ssl_proxy_destroy(proxy);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return -1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return handle_ssl_error(proxy, rcvd);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch}
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic int proxy_send_ssl(struct ssl_proxy *proxy,
9dc01e0d10a61cab14867b26bf0d2d1dcf8ad978Timo Sirainen const void *data, size_t size)
9dc01e0d10a61cab14867b26bf0d2d1dcf8ad978Timo Sirainen{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch int sent;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch sent = gnutls_record_send(proxy->session, data, size);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (sent >= 0)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return sent;
aacf2a69acc59e9382578d6f4e030788abc79706Timo Sirainen
aacf2a69acc59e9382578d6f4e030788abc79706Timo Sirainen if (sent == GNUTLS_E_PUSH_ERROR || sent == GNUTLS_E_INVALID_SESSION) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* don't warn about errors related to unexpected
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch disconnection */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch ssl_proxy_destroy(proxy);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return -1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen return handle_ssl_error(proxy, sent);
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen}
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic int ssl_proxy_destroy(struct ssl_proxy *proxy)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (--proxy->refcount > 0)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return TRUE;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch hash_remove(ssl_proxies, proxy);
636d0f43138468f8efe685a681326b123f660e49Timo Sirainen
fc94140acba51adafedafbc8491a3223a51db7a8Stephan Bosch gnutls_deinit(proxy->session);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (proxy->io_ssl != NULL)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch io_remove(proxy->io_ssl);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (proxy->io_plain != NULL)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch io_remove(proxy->io_plain);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch (void)net_disconnect(proxy->fd_ssl);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch (void)net_disconnect(proxy->fd_plain);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_free(proxy);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch main_unref();
fc94140acba51adafedafbc8491a3223a51db7a8Stephan Bosch return FALSE;
fc94140acba51adafedafbc8491a3223a51db7a8Stephan Bosch}
fc94140acba51adafedafbc8491a3223a51db7a8Stephan Bosch
fc94140acba51adafedafbc8491a3223a51db7a8Stephan Boschstatic void ssl_output(struct ssl_proxy *proxy)
636d0f43138468f8efe685a681326b123f660e49Timo Sirainen{
636d0f43138468f8efe685a681326b123f660e49Timo Sirainen int sent;
636d0f43138468f8efe685a681326b123f660e49Timo Sirainen
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch sent = net_transmit(proxy->fd_plain,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch proxy->outbuf_plain + proxy->outbuf_pos_plain,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch proxy->send_left_plain);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (sent < 0) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* disconnected */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch ssl_proxy_destroy(proxy);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
fb1be3de0159d6a10e916ad992e2bc53be64c6d5Timo Sirainen
fb1be3de0159d6a10e916ad992e2bc53be64c6d5Timo Sirainen proxy->send_left_plain -= sent;
fb1be3de0159d6a10e916ad992e2bc53be64c6d5Timo Sirainen proxy->outbuf_pos_plain += sent;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (proxy->send_left_plain > 0)
1a9a35a6b307f8d5b25345af55e40a99162b4072Timo Sirainen return;
1a9a35a6b307f8d5b25345af55e40a99162b4072Timo Sirainen
1a9a35a6b307f8d5b25345af55e40a99162b4072Timo Sirainen /* everything is sent, start reading again */
1a9a35a6b307f8d5b25345af55e40a99162b4072Timo Sirainen io_remove(proxy->io_ssl);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch proxy->io_ssl = io_add(proxy->fd_ssl, IO_READ, ssl_input, proxy);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch}
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic void ssl_input(struct ssl_proxy *proxy)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch int rcvd, sent;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch rcvd = proxy_recv_ssl(proxy, proxy->outbuf_plain,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch sizeof(proxy->outbuf_plain));
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (rcvd <= 0)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch sent = net_transmit(proxy->fd_plain, proxy->outbuf_plain, (size_t)rcvd);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (sent == rcvd)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return;
17cd0e0963f2fb0e66d49703e8cd0bda1b842468Timo Sirainen
17cd0e0963f2fb0e66d49703e8cd0bda1b842468Timo Sirainen if (sent < 0) {
17cd0e0963f2fb0e66d49703e8cd0bda1b842468Timo Sirainen /* disconnected */
17cd0e0963f2fb0e66d49703e8cd0bda1b842468Timo Sirainen ssl_proxy_destroy(proxy);
17cd0e0963f2fb0e66d49703e8cd0bda1b842468Timo Sirainen return;
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen }
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen /* everything wasn't sent - don't read anything until we've
ba1c847d0af4afe4787ed470d0c818e948e184e2Timo Sirainen sent it all */
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen proxy->outbuf_pos_plain = 0;
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen proxy->send_left_plain = rcvd - sent;
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen io_remove(proxy->io_ssl);
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen proxy->io_ssl = io_add(proxy->fd_ssl, IO_WRITE, ssl_output, proxy);
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen}
56d1345c43bbd28c36b7faa85e4163bd9e874290Timo Sirainen
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainenstatic void plain_output(struct ssl_proxy *proxy)
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen{
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen int sent;
35e962a9186b4e9b2001628c1d7b55c24b33ce84Timo Sirainen
35e962a9186b4e9b2001628c1d7b55c24b33ce84Timo Sirainen sent = proxy_send_ssl(proxy, NULL, proxy->send_left_ssl);
35e962a9186b4e9b2001628c1d7b55c24b33ce84Timo Sirainen if (sent <= 0)
d47b9f1bd7274c7b2d9049c2e1718d1cf89cc572Timo Sirainen return;
d47b9f1bd7274c7b2d9049c2e1718d1cf89cc572Timo Sirainen
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen proxy->send_left_ssl -= sent;
ba1c847d0af4afe4787ed470d0c818e948e184e2Timo Sirainen if (proxy->send_left_ssl > 0)
ba1c847d0af4afe4787ed470d0c818e948e184e2Timo Sirainen return;
ba1c847d0af4afe4787ed470d0c818e948e184e2Timo Sirainen
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen /* everything is sent, start reading again */
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen io_remove(proxy->io_plain);
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen proxy->io_plain = io_add(proxy->fd_plain, IO_READ, plain_input, proxy);
415e16c3dc185578695b7d88e561a52de6c8b1b1Timo Sirainen}
static void plain_input(struct ssl_proxy *proxy)
{
char buf[1024];
ssize_t rcvd, sent;
rcvd = net_receive(proxy->fd_plain, buf, sizeof(buf));
if (rcvd < 0) {
/* disconnected */
gnutls_bye(proxy->session, 1);
ssl_proxy_destroy(proxy);
return;
}
sent = proxy_send_ssl(proxy, buf, (size_t)rcvd);
if (sent < 0 || sent == rcvd)
return;
/* everything wasn't sent - don't read anything until we've
sent it all */
proxy->send_left_ssl = rcvd - sent;
io_remove(proxy->io_plain);
proxy->io_plain = io_add(proxy->fd_ssl, IO_WRITE, plain_output, proxy);
}
static void ssl_handshake(struct ssl_proxy *proxy)
{
int ret, dir;
ret = gnutls_handshake(proxy->session);
if (ret >= 0) {
/* handshake done, now we can start reading */
if (proxy->io_ssl != NULL)
io_remove(proxy->io_ssl);
proxy->io_plain = io_add(proxy->fd_plain, IO_READ,
plain_input, proxy);
proxy->io_ssl = io_add(proxy->fd_ssl, IO_READ,
ssl_input, proxy);
return;
}
if (handle_ssl_error(proxy, ret) < 0)
return;
/* i/o interrupted */
dir = gnutls_record_get_direction(proxy->session) == 0 ?
IO_READ : IO_WRITE;
if (proxy->io_ssl_dir != dir) {
if (proxy->io_ssl != NULL)
io_remove(proxy->io_ssl);
proxy->io_ssl = io_add(proxy->fd_ssl, dir,
ssl_handshake, proxy);
proxy->io_ssl_dir = dir;
}
}
static gnutls_session initialize_state(void)
{
gnutls_session session;
gnutls_init(&session, GNUTLS_SERVER);
gnutls_protocol_set_priority(session, protocol_priority);
gnutls_cipher_set_priority(session, cipher_priority);
gnutls_compression_set_priority(session, comp_priority);
gnutls_kx_set_priority(session, kx_priority);
gnutls_mac_set_priority(session, mac_priority);
gnutls_certificate_type_set_priority(session, cert_type_priority);
gnutls_cred_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
return session;
}
int ssl_proxy_new(int fd, struct ip_addr *ip)
{
struct ssl_proxy *proxy;
gnutls_session session;
int sfd[2];
if (!ssl_initialized) {
i_error("SSL support not enabled in configuration");
return -1;
}
session = initialize_state();
gnutls_transport_set_ptr(session, fd);
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) == -1) {
i_error("socketpair() failed: %m");
gnutls_deinit(session);
return -1;
}
net_set_nonblock(sfd[0], TRUE);
net_set_nonblock(sfd[1], TRUE);
net_set_nonblock(fd, TRUE);
proxy = i_new(struct ssl_proxy, 1);
proxy->refcount = 1;
proxy->session = session;
proxy->fd_ssl = fd;
proxy->fd_plain = sfd[0];
proxy->ip = *ip;
hash_insert(ssl_proxies, proxy, proxy);
proxy->refcount++;
ssl_handshake(proxy);
if (!ssl_proxy_destroy(proxy)) {
/* handshake failed. return the disconnected socket anyway
so the caller doesn't try to use the old closed fd */
return sfd[1];
}
main_ref();
return sfd[1];
}
static void read_next_field(int fd, gnutls_datum *datum,
const char *fname, const char *field_name)
{
ssize_t ret;
/* get size */
ret = read(fd, &datum->size, sizeof(datum->size));
if (ret < 0)
i_fatal("read() failed for %s: %m", fname);
if (ret != sizeof(datum->size)) {
(void)unlink(fname);
i_fatal("Corrupted SSL parameter file %s: File too small",
fname);
}
if (datum->size > 10240) {
(void)unlink(fname);
i_fatal("Corrupted SSL parameter file %s: "
"Field '%s' too large (%u)",
fname, field_name, datum->size);
}
/* read the actual data */
datum->data = t_malloc(datum->size);
ret = read(fd, datum->data, datum->size);
if (ret < 0)
i_fatal("read() failed for %s: %m", fname);
if ((size_t)ret != datum->size) {
(void)unlink(fname);
i_fatal("Corrupted SSL parameter file %s: "
"Field '%s' not fully in file (%u < %u)",
fname, field_name, datum->size - ret, datum->size);
}
}
static void read_dh_parameters(int fd, const char *fname)
{
gnutls_datum dbits, prime, generator;
int ret, bits;
if ((ret = gnutls_dh_params_init(&dh_params)) < 0) {
i_fatal("gnutls_dh_params_init() failed: %s",
gnutls_strerror(ret));
}
/* read until bits field is 0 */
for (;;) {
read_next_field(fd, &dbits, fname, "DH bits");
if (dbits.size != sizeof(int)) {
(void)unlink(fname);
i_fatal("Corrupted SSL parameter file %s: "
"Field 'DH bits' has invalid size %u",
fname, dbits.size);
}
bits = *((int *) dbits.data);
if (bits == 0)
break;
read_next_field(fd, &prime, fname, "DH prime");
read_next_field(fd, &generator, fname, "DH generator");
ret = gnutls_dh_params_set(dh_params, prime, generator, bits);
if (ret < 0) {
i_fatal("gnutls_dh_params_set() failed: %s",
gnutls_strerror(ret));
}
}
}
static void read_rsa_parameters(int fd, const char *fname)
{
gnutls_datum m, e, d, p, q, u;
int ret;
read_next_field(fd, &m, fname, "RSA m");
read_next_field(fd, &e, fname, "RSA e");
read_next_field(fd, &d, fname, "RSA d");
read_next_field(fd, &p, fname, "RSA p");
read_next_field(fd, &q, fname, "RSA q");
read_next_field(fd, &u, fname, "RSA u");
if ((ret = gnutls_rsa_params_init(&rsa_params)) < 0) {
i_fatal("gnutls_rsa_params_init() failed: %s",
gnutls_strerror(ret));
}
/* only 512bit is allowed */
ret = gnutls_rsa_params_set(rsa_params, m, e, d, p, q, u, 512);
if (ret < 0) {
i_fatal("gnutls_rsa_params_set() failed: %s",
gnutls_strerror(ret));
}
}
static void read_parameters(const char *fname)
{
int fd;
/* we'll wait until parameter file exists */
for (;;) {
fd = open(fname, O_RDONLY);
if (fd != -1)
break;
if (errno != ENOENT)
i_fatal("Can't open SSL parameter file %s: %m", fname);
sleep(1);
}
read_dh_parameters(fd, fname);
read_rsa_parameters(fd, fname);
(void)close(fd);
}
static void gcrypt_log_handler(void *context ATTR_UNUSED, int level,
const char *fmt, va_list args)
{
if (level == GCRY_LOG_FATAL) {
t_push();
i_error("gcrypt fatal: %s", t_strdup_vprintf(fmt, args));
t_pop();
}
}
void ssl_proxy_init(void)
{
const char *certfile, *keyfile, *paramfile;
unsigned char buf[4];
int ret;
certfile = getenv("SSL_CERT_FILE");
keyfile = getenv("SSL_KEY_FILE");
paramfile = getenv("SSL_PARAM_FILE");
if (certfile == NULL || keyfile == NULL || paramfile == NULL) {
/* SSL support is disabled */
return;
}
if ((ret = gnutls_global_init() < 0)) {
i_fatal("gnu_tls_global_init() failed: %s",
gnutls_strerror(ret));
}
/* gcrypt initialization - set log handler and make sure randomizer
opens /dev/urandom now instead of after we've chrooted */
gcry_set_log_handler(gcrypt_log_handler, NULL);
gcry_randomize(buf, sizeof(buf), GCRY_STRONG_RANDOM);
read_parameters(paramfile);
if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) {
i_fatal("gnutls_certificate_allocate_credentials() failed: %s",
gnutls_strerror(ret));
}
ret = gnutls_certificate_set_x509_key_file(x509_cred, certfile, keyfile,
GNUTLS_X509_FMT_PEM);
if (ret < 0) {
i_fatal("Can't load certificate files %s and %s: %s",
certfile, keyfile, gnutls_strerror(ret));
}
gnutls_certificate_set_dh_params(x509_cred, dh_params);
gnutls_certificate_set_rsa_export_params(x509_cred, rsa_params);
ssl_proxies = hash_create(system_pool, system_pool, 0, NULL, NULL);
ssl_initialized = TRUE;
}
void ssl_proxy_deinit(void)
{
struct hash_iterate_context *iter;
void *key, *value;
if (!ssl_initialized)
return;
iter = hash_iterate_init(ssl_proxies);
while (hash_iterate(iter, &key, &value))
ssl_proxy_destroy(value);
hash_iterate_deinit(iter);
hash_destroy(ssl_proxies);
gnutls_certificate_free_credentials(x509_cred);
gnutls_global_deinit();
}
#endif