ssl-proxy-gnutls.c revision 76b43e4417bab52e913da39b5f5bc2a130d3f149
5a580c3a38ced62d4bcc95b8ac7c4f2935b5d294Timo Sirainen/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen#include "common.h"
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen#include "ioloop.h"
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen#include "network.h"
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen#include "hash.h"
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen#include "ssl-proxy.h"
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen#ifdef HAVE_GNUTLS
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen#error broken currently
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen#include <stdio.h>
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen#include <stdlib.h>
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen#include <unistd.h>
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen#include <fcntl.h>
0db42260be85e797aa9909a29b20296996f52e75Timo Sirainen#include <gcrypt.h>
0db42260be85e797aa9909a29b20296996f52e75Timo Sirainen#include <gnutls/gnutls.h>
0db42260be85e797aa9909a29b20296996f52e75Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstruct ssl_proxy {
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen int refcount;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen gnutls_session session;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen struct ip_addr ip;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen int fd_ssl, fd_plain;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen struct io *io_ssl, *io_plain;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen int io_ssl_dir;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen unsigned char outbuf_plain[1024];
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen unsigned int outbuf_pos_plain;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen size_t send_left_ssl, send_left_plain;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen};
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenconst int protocol_priority[] =
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenconst int kx_priority[] =
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen { GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, 0 };
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenconst int cipher_priority[] =
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen { GNUTLS_CIPHER_RIJNDAEL_CBC, GNUTLS_CIPHER_3DES_CBC,
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen GNUTLS_CIPHER_ARCFOUR_128, GNUTLS_CIPHER_ARCFOUR_40, 0 };
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenconst int comp_priority[] =
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen { GNUTLS_COMP_LZO, GNUTLS_COMP_ZLIB, GNUTLS_COMP_NULL, 0 };
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenconst int mac_priority[] =
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen { GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0 };
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenconst int cert_type_priority[] =
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen { GNUTLS_CRT_X509, 0 };
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic struct hash_table *ssl_proxies;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic gnutls_certificate_credentials x509_cred;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic gnutls_dh_params dh_params;
16cb5d65265dd0b216542803fd80c4b999ae118eTimo Sirainenstatic gnutls_rsa_params rsa_params;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic void ssl_input(struct ssl_proxy *proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic void plain_input(struct ssl_proxy *proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic bool ssl_proxy_destroy(struct ssl_proxy *proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic const char *get_alert_text(struct ssl_proxy *proxy)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen{
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return gnutls_alert_get_name(gnutls_alert_get(proxy->session));
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen}
0ca3b9cb0f2a322a25ce7f229dc3d3a0b46be17bTimo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic int handle_ssl_error(struct ssl_proxy *proxy, int error)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen{
16cb5d65265dd0b216542803fd80c4b999ae118eTimo Sirainen if (!gnutls_error_is_fatal(error)) {
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (!verbose_ssl)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return 0;
16cb5d65265dd0b216542803fd80c4b999ae118eTimo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (error == GNUTLS_E_WARNING_ALERT_RECEIVED) {
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen i_warning("Received SSL warning alert: %s [%s]",
16cb5d65265dd0b216542803fd80c4b999ae118eTimo Sirainen get_alert_text(proxy),
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen net_ip2addr(&proxy->ip));
16cb5d65265dd0b216542803fd80c4b999ae118eTimo Sirainen } else {
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen i_warning("Non-fatal SSL error: %s: %s",
16cb5d65265dd0b216542803fd80c4b999ae118eTimo Sirainen get_alert_text(proxy),
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen net_ip2addr(&proxy->ip));
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen }
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return 0;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen }
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (verbose_ssl) {
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen /* fatal error occurred */
16cb5d65265dd0b216542803fd80c4b999ae118eTimo Sirainen if (error == GNUTLS_E_FATAL_ALERT_RECEIVED) {
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen i_warning("Received SSL fatal alert: %s [%s]",
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen get_alert_text(proxy),
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen net_ip2addr(&proxy->ip));
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen } else {
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen i_warning("Error reading from SSL client: %s [%s]",
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen gnutls_strerror(error),
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen net_ip2addr(&proxy->ip));
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen }
56aa97d74071f3a2987140c2ff1cfd5a59cb35aaTimo Sirainen }
56aa97d74071f3a2987140c2ff1cfd5a59cb35aaTimo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen gnutls_alert_send_appropriate(proxy->session, error);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen ssl_proxy_destroy(proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return -1;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen}
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic int proxy_recv_ssl(struct ssl_proxy *proxy, void *data, size_t size)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen{
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen int rcvd;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen rcvd = gnutls_record_recv(proxy->session, data, size);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (rcvd > 0)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return rcvd;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (rcvd == 0 || rcvd == GNUTLS_E_UNEXPECTED_PACKET_LENGTH) {
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen /* disconnected, either by nicely telling us that we'll
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen close the connection, or by simply killing the
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen connection which gives us the packet length error. */
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen ssl_proxy_destroy(proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return -1;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen }
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return handle_ssl_error(proxy, rcvd);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen}
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic int proxy_send_ssl(struct ssl_proxy *proxy,
8c909e451d14075c05d90382cf8eebc4e354f569Timo Sirainen const void *data, size_t size)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen{
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen int sent;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen sent = gnutls_record_send(proxy->session, data, size);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (sent >= 0)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return sent;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (sent == GNUTLS_E_PUSH_ERROR || sent == GNUTLS_E_INVALID_SESSION) {
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen /* don't warn about errors related to unexpected
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen disconnection */
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen ssl_proxy_destroy(proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return -1;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen }
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return handle_ssl_error(proxy, sent);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen}
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic int ssl_proxy_destroy(struct ssl_proxy *proxy)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen{
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (--proxy->refcount > 0)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return TRUE;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen hash_remove(ssl_proxies, proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen gnutls_deinit(proxy->session);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (proxy->io_ssl != NULL)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen io_remove(proxy->io_ssl);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (proxy->io_plain != NULL)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen io_remove(proxy->io_plain);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen (void)net_disconnect(proxy->fd_ssl);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen (void)net_disconnect(proxy->fd_plain);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen i_free(proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen main_unref();
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return FALSE;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen}
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic void ssl_output(struct ssl_proxy *proxy)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen{
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen int sent;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
9a1f68e5ab08eabd352d533315cba1c69006e2c1Timo Sirainen sent = net_transmit(proxy->fd_plain,
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen proxy->outbuf_plain + proxy->outbuf_pos_plain,
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen proxy->send_left_plain);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (sent < 0) {
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen /* disconnected */
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen ssl_proxy_destroy(proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen }
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
16cb5d65265dd0b216542803fd80c4b999ae118eTimo Sirainen proxy->send_left_plain -= sent;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen proxy->outbuf_pos_plain += sent;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (proxy->send_left_plain > 0)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen /* everything is sent, start reading again */
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen io_remove(proxy->io_ssl);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen proxy->io_ssl = io_add(proxy->fd_ssl, IO_READ, ssl_input, proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen}
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic void ssl_input(struct ssl_proxy *proxy)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen{
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen int rcvd, sent;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen rcvd = proxy_recv_ssl(proxy, proxy->outbuf_plain,
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen sizeof(proxy->outbuf_plain));
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (rcvd <= 0)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen sent = net_transmit(proxy->fd_plain, proxy->outbuf_plain, (size_t)rcvd);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (sent == rcvd)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (sent < 0) {
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen /* disconnected */
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen ssl_proxy_destroy(proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen }
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen /* everything wasn't sent - don't read anything until we've
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen sent it all */
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen proxy->outbuf_pos_plain = 0;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen proxy->send_left_plain = rcvd - sent;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen io_remove(proxy->io_ssl);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen proxy->io_ssl = io_add(proxy->fd_ssl, IO_WRITE, ssl_output, proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen}
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic void plain_output(struct ssl_proxy *proxy)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen{
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen int sent;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen sent = proxy_send_ssl(proxy, NULL, proxy->send_left_ssl);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (sent <= 0)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen proxy->send_left_ssl -= sent;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (proxy->send_left_ssl > 0)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
56aa97d74071f3a2987140c2ff1cfd5a59cb35aaTimo Sirainen /* everything is sent, start reading again */
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen io_remove(proxy->io_plain);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen proxy->io_plain = io_add(proxy->fd_plain, IO_READ, plain_input, proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen}
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic void plain_input(struct ssl_proxy *proxy)
c6afd726060aae56b6622c6c52aec10231c4bf1cTimo Sirainen{
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen char buf[1024];
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen ssize_t rcvd, sent;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen rcvd = net_receive(proxy->fd_plain, buf, sizeof(buf));
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (rcvd < 0) {
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen /* disconnected */
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen gnutls_bye(proxy->session, 1);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen ssl_proxy_destroy(proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen }
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen sent = proxy_send_ssl(proxy, buf, (size_t)rcvd);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (sent < 0 || sent == rcvd)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen /* everything wasn't sent - don't read anything until we've
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen sent it all */
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen proxy->send_left_ssl = rcvd - sent;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen io_remove(proxy->io_plain);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen proxy->io_plain = io_add(proxy->fd_ssl, IO_WRITE, plain_output, proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen}
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
62041dfb7d6ac6e9c633a557075999cdfcff7bd5Timo Sirainenstatic void ssl_handshake(struct ssl_proxy *proxy)
62041dfb7d6ac6e9c633a557075999cdfcff7bd5Timo Sirainen{
62041dfb7d6ac6e9c633a557075999cdfcff7bd5Timo Sirainen int ret, dir;
62041dfb7d6ac6e9c633a557075999cdfcff7bd5Timo Sirainen
62041dfb7d6ac6e9c633a557075999cdfcff7bd5Timo Sirainen ret = gnutls_handshake(proxy->session);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (ret >= 0) {
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen /* handshake done, now we can start reading */
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (proxy->io_ssl != NULL)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen io_remove(proxy->io_ssl);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen proxy->io_plain = io_add(proxy->fd_plain, IO_READ,
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen plain_input, proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen proxy->io_ssl = io_add(proxy->fd_ssl, IO_READ,
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen ssl_input, proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen }
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (handle_ssl_error(proxy, ret) < 0)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen return;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen /* i/o interrupted */
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen dir = gnutls_record_get_direction(proxy->session) == 0 ?
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen IO_READ : IO_WRITE;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (proxy->io_ssl_dir != dir) {
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen if (proxy->io_ssl != NULL)
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen io_remove(proxy->io_ssl);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen proxy->io_ssl = io_add(proxy->fd_ssl, dir,
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen ssl_handshake, proxy);
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen proxy->io_ssl_dir = dir;
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen }
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen}
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainen
01435c38e7d671d5a892c4b802cfb204881cd454Timo Sirainenstatic 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)
return;
T_FRAME(
i_error("gcrypt fatal: %s", t_strdup_vprintf(fmt, args));
);
}
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