ssl-proxy-gnutls.c revision 2e37d45867d081db150ab78dad303b9077aea24f
a8c5a86d183db25a57bf193c06b41e092ec2e151Timo Sirainen/* Copyright (c) 2002-2011 Dovecot authors, see the included COPYING file */
cd56a23e21f1df3f79648cf07e2f4385e2fadebbTimo Sirainen { GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, 0 };
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen { GNUTLS_CIPHER_RIJNDAEL_CBC, GNUTLS_CIPHER_3DES_CBC,
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen GNUTLS_CIPHER_ARCFOUR_128, GNUTLS_CIPHER_ARCFOUR_40, 0 };
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen { GNUTLS_COMP_LZO, GNUTLS_COMP_ZLIB, GNUTLS_COMP_NULL, 0 };
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainenstatic gnutls_certificate_credentials x509_cred;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenstatic void ssl_input(struct ssl_proxy *proxy);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenstatic void plain_input(struct ssl_proxy *proxy);
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainenstatic bool ssl_proxy_destroy(struct ssl_proxy *proxy);
5ac0b0bf32898c63da086ae169674ecac151a31eTimo Sirainenstatic const char *get_alert_text(struct ssl_proxy *proxy)
5ac0b0bf32898c63da086ae169674ecac151a31eTimo Sirainen return gnutls_alert_get_name(gnutls_alert_get(proxy->session));
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainenstatic int handle_ssl_error(struct ssl_proxy *proxy, int error)
5ac0b0bf32898c63da086ae169674ecac151a31eTimo Sirainen if (error == GNUTLS_E_WARNING_ALERT_RECEIVED) {
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen i_warning("Received SSL warning alert: %s [%s]",
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen /* fatal error occurred */
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen i_warning("Received SSL fatal alert: %s [%s]",
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen i_warning("Error reading from SSL client: %s [%s]",
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen gnutls_alert_send_appropriate(proxy->session, error);
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainenstatic int proxy_recv_ssl(struct ssl_proxy *proxy, void *data, size_t size)
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen rcvd = gnutls_record_recv(proxy->session, data, size);
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen if (rcvd == 0 || rcvd == GNUTLS_E_UNEXPECTED_PACKET_LENGTH) {
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen /* disconnected, either by nicely telling us that we'll
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen close the connection, or by simply killing the
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen connection which gives us the packet length error. */
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainenstatic int proxy_send_ssl(struct ssl_proxy *proxy,
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen sent = gnutls_record_send(proxy->session, data, size);
0a49b316fc729e5d57268ffa63c7122ac73f994cTimo Sirainen if (sent == GNUTLS_E_PUSH_ERROR || sent == GNUTLS_E_INVALID_SESSION) {
0a49b316fc729e5d57268ffa63c7122ac73f994cTimo Sirainen /* don't warn about errors related to unexpected
0a49b316fc729e5d57268ffa63c7122ac73f994cTimo Sirainen disconnection */
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainenstatic int ssl_proxy_destroy(struct ssl_proxy *proxy)
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainenstatic void ssl_output(struct ssl_proxy *proxy)
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen proxy->outbuf_plain + proxy->outbuf_pos_plain,
0a0cd45a633112a2ae6aad801c1e6afe53ab95deTimo Sirainen /* disconnected */
c24ef531ca58abad996482f5c2e8992be9ae8981Timo Sirainen /* everything is sent, start reading again */
0a0cd45a633112a2ae6aad801c1e6afe53ab95deTimo Sirainen proxy->io_ssl = io_add(proxy->fd_ssl, IO_READ, ssl_input, proxy);
0892446b45c195461bb7be6599f02d97e1e2c9b2Timo Sirainen rcvd = proxy_recv_ssl(proxy, proxy->outbuf_plain,
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen sent = net_transmit(proxy->fd_plain, proxy->outbuf_plain, (size_t)rcvd);
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen /* disconnected */
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen /* everything wasn't sent - don't read anything until we've
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen sent it all */
66dc739bb67d678770e1b7a7bc75f4f6f9523d2aTimo Sirainen proxy->io_ssl = io_add(proxy->fd_ssl, IO_WRITE, ssl_output, proxy);
66dc739bb67d678770e1b7a7bc75f4f6f9523d2aTimo Sirainenstatic void plain_output(struct ssl_proxy *proxy)
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen sent = proxy_send_ssl(proxy, NULL, proxy->send_left_ssl);
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen /* everything is sent, start reading again */
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen proxy->io_plain = io_add(proxy->fd_plain, IO_READ, plain_input, proxy);
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainenstatic void plain_input(struct ssl_proxy *proxy)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen rcvd = net_receive(proxy->fd_plain, buf, sizeof(buf));
20344c0e814139e3c365fbb9287478f91512089eTimo Sirainen /* disconnected */
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen sent = proxy_send_ssl(proxy, buf, (size_t)rcvd);
20344c0e814139e3c365fbb9287478f91512089eTimo Sirainen /* everything wasn't sent - don't read anything until we've
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen sent it all */
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen proxy->io_plain = io_add(proxy->fd_ssl, IO_WRITE, plain_output, proxy);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenstatic void ssl_handshake(struct ssl_proxy *proxy)
e3fc1874694a8ddba9552ec23f9952f74f33d1d5Timo Sirainen /* handshake done, now we can start reading */
e3fc1874694a8ddba9552ec23f9952f74f33d1d5Timo Sirainen proxy->io_plain = io_add(proxy->fd_plain, IO_READ,
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen proxy->io_ssl = io_add(proxy->fd_ssl, IO_READ,
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen /* i/o interrupted */
601f5f14c6cde28f0e0c6ca7c5d735315d3d48dfTimo Sirainen dir = gnutls_record_get_direction(proxy->session) == 0 ?
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen gnutls_protocol_set_priority(session, protocol_priority);
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen gnutls_cipher_set_priority(session, cipher_priority);
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen gnutls_compression_set_priority(session, comp_priority);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen gnutls_mac_set_priority(session, mac_priority);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen gnutls_certificate_type_set_priority(session, cert_type_priority);
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen gnutls_cred_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen i_error("SSL support not enabled in configuration");
548e394330621952db0f03dd667b70184c4a37b6Timo Sirainen if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) == -1) {
a3c197999dfe2b0c8ea38cb77cfa5e95026005c0Timo Sirainen /* handshake failed. return the disconnected socket anyway
a3c197999dfe2b0c8ea38cb77cfa5e95026005c0Timo Sirainen so the caller doesn't try to use the old closed fd */
923115fd382904fa13bb09bf307bf2835b52df60Timo Sirainenstatic void read_next_field(int fd, gnutls_datum *datum,
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen /* get size */
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen ret = read(fd, &datum->size, sizeof(datum->size));
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen i_fatal("Corrupted SSL parameter file %s: File too small",
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen "Field '%s' too large (%u)",
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen /* read the actual data */
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen "Field '%s' not fully in file (%u < %u)",
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen fname, field_name, datum->size - ret, datum->size);
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainenstatic void read_dh_parameters(int fd, const char *fname)
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen if ((ret = gnutls_dh_params_init(&dh_params)) < 0) {
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen /* read until bits field is 0 */
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen read_next_field(fd, &dbits, fname, "DH bits");
df16c7e87511fed827e6890a2a47d13ca48716deTimo Sirainen "Field 'DH bits' has invalid size %u",
7b42d6cbee8186195d8c5e66078043a0fa1f25c1Timo Sirainen read_next_field(fd, &prime, fname, "DH prime");
7b42d6cbee8186195d8c5e66078043a0fa1f25c1Timo Sirainen read_next_field(fd, &generator, fname, "DH generator");
7b42d6cbee8186195d8c5e66078043a0fa1f25c1Timo Sirainen ret = gnutls_dh_params_set(dh_params, prime, generator, bits);
89e195dfb5c4b0efd9b9f459771a4467674e5b1fTimo Sirainenstatic void read_rsa_parameters(int fd, const char *fname)
1036ad17ac837a451f6b045cac504d3efa2edb8eTimo Sirainen gnutls_datum m, e, d, p, q, u;
a0b6b441fc679e562e79be0fb2819ffc24ab5b74Timo Sirainen if ((ret = gnutls_rsa_params_init(&rsa_params)) < 0) {
a0b6b441fc679e562e79be0fb2819ffc24ab5b74Timo Sirainen i_fatal("gnutls_rsa_params_init() failed: %s",
a0b6b441fc679e562e79be0fb2819ffc24ab5b74Timo Sirainen /* only 512bit is allowed */
1036ad17ac837a451f6b045cac504d3efa2edb8eTimo Sirainen ret = gnutls_rsa_params_set(rsa_params, m, e, d, p, q, u, 512);
f5982bb5b0a704e88fa2b44b0b74e365d13103b9Timo Sirainen /* we'll wait until parameter file exists */
f5982bb5b0a704e88fa2b44b0b74e365d13103b9Timo Sirainen i_fatal("Can't open SSL parameter file %s: %m", fname);
68a4946b12583b88fa802e52ebee45cd96056772Timo Sirainenstatic void gcrypt_log_handler(void *context ATTR_UNUSED, int level,
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen i_error("gcrypt fatal: %s", t_strdup_vprintf(fmt, args));
f5982bb5b0a704e88fa2b44b0b74e365d13103b9Timo Sirainen if (certfile == NULL || keyfile == NULL || paramfile == NULL) {
f5982bb5b0a704e88fa2b44b0b74e365d13103b9Timo Sirainen /* SSL support is disabled */
4106a25399703eb6cbb166dcbd5bb932cb2f7ad2Timo Sirainen /* gcrypt initialization - set log handler and make sure randomizer
f5982bb5b0a704e88fa2b44b0b74e365d13103b9Timo Sirainen opens /dev/urandom now instead of after we've chrooted */
f5982bb5b0a704e88fa2b44b0b74e365d13103b9Timo Sirainen gcry_set_log_handler(gcrypt_log_handler, NULL);
f5982bb5b0a704e88fa2b44b0b74e365d13103b9Timo Sirainen gcry_randomize(buf, sizeof(buf), GCRY_STRONG_RANDOM);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) {
4106a25399703eb6cbb166dcbd5bb932cb2f7ad2Timo Sirainen i_fatal("gnutls_certificate_allocate_credentials() failed: %s",
f5982bb5b0a704e88fa2b44b0b74e365d13103b9Timo Sirainen ret = gnutls_certificate_set_x509_key_file(x509_cred, certfile, keyfile,
3785910c303507db5f629684e6dde2cc7f83668eTimo Sirainen i_fatal("Can't load certificate files %s and %s: %s",
4106a25399703eb6cbb166dcbd5bb932cb2f7ad2Timo Sirainen gnutls_certificate_set_dh_params(x509_cred, dh_params);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen gnutls_certificate_set_rsa_export_params(x509_cred, rsa_params);
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen ssl_proxies = hash_table_create(system_pool, system_pool, 0,
37ab3cde96bfa4bc5304c0c348fc420aec79572dTimo Sirainen while (hash_table_iterate(iter, &key, &value))