ssl-proxy-gnutls.c revision 183bea41fa640dc8117f3eb45ff935cd81377a84
76b43e4417bab52e913da39b5f5bc2a130d3f149Timo Sirainen/* Copyright (c) 2002-2011 Dovecot authors, see the included COPYING file */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include "login-common.h"
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include "ioloop.h"
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include "network.h"
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen#include "hash.h"
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include "ssl-proxy.h"
00d58fcfe8191d6ce7efa801d289a5c0fe88d1aeTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#ifdef HAVE_GNUTLS
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#error broken currently
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include <stdio.h>
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include <stdlib.h>
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include <unistd.h>
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include <fcntl.h>
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen#include <gcrypt.h>
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen#include <gnutls/gnutls.h>
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstruct ssl_proxy {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen int refcount;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gnutls_session session;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen struct ip_addr ip;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen int fd_ssl, fd_plain;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen struct io *io_ssl, *io_plain;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen int io_ssl_dir;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen unsigned char outbuf_plain[1024];
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen unsigned int outbuf_pos_plain;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen size_t send_left_ssl, send_left_plain;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen};
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainenconst int protocol_priority[] =
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenconst int kx_priority[] =
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen { GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, 0 };
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenconst int cipher_priority[] =
f6c1297c26b355c4aec2a08978f51ec3efecb351Timo Sirainen { GNUTLS_CIPHER_RIJNDAEL_CBC, GNUTLS_CIPHER_3DES_CBC,
f6c1297c26b355c4aec2a08978f51ec3efecb351Timo Sirainen GNUTLS_CIPHER_ARCFOUR_128, GNUTLS_CIPHER_ARCFOUR_40, 0 };
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenconst int comp_priority[] =
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen { GNUTLS_COMP_LZO, GNUTLS_COMP_ZLIB, GNUTLS_COMP_NULL, 0 };
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainenconst int mac_priority[] =
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen { GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0 };
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenconst int cert_type_priority[] =
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen { GNUTLS_CRT_X509, 0 };
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic struct hash_table *ssl_proxies;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic gnutls_certificate_credentials x509_cred;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic gnutls_dh_params dh_params;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic gnutls_rsa_params rsa_params;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic void ssl_input(struct ssl_proxy *proxy);
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainenstatic void plain_input(struct ssl_proxy *proxy);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic bool ssl_proxy_destroy(struct ssl_proxy *proxy);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainenstatic const char *get_alert_text(struct ssl_proxy *proxy)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return gnutls_alert_get_name(gnutls_alert_get(proxy->session));
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen}
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainenstatic int handle_ssl_error(struct ssl_proxy *proxy, int error)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (!gnutls_error_is_fatal(error)) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (!verbose_ssl)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return 0;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen if (error == GNUTLS_E_WARNING_ALERT_RECEIVED) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen i_warning("Received SSL warning alert: %s [%s]",
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen get_alert_text(proxy),
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen net_ip2addr(&proxy->ip));
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen } else {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen i_warning("Non-fatal SSL error: %s: %s",
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen get_alert_text(proxy),
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen net_ip2addr(&proxy->ip));
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen return 0;
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen }
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen if (verbose_ssl) {
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen /* fatal error occurred */
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen if (error == GNUTLS_E_FATAL_ALERT_RECEIVED) {
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen i_warning("Received SSL fatal alert: %s [%s]",
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen get_alert_text(proxy),
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen net_ip2addr(&proxy->ip));
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen } else {
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen i_warning("Error reading from SSL client: %s [%s]",
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen gnutls_strerror(error),
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen net_ip2addr(&proxy->ip));
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
b04e691711fd026fc82ba3e0b411420e7da4ec7eTimo Sirainen gnutls_alert_send_appropriate(proxy->session, error);
4d2211dac61c615c5bdfd501ea54d46c89d41b0fTimo Sirainen ssl_proxy_destroy(proxy);
4d2211dac61c615c5bdfd501ea54d46c89d41b0fTimo Sirainen return -1;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen}
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic int proxy_recv_ssl(struct ssl_proxy *proxy, void *data, size_t size)
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen{
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen int rcvd;
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen rcvd = gnutls_record_recv(proxy->session, data, size);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (rcvd > 0)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return rcvd;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen if (rcvd == 0 || rcvd == GNUTLS_E_UNEXPECTED_PACKET_LENGTH) {
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen /* disconnected, either by nicely telling us that we'll
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen close the connection, or by simply killing the
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen connection which gives us the packet length error. */
4d2211dac61c615c5bdfd501ea54d46c89d41b0fTimo Sirainen ssl_proxy_destroy(proxy);
63f36c2b47217fc2dc4ed49cfc1907311d5ed366Timo Sirainen return -1;
b04e691711fd026fc82ba3e0b411420e7da4ec7eTimo Sirainen }
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return handle_ssl_error(proxy, rcvd);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen}
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainenstatic int proxy_send_ssl(struct ssl_proxy *proxy,
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen const void *data, size_t size)
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen{
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen int sent;
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen sent = gnutls_record_send(proxy->session, data, size);
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen if (sent >= 0)
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen return sent;
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen if (sent == GNUTLS_E_PUSH_ERROR || sent == GNUTLS_E_INVALID_SESSION) {
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen /* don't warn about errors related to unexpected
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen disconnection */
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen ssl_proxy_destroy(proxy);
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen return -1;
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen }
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen return handle_ssl_error(proxy, sent);
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen}
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainenstatic int ssl_proxy_destroy(struct ssl_proxy *proxy)
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen{
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen if (--proxy->refcount > 0)
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen return TRUE;
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen hash_table_remove(ssl_proxies, proxy);
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen gnutls_deinit(proxy->session);
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen if (proxy->io_ssl != NULL)
19e8adccba16ff419f5675b1575358c2956dce83Timo Sirainen io_remove(proxy->io_ssl);
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen if (proxy->io_plain != NULL)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen io_remove(proxy->io_plain);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen (void)net_disconnect(proxy->fd_ssl);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen (void)net_disconnect(proxy->fd_plain);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen i_free(proxy);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen main_unref();
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return FALSE;
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen}
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic void ssl_output(struct ssl_proxy *proxy)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen int sent;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen sent = net_transmit(proxy->fd_plain,
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen proxy->outbuf_plain + proxy->outbuf_pos_plain,
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen proxy->send_left_plain);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (sent < 0) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* disconnected */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen ssl_proxy_destroy(proxy);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen proxy->send_left_plain -= sent;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen proxy->outbuf_pos_plain += sent;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (proxy->send_left_plain > 0)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* everything is sent, start reading again */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen io_remove(proxy->io_ssl);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen proxy->io_ssl = io_add(proxy->fd_ssl, IO_READ, ssl_input, proxy);
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen}
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic void ssl_input(struct ssl_proxy *proxy)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen int rcvd, sent;
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen rcvd = proxy_recv_ssl(proxy, proxy->outbuf_plain,
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen sizeof(proxy->outbuf_plain));
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen if (rcvd <= 0)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen sent = net_transmit(proxy->fd_plain, proxy->outbuf_plain, (size_t)rcvd);
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen if (sent == rcvd)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (sent < 0) {
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen /* disconnected */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen ssl_proxy_destroy(proxy);
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen return;
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen }
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* everything wasn't sent - don't read anything until we've
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen sent it all */
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen proxy->outbuf_pos_plain = 0;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen proxy->send_left_plain = rcvd - sent;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen io_remove(proxy->io_ssl);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen proxy->io_ssl = io_add(proxy->fd_ssl, IO_WRITE, ssl_output, proxy);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen}
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic void plain_output(struct ssl_proxy *proxy)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen int sent;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen sent = proxy_send_ssl(proxy, NULL, proxy->send_left_ssl);
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen if (sent <= 0)
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen return;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen proxy->send_left_ssl -= sent;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (proxy->send_left_ssl > 0)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* everything is sent, start reading again */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen io_remove(proxy->io_plain);
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen proxy->io_plain = io_add(proxy->fd_plain, IO_READ, plain_input, proxy);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen}
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic void plain_input(struct ssl_proxy *proxy)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen char buf[1024];
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen ssize_t rcvd, sent;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen rcvd = net_receive(proxy->fd_plain, buf, sizeof(buf));
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (rcvd < 0) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* disconnected */
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen gnutls_bye(proxy->session, 1);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen ssl_proxy_destroy(proxy);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen sent = proxy_send_ssl(proxy, buf, (size_t)rcvd);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (sent < 0 || sent == rcvd)
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen return;
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* everything wasn't sent - don't read anything until we've
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen sent it all */
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen proxy->send_left_ssl = rcvd - sent;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen io_remove(proxy->io_plain);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen proxy->io_plain = io_add(proxy->fd_ssl, IO_WRITE, plain_output, proxy);
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen}
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainenstatic void ssl_handshake(struct ssl_proxy *proxy)
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen{
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen int ret, dir;
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen ret = gnutls_handshake(proxy->session);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (ret >= 0) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* handshake done, now we can start reading */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (proxy->io_ssl != NULL)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen io_remove(proxy->io_ssl);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen proxy->io_plain = io_add(proxy->fd_plain, IO_READ,
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen plain_input, proxy);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen proxy->io_ssl = io_add(proxy->fd_ssl, IO_READ,
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen ssl_input, proxy);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return;
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen }
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (handle_ssl_error(proxy, ret) < 0)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen /* i/o interrupted */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen dir = gnutls_record_get_direction(proxy->session) == 0 ?
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen IO_READ : IO_WRITE;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (proxy->io_ssl_dir != dir) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (proxy->io_ssl != NULL)
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen io_remove(proxy->io_ssl);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen proxy->io_ssl = io_add(proxy->fd_ssl, dir,
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen ssl_handshake, proxy);
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen proxy->io_ssl_dir = dir;
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen}
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic gnutls_session initialize_state(void)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen gnutls_session session;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gnutls_init(&session, GNUTLS_SERVER);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gnutls_protocol_set_priority(session, protocol_priority);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gnutls_cipher_set_priority(session, cipher_priority);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gnutls_compression_set_priority(session, comp_priority);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gnutls_kx_set_priority(session, kx_priority);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gnutls_mac_set_priority(session, mac_priority);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gnutls_certificate_type_set_priority(session, cert_type_priority);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gnutls_cred_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return session;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen}
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenint ssl_proxy_new(int fd, struct ip_addr *ip)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen struct ssl_proxy *proxy;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gnutls_session session;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen 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_table_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_BEGIN {
i_error("gcrypt fatal: %s", t_strdup_vprintf(fmt, args));
} T_END;
}
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_table_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_table_iterate_init(ssl_proxies);
while (hash_table_iterate(iter, &key, &value))
ssl_proxy_destroy(value);
hash_table_iterate_deinit(iter);
hash_table_destroy(ssl_proxies);
gnutls_certificate_free_credentials(x509_cred);
gnutls_global_deinit();
}
#endif