]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
imap/pop3 proxy: Support SSL/TLS connections to remote servers.
authorTimo Sirainen <tss@iki.fi>
Wed, 29 Apr 2009 02:55:03 +0000 (22:55 -0400)
committerTimo Sirainen <tss@iki.fi>
Wed, 29 Apr 2009 02:55:03 +0000 (22:55 -0400)
passdb can return ssl=yes, ssl=any-cert and starttls options.

--HG--
branch : HEAD

13 files changed:
src/imap-login/client-authenticate.c
src/imap-login/client.h
src/imap-login/imap-proxy.c
src/imap-login/imap-proxy.h
src/login-common/login-proxy.c
src/login-common/login-proxy.h
src/login-common/ssl-proxy-openssl.c
src/login-common/ssl-proxy.c
src/login-common/ssl-proxy.h
src/pop3-login/client-authenticate.c
src/pop3-login/client.h
src/pop3-login/pop3-proxy.c
src/pop3-login/pop3-proxy.h

index 47b95069c8d5278808448aa5ec244feb8ed7efb9..9df8b001b1bf34b59857c3d908ef07ec8b42d1bc 100644 (file)
@@ -124,6 +124,8 @@ static bool client_handle_args(struct imap_client *client,
 {
        const char *reason = NULL, *host = NULL, *destuser = NULL, *pass = NULL;
        const char *master_user = NULL;
+       const char *key, *value, *p;
+       enum login_proxy_ssl_flags ssl_flags = 0;
        string_t *reply;
        unsigned int port = 143;
        bool proxy = FALSE, temp = FALSE, nologin = !success;
@@ -131,34 +133,40 @@ static bool client_handle_args(struct imap_client *client,
 
        *nodelay_r = FALSE;
        for (; *args != NULL; args++) {
-               if (strcmp(*args, "nologin") == 0)
+               p = strchr(*args, '=');
+               if (p == NULL) {
+                       key = *args;
+                       value = "";
+               } else {
+                       key = t_strdup_until(*args, p);
+                       value = p + 1;
+               }
+               if (strcmp(key, "nologin") == 0)
                        nologin = TRUE;
-               else if (strcmp(*args, "nodelay") == 0)
+               else if (strcmp(key, "nodelay") == 0)
                        *nodelay_r = TRUE;
-               else if (strcmp(*args, "proxy") == 0)
+               else if (strcmp(key, "proxy") == 0)
                        proxy = TRUE;
-               else if (strcmp(*args, "temp") == 0)
+               else if (strcmp(key, "temp") == 0)
                        temp = TRUE;
-               else if (strcmp(*args, "authz") == 0)
+               else if (strcmp(key, "authz") == 0)
                        authz_failure = TRUE;
-               else if (strncmp(*args, "reason=", 7) == 0)
-                       reason = *args + 7;
-               else if (strncmp(*args, "host=", 5) == 0)
-                       host = *args + 5;
-               else if (strncmp(*args, "port=", 5) == 0)
-                       port = atoi(*args + 5);
-               else if (strncmp(*args, "destuser=", 9) == 0)
-                       destuser = *args + 9;
-               else if (strncmp(*args, "pass=", 5) == 0)
-                       pass = *args + 5;
-               else if (strncmp(*args, "master=", 7) == 0)
-                       master_user = *args + 7;
-               else if (strncmp(*args, "user=", 5) == 0) {
+               else if (strcmp(key, "reason") == 0)
+                       reason = value;
+               else if (strcmp(key, "host") == 0)
+                       host = value;
+               else if (strcmp(key, "port") == 0)
+                       port = atoi(value);
+               else if (strcmp(key, "destuser") == 0)
+                       destuser = value;
+               else if (strcmp(key, "pass") == 0)
+                       pass = value;
+               else if (strcmp(key, "master") == 0)
+                       master_user = value;
+               else if (strcmp(key, "user") == 0) {
                        /* already handled in login-common */
-               } else if (login_settings->auth_debug) {
-                       i_info("Ignoring unknown passdb extra field: %s",
-                              *args);
-               }
+               } else if (login_settings->auth_debug)
+                       i_info("Ignoring unknown passdb extra field: %s", key);
        }
 
        if (destuser == NULL)
@@ -173,7 +181,7 @@ static bool client_handle_args(struct imap_client *client,
                if (!success)
                        return FALSE;
                if (imap_proxy_new(client, host, port, destuser, master_user,
-                                  pass) < 0)
+                                  pass, ssl_flags) < 0)
                        client_auth_failed(client, TRUE);
                return TRUE;
        }
index 09c4e53f590892172e0c6cd451d3aa658790d570..1de2696017f93a2b34e625ffb9401e0235cca458 100644 (file)
@@ -29,7 +29,8 @@ struct imap_client {
 
        unsigned int login_success:1;
        unsigned int cmd_finished:1;
-       unsigned int proxy_login_sent:1;
+       unsigned int proxy_sasl_ir:1;
+       unsigned int proxy_seen_banner:1;
        unsigned int skip_line:1;
        unsigned int input_blocked:1;
        unsigned int destroyed:1;
index d3c0338c3310849583d16c7ea4f226241168ceb6..fcd7737bcca962461d3bb22d391a3c3b88588ffe 100644 (file)
@@ -136,9 +136,35 @@ client_send_capability_if_needed(struct imap_client *client, string_t *str,
        str_printfa(str, "* CAPABILITY %s\r\n", capability);
 }
 
+static void proxy_write_login(struct imap_client *client, string_t *str)
+{
+       if (client->capability_command_used)
+               str_append(str, "C CAPABILITY\r\n");
+
+       if (client->proxy_master_user == NULL) {
+               /* logging in normally - use LOGIN command */
+               str_append(str, "L LOGIN ");
+               imap_quote_append_string(str, client->proxy_user, FALSE);
+               str_append_c(str, ' ');
+               imap_quote_append_string(str, client->proxy_password, FALSE);
+
+               proxy_free_password(client);
+       } else if (client->proxy_sasl_ir) {
+               /* master user login with SASL initial response support */
+               str_append(str, "L AUTHENTICATE PLAIN ");
+               get_plain_auth(client, str);
+               proxy_free_password(client);
+       } else {
+               /* master user login without SASL initial response */
+               str_append(str, "L AUTHENTICATE PLAIN");
+       }
+       str_append(str, "\r\n");
+}
+
 static int proxy_input_banner(struct imap_client *client,
                              struct ostream *output, const char *line)
 {
+       enum login_proxy_ssl_flags ssl_flags;
        const char *const *capabilities = NULL;
        string_t *str;
 
@@ -154,44 +180,39 @@ static int proxy_input_banner(struct imap_client *client,
                capabilities = t_strsplit(t_strcut(line + 5 + 12, ']'), " ");
                if (str_array_icase_find(capabilities, "ID"))
                        proxy_write_id(client, str);
+               if (str_array_icase_find(capabilities, "SASL-IR"))
+                       client->proxy_sasl_ir = TRUE;
        }
-       if (client->capability_command_used)
-               str_append(str, "C CAPABILITY\r\n");
 
-       if (client->proxy_master_user == NULL) {
-               /* logging in normally - use LOGIN command */
-               str_append(str, "L LOGIN ");
-               imap_quote_append_string(str, client->proxy_user, FALSE);
-               str_append_c(str, ' ');
-               imap_quote_append_string(str, client->proxy_password, FALSE);
-
-               proxy_free_password(client);
-       } else if (capabilities != NULL &&
-                  str_array_icase_find(capabilities, "SASL-IR")) {
-               /* master user login with SASL initial response support */
-               str_append(str, "L AUTHENTICATE PLAIN ");
-               get_plain_auth(client, str);
-               proxy_free_password(client);
+       ssl_flags = login_proxy_get_ssl_flags(client->proxy);
+       if ((ssl_flags & PROXY_SSL_FLAG_STARTTLS) != 0) {
+               if (capabilities != NULL &&
+                   !str_array_icase_find(capabilities, "STARTTLS")) {
+                       client_syslog_err(&client->common,
+                               "proxy: Remote doesn't support STARTTLS");
+                       return -1;
+               }
+               str_append(str, "S STARTTLS\r\n");
        } else {
-               /* master user login without SASL initial response */
-               str_append(str, "L AUTHENTICATE PLAIN");
+               proxy_write_login(client, str);
        }
-       str_append(str, "\r\n");
+
        (void)o_stream_send(output, str_data(str), str_len(str));
-       client->proxy_login_sent = TRUE;
        return 0;
 }
 
-static int proxy_input_line(struct imap_client *client,
-                           struct ostream *output, const char *line)
+static int proxy_input_line(struct imap_client *client, const char *line)
 {
+       struct ostream *output;
        const char *capability;
        string_t *str;
 
        i_assert(!client->destroyed);
 
-       if (!client->proxy_login_sent) {
+       output = login_proxy_get_ostream(client->proxy);
+       if (!client->proxy_seen_banner) {
                /* this is a banner */
+               client->proxy_seen_banner = TRUE;
                if (proxy_input_banner(client, output, line) < 0) {
                        proxy_failed(client, TRUE);
                        return -1;
@@ -206,6 +227,26 @@ static int proxy_input_line(struct imap_client *client,
 
                (void)o_stream_send(output, str_data(str), str_len(str));
                return 0;
+       } else if (strncmp(line, "S ", 2) == 0) {
+               if (strncmp(line, "S OK ", 5) != 0) {
+                       /* STARTTLS failed */
+                       client_syslog_err(&client->common, t_strdup_printf(
+                               "proxy: Remote STARTTLS failed: %s",
+                               str_sanitize(line + 5, 160)));
+                       proxy_failed(client, TRUE);
+                       return -1;
+               }
+               /* STARTTLS successful, begin TLS negotiation. */
+               if (login_proxy_starttls(client->proxy) < 0) {
+                       proxy_failed(client, TRUE);
+                       return -1;
+               }
+               /* i/ostreams changed. */
+               output = login_proxy_get_ostream(client->proxy);
+               str = t_str_new(128);
+               proxy_write_login(client, str);
+               (void)o_stream_send(output, str_data(str), str_len(str));
+               return 1;
        } else if (strncmp(line, "L OK ", 5) == 0) {
                /* Login successful. Send this line to client. */
                capability = client->proxy_backend_capability;
@@ -307,17 +348,18 @@ static int proxy_input_line(struct imap_client *client,
        }
 }
 
-static void proxy_input(struct istream *input, struct ostream *output,
-                       struct imap_client *client)
+static void proxy_input(struct imap_client *client)
 {
+       struct istream *input;
        const char *line;
 
-       if (input == NULL) {
-               if (client->proxy == NULL) {
-                       /* we're just freeing the proxy */
-                       return;
-               }
+       if (client->proxy == NULL) {
+               /* we're just freeing the proxy */
+               return;
+       }
 
+       input = login_proxy_get_istream(client->proxy);
+       if (input == NULL) {
                if (client->destroyed) {
                        /* we came here from client_destroy() */
                        return;
@@ -344,14 +386,14 @@ static void proxy_input(struct istream *input, struct ostream *output,
        }
 
        while ((line = i_stream_next_line(input)) != NULL) {
-               if (proxy_input_line(client, output, line) != 0)
+               if (proxy_input_line(client, line) != 0)
                        break;
        }
 }
 
 int imap_proxy_new(struct imap_client *client, const char *host,
                   unsigned int port, const char *user, const char *master_user,
-                  const char *password)
+                  const char *password, enum login_proxy_ssl_flags ssl_flags)
 {
        i_assert(user != NULL);
        i_assert(!client->destroyed);
@@ -375,14 +417,15 @@ int imap_proxy_new(struct imap_client *client, const char *host,
                return -1;
        }
 
-       client->proxy = login_proxy_new(&client->common, host, port,
+       client->proxy = login_proxy_new(&client->common, host, port, ssl_flags,
                                        proxy_input, client);
        if (client->proxy == NULL) {
                client_send_tagline(client, PROXY_FAILURE_MSG);
                return -1;
        }
 
-       client->proxy_login_sent = FALSE;
+       client->proxy_sasl_ir = FALSE;
+       client->proxy_seen_banner = FALSE;
        client->proxy_user = i_strdup(user);
        client->proxy_master_user = i_strdup(master_user);
        client->proxy_password = i_strdup(password);
index b27b7b867c9af32637620f3793e063144ba43e64..1a5b708e2774523792938d4f18f9073627232840 100644 (file)
@@ -5,6 +5,6 @@
 
 int imap_proxy_new(struct imap_client *client, const char *hosts,
                   unsigned int port, const char *user, const char *master_user,
-                  const char *password);
+                  const char *password, enum login_proxy_ssl_flags ssl_flags);
 
 #endif
index 9941ff24d4bd3f9a6597482242360c35b1af5a92..c8017fa421f46b232ff03fef3c6efe239370d33a 100644 (file)
@@ -8,6 +8,7 @@
 #include "str-sanitize.h"
 #include "master-service.h"
 #include "client-common.h"
+#include "ssl-proxy.h"
 #include "login-proxy.h"
 
 #define MAX_PROXY_INPUT_SIZE 4096
 struct login_proxy {
        struct login_proxy *prev, *next;
 
+       struct client *prelogin_client;
        int client_fd, server_fd;
        struct io *client_io, *server_io;
        struct istream *server_input;
        struct ostream *client_output, *server_output;
        struct ip_addr ip;
+       struct ssl_proxy *ssl_proxy;
 
        char *host, *user;
        unsigned int port;
+       enum login_proxy_ssl_flags ssl_flags;
 
        proxy_callback_t *callback;
        void *context;
 
        unsigned int destroying:1;
+       unsigned int disconnecting:1;
 };
 
 static struct login_proxy *login_proxies = NULL;
@@ -107,8 +112,19 @@ static int proxy_client_output(struct login_proxy *proxy)
 
 static void proxy_prelogin_input(struct login_proxy *proxy)
 {
-       proxy->callback(proxy->server_input, proxy->server_output,
-                       proxy->context);
+       proxy->callback(proxy->context);
+}
+
+static void proxy_plain_connected(struct login_proxy *proxy)
+{
+       proxy->server_input =
+               i_stream_create_fd(proxy->server_fd, MAX_PROXY_INPUT_SIZE,
+                                  FALSE);
+       proxy->server_output =
+               o_stream_create_fd(proxy->server_fd, (size_t)-1, FALSE);
+
+       proxy->server_io =
+               io_add(proxy->server_fd, IO_READ, proxy_prelogin_input, proxy);
 }
 
 static void proxy_wait_connect(struct login_proxy *proxy)
@@ -123,21 +139,22 @@ static void proxy_wait_connect(struct login_proxy *proxy)
                return;
        }
 
-       /* connect successful */
-       proxy->server_input =
-               i_stream_create_fd(proxy->server_fd, MAX_PROXY_INPUT_SIZE,
-                                  FALSE);
-       proxy->server_output =
-               o_stream_create_fd(proxy->server_fd, (size_t)-1, FALSE);
-
-       io_remove(&proxy->server_io);
-       proxy->server_io =
-               io_add(proxy->server_fd, IO_READ, proxy_prelogin_input, proxy);
+       if ((proxy->ssl_flags & PROXY_SSL_FLAG_YES) != 0 &&
+           (proxy->ssl_flags & PROXY_SSL_FLAG_STARTTLS) == 0) {
+               if (login_proxy_starttls(proxy) < 0) {
+                       login_proxy_free(&proxy);
+                       return;
+               }
+       } else {
+               io_remove(&proxy->server_io);
+               proxy_plain_connected(proxy);
+       }
 }
 
 #undef login_proxy_new
 struct login_proxy *
 login_proxy_new(struct client *client, const char *host, unsigned int port,
+               enum login_proxy_ssl_flags ssl_flags,
                proxy_callback_t *callback, void *context)
 {
        struct login_proxy *proxy;
@@ -166,6 +183,8 @@ login_proxy_new(struct client *client, const char *host, unsigned int port,
        proxy->host = i_strdup(host);
        proxy->user = i_strdup(client->virtual_user);
        proxy->port = port;
+       proxy->ssl_flags = ssl_flags;
+       proxy->prelogin_client = client;
 
        proxy->server_fd = fd;
        proxy->server_io = io_add(fd, IO_WRITE, proxy_wait_connect, proxy);
@@ -189,6 +208,13 @@ void login_proxy_free(struct login_proxy **_proxy)
                return;
        proxy->destroying = TRUE;
 
+       if (proxy->server_io != NULL)
+               io_remove(&proxy->server_io);
+       if (proxy->server_input != NULL)
+               i_stream_destroy(&proxy->server_input);
+       if (proxy->server_output != NULL)
+               o_stream_destroy(&proxy->server_output);
+
        if (proxy->client_fd != -1) {
                /* detached proxy */
                DLLIST_REMOVE(&login_proxies, proxy);
@@ -207,15 +233,11 @@ void login_proxy_free(struct login_proxy **_proxy)
                i_assert(proxy->client_io == NULL);
                i_assert(proxy->client_output == NULL);
 
-               proxy->callback(NULL, NULL, proxy->context);
+               proxy->callback(proxy->context);
        }
 
-       if (proxy->server_io != NULL)
-               io_remove(&proxy->server_io);
-       if (proxy->server_input != NULL)
-               i_stream_destroy(&proxy->server_input);
-       if (proxy->server_output != NULL)
-               o_stream_destroy(&proxy->server_output);
+       if (proxy->ssl_proxy != NULL)
+               ssl_proxy_free(proxy->ssl_proxy);
        net_disconnect(proxy->server_fd);
 
        i_free(proxy->host);
@@ -241,6 +263,16 @@ bool login_proxy_is_ourself(const struct client *client, const char *host,
        return strcmp(client->virtual_user, destuser) == 0;
 }
 
+struct istream *login_proxy_get_istream(struct login_proxy *proxy)
+{
+       return proxy->disconnecting ? NULL : proxy->server_input;
+}
+
+struct ostream *login_proxy_get_ostream(struct login_proxy *proxy)
+{
+       return proxy->server_output;
+}
+
 const char *login_proxy_get_host(const struct login_proxy *proxy)
 {
        return proxy->host;
@@ -251,6 +283,12 @@ unsigned int login_proxy_get_port(const struct login_proxy *proxy)
        return proxy->port;
 }
 
+enum login_proxy_ssl_flags
+login_proxy_get_ssl_flags(const struct login_proxy *proxy)
+{
+       return proxy->ssl_flags;
+}
+
 void login_proxy_detach(struct login_proxy *proxy, struct istream *client_input,
                        struct ostream *client_output)
 {
@@ -260,6 +298,7 @@ void login_proxy_detach(struct login_proxy *proxy, struct istream *client_input,
        i_assert(proxy->client_fd == -1);
        i_assert(proxy->server_output != NULL);
 
+       proxy->prelogin_client = NULL;
        proxy->client_fd = i_stream_get_fd(client_input);
        proxy->client_output = client_output;
 
@@ -287,6 +326,52 @@ void login_proxy_detach(struct login_proxy *proxy, struct istream *client_input,
        DLLIST_PREPEND(&login_proxies, proxy);
 }
 
+static int login_proxy_ssl_handshaked(void *context)
+{
+       struct login_proxy *proxy = context;
+
+       if ((proxy->ssl_flags & PROXY_SSL_FLAG_ANY_CERT) != 0 ||
+           ssl_proxy_has_valid_client_cert(proxy->ssl_proxy))
+               return 0;
+
+       if (!ssl_proxy_has_broken_client_cert(proxy->ssl_proxy)) {
+               client_syslog_err(proxy->prelogin_client, t_strdup_printf(
+                       "proxy: SSL certificate not received from %s:%u",
+                       proxy->host, proxy->port));
+       } else {
+               client_syslog_err(proxy->prelogin_client, t_strdup_printf(
+                       "proxy: Received invalid SSL certificate from %s:%u",
+                       proxy->host, proxy->port));
+       }
+       proxy->disconnecting = TRUE;
+       return -1;
+}
+
+int login_proxy_starttls(struct login_proxy *proxy)
+{
+       int fd;
+
+       if (proxy->server_input != NULL)
+               i_stream_destroy(&proxy->server_input);
+       if (proxy->server_output != NULL)
+               o_stream_destroy(&proxy->server_output);
+       io_remove(&proxy->server_io);
+
+       fd = ssl_proxy_client_new(proxy->server_fd, &proxy->ip,
+                                 login_proxy_ssl_handshaked, proxy,
+                                 &proxy->ssl_proxy);
+       if (fd < 0) {
+               client_syslog_err(proxy->prelogin_client, t_strdup_printf(
+                       "proxy: SSL handshake failed to %s:%u",
+                       proxy->host, proxy->port));
+               return -1;
+       }
+
+       proxy->server_fd = fd;
+       proxy_plain_connected(proxy);
+       return 0;
+}
+
 void login_proxy_deinit(void)
 {
        struct login_proxy *proxy;
index 556ac42d096cfd950280dc8659adbfab735ad3eb..25d034c31453bf45cf5529e9d5024ff0389585e2 100644 (file)
@@ -3,24 +3,32 @@
 
 struct login_proxy;
 
+enum login_proxy_ssl_flags {
+       /* Use SSL/TLS enabled */
+       PROXY_SSL_FLAG_YES      = 0x01,
+       /* Don't do SSL handshake immediately after connected */
+       PROXY_SSL_FLAG_STARTTLS = 0x02,
+       /* Don't require that the received certificate is valid */
+       PROXY_SSL_FLAG_ANY_CERT = 0x04
+};
+
 /* Called when new input comes from proxy. */
-typedef void proxy_callback_t(struct istream *input, struct ostream *output,
-                             void *context);
+typedef void proxy_callback_t(void *context);
 
 /* Create a proxy to given host. Returns NULL if failed. Given callback is
    called when new input is available from proxy. */
 struct login_proxy *
 login_proxy_new(struct client *client, const char *host, unsigned int port,
+               enum login_proxy_ssl_flags ssl_flags,
                proxy_callback_t *callback, void *context);
 #ifdef CONTEXT_TYPE_SAFETY
-#  define login_proxy_new(client, host, port, callback, context) \
-       ({(void)(1 ? 0 : callback((struct istream *)NULL, \
-                                 (struct ostream *)NULL, context)); \
-         login_proxy_new(client, host, port, \
+#  define login_proxy_new(client, host, port, ssl_flags, callback, context) \
+       ({(void)(1 ? 0 : callback(context)); \
+         login_proxy_new(client, host, port, ssl_flags, \
                (proxy_callback_t *)callback, context); })
 #else
-#  define login_proxy_new(client, host, port, callback, context) \
-         login_proxy_new(client, host, port, \
+#  define login_proxy_new(client, host, port, ssl_flags, callback, context) \
+         login_proxy_new(client, host, port, ssl_flags, \
                (proxy_callback_t *)callback, context)
 #endif
 /* Free the proxy. This should be called if authentication fails. */
@@ -36,8 +44,16 @@ bool login_proxy_is_ourself(const struct client *client, const char *host,
 void login_proxy_detach(struct login_proxy *proxy, struct istream *client_input,
                        struct ostream *client_output);
 
+/* STARTTLS command was issued. */
+int login_proxy_starttls(struct login_proxy *proxy);
+
+struct istream *login_proxy_get_istream(struct login_proxy *proxy);
+struct ostream *login_proxy_get_ostream(struct login_proxy *proxy);
+
 const char *login_proxy_get_host(const struct login_proxy *proxy) ATTR_PURE;
 unsigned int login_proxy_get_port(const struct login_proxy *proxy) ATTR_PURE;
+enum login_proxy_ssl_flags
+login_proxy_get_ssl_flags(const struct login_proxy *proxy) ATTR_PURE;
 
 void login_proxy_deinit(void);
 
index dd81722893ff30786fdf4d0f036233ba315c6df0..b445cf860cb20793b8d855f6c467a6d6935efe42 100644 (file)
@@ -50,11 +50,15 @@ struct ssl_proxy {
        unsigned char sslout_buf[1024];
        unsigned int sslout_size;
 
+       ssl_handshake_callback_t *handshake_callback;
+       void *handshake_callback_context;
+
        char *last_error;
        unsigned int handshaked:1;
        unsigned int destroyed:1;
        unsigned int cert_received:1;
        unsigned int cert_broken:1;
+       unsigned int client:1;
 };
 
 struct ssl_parameters {
@@ -66,7 +70,8 @@ struct ssl_parameters {
 };
 
 static int extdata_index;
-static SSL_CTX *ssl_ctx;
+static SSL_CTX *ssl_server_ctx;
+static SSL_CTX *ssl_client_ctx;
 static unsigned int ssl_proxy_count;
 static struct ssl_proxy *ssl_proxies;
 static struct ssl_parameters ssl_params;
@@ -397,16 +402,27 @@ static void ssl_handshake(struct ssl_proxy *proxy)
 {
        int ret;
 
-       ret = SSL_accept(proxy->ssl);
-       if (ret != 1)
-               ssl_handle_error(proxy, ret, "SSL_accept()");
-       else {
-               i_free_and_null(proxy->last_error);
-               proxy->handshaked = TRUE;
-
-               ssl_set_io(proxy, SSL_ADD_INPUT);
-               plain_block_input(proxy, FALSE);
+       if (proxy->client) {
+               ret = SSL_connect(proxy->ssl);
+               if (ret != 1) {
+                       ssl_handle_error(proxy, ret, "SSL_connect()");
+                       return;
+               }
+       } else {
+               ret = SSL_accept(proxy->ssl);
+               if (ret != 1) {
+                       ssl_handle_error(proxy, ret, "SSL_accept()");
+                       return;
+               }
        }
+       i_free_and_null(proxy->last_error);
+       proxy->handshaked = TRUE;
+
+       ssl_set_io(proxy, SSL_ADD_INPUT);
+       plain_block_input(proxy, FALSE);
+
+       if (proxy->handshake_callback(proxy->handshake_callback_context) < 0)
+               ssl_proxy_destroy(proxy);
 }
 
 static void ssl_read(struct ssl_proxy *proxy)
@@ -474,7 +490,9 @@ static void ssl_step(struct ssl_proxy *proxy)
        ssl_proxy_unref(proxy);
 }
 
-int ssl_proxy_new(int fd, const struct ip_addr *ip, struct ssl_proxy **proxy_r)
+static int
+ssl_proxy_new_common(SSL_CTX *ssl_ctx, int fd, const struct ip_addr *ip,
+                    struct ssl_proxy **proxy_r)
 {
        struct ssl_proxy *proxy;
        SSL *ssl;
@@ -524,12 +542,37 @@ int ssl_proxy_new(int fd, const struct ip_addr *ip, struct ssl_proxy **proxy_r)
        ssl_proxy_count++;
        DLLIST_PREPEND(&ssl_proxies, proxy);
 
-       ssl_step(proxy);
-
        *proxy_r = proxy;
        return sfd[1];
 }
 
+int ssl_proxy_new(int fd, const struct ip_addr *ip, struct ssl_proxy **proxy_r)
+{
+       int ret;
+
+       if ((ret = ssl_proxy_new_common(ssl_server_ctx, fd, ip, proxy_r)) < 0)
+               return -1;
+
+       ssl_step(*proxy_r);
+       return ret;
+}
+
+int ssl_proxy_client_new(int fd, struct ip_addr *ip,
+                        ssl_handshake_callback_t *callback, void *context,
+                        struct ssl_proxy **proxy_r)
+{
+       int ret;
+
+       if ((ret = ssl_proxy_new_common(ssl_client_ctx, fd, ip, proxy_r)) < 0)
+               return -1;
+
+       (*proxy_r)->handshake_callback = callback;
+       (*proxy_r)->handshake_callback_context = context;
+       (*proxy_r)->client = TRUE;
+       ssl_step(*proxy_r);
+       return ret;
+}
+
 bool ssl_proxy_has_valid_client_cert(const struct ssl_proxy *proxy)
 {
        return proxy->cert_received && !proxy->cert_broken;
@@ -736,32 +779,11 @@ static bool is_pem_key_file(const char *path)
        return strstr(buf, "PRIVATE KEY---") != NULL;
 }
 
-void ssl_proxy_init(void)
+static void
+ssl_proxy_ctx_init(SSL_CTX *ssl_ctx, const struct login_settings *set)
 {
-       static char dovecot[] = "dovecot";
-       const struct login_settings *set = login_settings;
-       unsigned char buf;
-       char *password;
-       unsigned long err;
-
-       if (strcmp(set->ssl, "no") == 0)
-               return;
-
-       SSL_library_init();
-       SSL_load_error_strings();
-
-       extdata_index = SSL_get_ex_new_index(0, dovecot, NULL, NULL, NULL);
-
-       if ((ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL)
-               i_fatal("SSL_CTX_new() failed");
-
        SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL);
 
-       if (SSL_CTX_set_cipher_list(ssl_ctx, set->ssl_cipher_list) != 1) {
-               i_fatal("Can't set cipher list to '%s': %s",
-                       set->ssl_cipher_list, ssl_last_error());
-       }
-
        if (*set->ssl_ca_file != '\0') {
                if (SSL_CTX_load_verify_locations(ssl_ctx, set->ssl_ca_file,
                                                  NULL) != 1) {
@@ -769,8 +791,44 @@ void ssl_proxy_init(void)
                                set->ssl_ca_file, ssl_last_error());
                }
        }
+       if (set->verbose_ssl)
+               SSL_CTX_set_info_callback(ssl_ctx, ssl_info_callback);
+       if (SSL_CTX_need_tmp_RSA(ssl_ctx))
+               SSL_CTX_set_tmp_rsa_callback(ssl_ctx, ssl_gen_rsa_key);
+       SSL_CTX_set_tmp_dh_callback(ssl_ctx, ssl_tmp_dh_callback);
+}
+
+static void
+ssl_proxy_ctx_verify_client(SSL_CTX *ssl_ctx, const struct login_settings *set)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x00907000L
+       X509_STORE *store;
+
+       store = SSL_CTX_get_cert_store(ssl_ctx);
+       X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK |
+                            X509_V_FLAG_CRL_CHECK_ALL);
+#endif
+       SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE,
+                          ssl_verify_client_cert);
+       SSL_CTX_set_client_CA_list(ssl_ctx,
+                                  SSL_load_client_CA_file(set->ssl_ca_file));
+}
+
+static void ssl_proxy_init_server(const struct login_settings *set)
+{
+       char *password;
+       unsigned long err;
+
+       if ((ssl_server_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL)
+               i_fatal("SSL_CTX_new() failed");
+       ssl_proxy_ctx_init(ssl_server_ctx, set);
+
+       if (SSL_CTX_set_cipher_list(ssl_server_ctx, set->ssl_cipher_list) != 1) {
+               i_fatal("Can't set cipher list to '%s': %s",
+                       set->ssl_cipher_list, ssl_last_error());
+       }
 
-       if (SSL_CTX_use_certificate_chain_file(ssl_ctx,
+       if (SSL_CTX_use_certificate_chain_file(ssl_server_ctx,
                                               set->ssl_cert_file) != 1) {
                err = ERR_peek_error();
                if (ERR_GET_LIB(err) != ERR_LIB_PEM ||
@@ -790,42 +848,51 @@ void ssl_proxy_init(void)
        }
 
        password = t_strdup_noconst(set->ssl_key_password);
-        SSL_CTX_set_default_passwd_cb(ssl_ctx, pem_password_callback);
-        SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, password);
-       if (SSL_CTX_use_PrivateKey_file(ssl_ctx, set->ssl_key_file,
+        SSL_CTX_set_default_passwd_cb(ssl_server_ctx, pem_password_callback);
+        SSL_CTX_set_default_passwd_cb_userdata(ssl_server_ctx, password);
+       if (SSL_CTX_use_PrivateKey_file(ssl_server_ctx, set->ssl_key_file,
                                        SSL_FILETYPE_PEM) != 1) {
                i_fatal("Can't load private key file %s: %s",
                        set->ssl_key_file, ssl_last_error());
        }
        safe_memset(password, 0, strlen(password));
 
-       if (SSL_CTX_need_tmp_RSA(ssl_ctx))
-               SSL_CTX_set_tmp_rsa_callback(ssl_ctx, ssl_gen_rsa_key);
-       SSL_CTX_set_tmp_dh_callback(ssl_ctx, ssl_tmp_dh_callback);
-
        if (set->verbose_ssl)
-               SSL_CTX_set_info_callback(ssl_ctx, ssl_info_callback);
+               SSL_CTX_set_info_callback(ssl_server_ctx, ssl_info_callback);
 
-       if (set->ssl_verify_client_cert) {
-#if OPENSSL_VERSION_NUMBER >= 0x00907000L
-               X509_STORE *store;
-
-               store = SSL_CTX_get_cert_store(ssl_ctx);
-               X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK |
-                                    X509_V_FLAG_CRL_CHECK_ALL);
-#endif
-               SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER |
-                                  SSL_VERIFY_CLIENT_ONCE,
-                                  ssl_verify_client_cert);
-               SSL_CTX_set_client_CA_list(ssl_ctx,
-                       SSL_load_client_CA_file(set->ssl_ca_file));
-       }
+       if (set->ssl_verify_client_cert)
+               ssl_proxy_ctx_verify_client(ssl_server_ctx, set);
 
        ssl_username_nid = OBJ_txt2nid(set->ssl_cert_username_field);
        if (ssl_username_nid == NID_undef) {
                i_fatal("Invalid ssl_cert_username_field: %s",
                        set->ssl_cert_username_field);
        }
+}
+
+static void ssl_proxy_init_client(const struct login_settings *set)
+{
+       if ((ssl_client_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL)
+               i_fatal("SSL_CTX_new() failed");
+       ssl_proxy_ctx_init(ssl_client_ctx, set);
+       ssl_proxy_ctx_verify_client(ssl_client_ctx, set);
+}
+
+void ssl_proxy_init(void)
+{
+       const struct login_settings *set = login_settings;
+       static char dovecot[] = "dovecot";
+       unsigned char buf;
+
+       if (strcmp(set->ssl, "no") == 0)
+               return;
+
+       SSL_library_init();
+       SSL_load_error_strings();
+
+       extdata_index = SSL_get_ex_new_index(0, dovecot, NULL, NULL, NULL);
+       ssl_proxy_init_server(set);
+       ssl_proxy_init_client(set);
 
        /* PRNG initialization might want to use /dev/urandom, make sure it
           does it before chrooting. We might not have enough entropy at
@@ -847,7 +914,8 @@ void ssl_proxy_deinit(void)
                ssl_proxy_destroy(ssl_proxies);
 
        ssl_free_parameters(&ssl_params);
-       SSL_CTX_free(ssl_ctx);
+       SSL_CTX_free(ssl_server_ctx);
+       SSL_CTX_free(ssl_client_ctx);
        EVP_cleanup();
        ERR_free_strings();
 }
index fa58d58b96e5d54b8d77d2a98222456f722e9268..5236f9307182340c2773e26b297a3503e88f9f25 100644 (file)
@@ -16,6 +16,15 @@ int ssl_proxy_new(int fd ATTR_UNUSED, const struct ip_addr *ip ATTR_UNUSED,
        return -1;
 }
 
+int ssl_proxy_client_new(int fd ATTR_UNUSED, struct ip_addr *ip ATTR_UNUSED,
+                        ssl_handshake_callback_t *callback ATTR_UNUSED,
+                        void *context ATTR_UNUSED,
+                        struct ssl_proxy **proxy_r ATTR_UNUSED)
+{
+       i_error("Dovecot wasn't built with SSL support");
+       return -1;
+}
+
 bool ssl_proxy_has_valid_client_cert(const struct ssl_proxy *proxy ATTR_UNUSED)
 {
        return FALSE;
index 5dbe42bcd57ddf9ab55ffe33010fccb9117d1c88..e1b66ccd75ecb7d0bbb1502cd27ff7f1c6cbde43 100644 (file)
@@ -1,15 +1,22 @@
 #ifndef SSL_PROXY_H
 #define SSL_PROXY_H
 
+#include "ioloop.h"
+
 struct ip_addr;
 struct ssl_proxy;
 
 extern bool ssl_initialized;
 
+typedef int ssl_handshake_callback_t(void *context);
+
 /* establish SSL connection with the given fd, returns a new fd which you
    must use from now on, or -1 if error occurred. Unless -1 is returned,
    the given fd must be simply forgotten. */
 int ssl_proxy_new(int fd, const struct ip_addr *ip, struct ssl_proxy **proxy_r);
+int ssl_proxy_client_new(int fd, struct ip_addr *ip,
+                        ssl_handshake_callback_t *callback, void *context,
+                        struct ssl_proxy **proxy_r);
 bool ssl_proxy_has_valid_client_cert(const struct ssl_proxy *proxy) ATTR_PURE;
 bool ssl_proxy_has_broken_client_cert(struct ssl_proxy *proxy);
 const char *ssl_proxy_get_peer_name(struct ssl_proxy *proxy);
index 0d6ea3115986c01368d4b1245fe1c0a783465f34..e1480f1e215e8ed22fbb70cca1b4b7b0ff2af536 100644 (file)
@@ -128,38 +128,46 @@ static bool client_handle_args(struct pop3_client *client,
 {
        const char *reason = NULL, *host = NULL, *destuser = NULL, *pass = NULL;
        const char *master_user = NULL;
+       const char *key, *value, *p;
+       enum login_proxy_ssl_flags ssl_flags = 0;
        string_t *reply;
        unsigned int port = 110;
        bool proxy = FALSE, temp = FALSE, nologin = !success;
 
        *nodelay_r = FALSE;
        for (; *args != NULL; args++) {
-               if (strcmp(*args, "nologin") == 0)
+               p = strchr(*args, '=');
+               if (p == NULL) {
+                       key = *args;
+                       value = "";
+               } else {
+                       key = t_strdup_until(*args, p);
+                       value = p + 1;
+               }
+               if (strcmp(key, "nologin") == 0)
                        nologin = TRUE;
-               else if (strcmp(*args, "nodelay") == 0)
+               else if (strcmp(key, "nodelay") == 0)
                        *nodelay_r = TRUE;
-               else if (strcmp(*args, "proxy") == 0)
+               else if (strcmp(key, "proxy") == 0)
                        proxy = TRUE;
-               else if (strcmp(*args, "temp") == 0)
+               else if (strcmp(key, "temp") == 0)
                        temp = TRUE;
-               else if (strncmp(*args, "reason=", 7) == 0)
-                       reason = *args + 7;
-               else if (strncmp(*args, "host=", 5) == 0)
-                       host = *args + 5;
-               else if (strncmp(*args, "port=", 5) == 0)
-                       port = atoi(*args + 5);
-               else if (strncmp(*args, "destuser=", 9) == 0)
-                       destuser = *args + 9;
-               else if (strncmp(*args, "pass=", 5) == 0)
-                       pass = *args + 5;
-               else if (strncmp(*args, "master=", 7) == 0)
-                       master_user = *args + 7;
-               else if (strncmp(*args, "user=", 5) == 0) {
+               else if (strcmp(key, "reason") == 0)
+                       reason = value;
+               else if (strcmp(key, "host") == 0)
+                       host = value;
+               else if (strcmp(key, "port") == 0)
+                       port = atoi(value);
+               else if (strcmp(key, "destuser") == 0)
+                       destuser = value;
+               else if (strcmp(key, "pass") == 0)
+                       pass = value;
+               else if (strcmp(key, "master") == 0)
+                       master_user = value;
+               else if (strcmp(key, "user") == 0) {
                        /* already handled in login-common */
-               } else if (login_settings->auth_debug) {
-                       i_info("Ignoring unknown passdb extra field: %s",
-                              *args);
-               }
+               } else if (login_settings->auth_debug)
+                       i_info("Ignoring unknown passdb extra field: %s", key);
        }
 
        if (destuser == NULL)
@@ -174,7 +182,7 @@ static bool client_handle_args(struct pop3_client *client,
                if (!success)
                        return FALSE;
                if (pop3_proxy_new(client, host, port, destuser, master_user,
-                                  pass) < 0)
+                                  pass, ssl_flags) < 0)
                        client_auth_failed(client, TRUE);
                return TRUE;
        }
index 663398276654a45ed7b7f3c36871dc6936c7cd66..c142427c54c2f742930f38b43ea6157d2bb8cd6e 100644 (file)
@@ -8,6 +8,13 @@
 /* Disconnect client after idling this many milliseconds */
 #define CLIENT_LOGIN_IDLE_TIMEOUT_MSECS (3*60*1000)
 
+enum pop3_proxy_state {
+       POP3_PROXY_BANNER = 0,
+       POP3_PROXY_STARTTLS,
+       POP3_PROXY_LOGIN1,
+       POP3_PROXY_LOGIN2
+};
+
 struct pop3_client {
        struct client common;
 
@@ -20,7 +27,7 @@ struct pop3_client {
 
        struct login_proxy *proxy;
        char *proxy_user, *proxy_master_user, *proxy_password;
-       int proxy_state;
+       enum pop3_proxy_state proxy_state;
 
        unsigned int bad_counter;
 
index 60a59ac3bd293f64a2ee35d66399560680f0d521..1cd1a5d145780e8c5308c67f9680543034b6a396 100644 (file)
@@ -49,15 +49,35 @@ static void get_plain_auth(struct pop3_client *client, string_t *dest)
        base64_encode(str_data(str), str_len(str), dest);
 }
 
-static int proxy_input_line(struct pop3_client *client,
-                           struct ostream *output, const char *line)
+static void proxy_send_login(struct pop3_client *client, struct ostream *output)
 {
        string_t *str;
 
+       str = t_str_new(128);
+       if (client->proxy_master_user == NULL) {
+               /* send USER command */
+               str_append(str, "USER ");
+               str_append(str, client->proxy_user);
+               str_append(str, "\r\n");
+       } else {
+               /* master user login - use AUTH PLAIN. */
+               str_append(str, "AUTH PLAIN\r\n");
+       }
+       (void)o_stream_send(output, str_data(str), str_len(str));
+       client->proxy_state = POP3_PROXY_LOGIN1;
+}
+
+static int proxy_input_line(struct pop3_client *client, const char *line)
+{
+       struct ostream *output;
+       enum login_proxy_ssl_flags ssl_flags;
+       string_t *str;
+
        i_assert(!client->destroyed);
 
+       output = login_proxy_get_ostream(client->proxy);
        switch (client->proxy_state) {
-       case 0:
+       case POP3_PROXY_BANNER:
                /* this is a banner */
                if (strncmp(line, "+OK", 3) != 0) {
                        client_syslog_err(&client->common, t_strdup_printf(
@@ -67,21 +87,31 @@ static int proxy_input_line(struct pop3_client *client,
                        return -1;
                }
 
-               str = t_str_new(128);
-               if (client->proxy_master_user == NULL) {
-                       /* send USER command */
-                       str_append(str, "USER ");
-                       str_append(str, client->proxy_user);
-                       str_append(str, "\r\n");
+               ssl_flags = login_proxy_get_ssl_flags(client->proxy);
+               if ((ssl_flags & PROXY_SSL_FLAG_STARTTLS) == 0) {
+                       proxy_send_login(client, output);
                } else {
-                       /* master user login - use AUTH PLAIN. */
-                       str_append(str, "AUTH PLAIN\r\n");
+                       (void)o_stream_send_str(output, "STLS\r\n");
+                       client->proxy_state = POP3_PROXY_STARTTLS;
                }
-               (void)o_stream_send(output, str_data(str), str_len(str));
-
-               client->proxy_state++;
                return 0;
-       case 1:
+       case POP3_PROXY_STARTTLS:
+               if (strncmp(line, "+OK", 3) != 0) {
+                       client_syslog_err(&client->common, t_strdup_printf(
+                               "proxy: Remote STLS failed: %s",
+                               str_sanitize(line, 160)));
+                       proxy_failed(client, TRUE);
+                       return -1;
+               }
+               if (login_proxy_starttls(client->proxy) < 0) {
+                       proxy_failed(client, TRUE);
+                       return -1;
+               }
+               /* i/ostreams changed. */
+               output = login_proxy_get_ostream(client->proxy);
+               proxy_send_login(client, output);
+               return 1;
+       case POP3_PROXY_LOGIN1:
                str = t_str_new(128);
                if (client->proxy_master_user == NULL) {
                        if (strncmp(line, "+OK", 3) != 0)
@@ -100,9 +130,9 @@ static int proxy_input_line(struct pop3_client *client,
                }
                (void)o_stream_send(output, str_data(str), str_len(str));
                proxy_free_password(client);
-               client->proxy_state++;
+               client->proxy_state = POP3_PROXY_LOGIN2;
                return 0;
-       case 2:
+       case POP3_PROXY_LOGIN2:
                if (strncmp(line, "+OK", 3) != 0)
                        break;
 
@@ -185,17 +215,18 @@ static int proxy_input_line(struct pop3_client *client,
        return -1;
 }
 
-static void proxy_input(struct istream *input, struct ostream *output,
-                       struct pop3_client *client)
+static void proxy_input(struct pop3_client *client)
 {
+       struct istream *input;
        const char *line;
 
-       if (input == NULL) {
-               if (client->proxy == NULL) {
-                       /* we're just freeing the proxy */
-                       return;
-               }
+       if (client->proxy == NULL) {
+               /* we're just freeing the proxy */
+               return;
+       }
 
+       input = login_proxy_get_istream(client->proxy);
+       if (input == NULL) {
                if (client->destroyed) {
                        /* we came here from client_destroy() */
                        return;
@@ -222,14 +253,14 @@ static void proxy_input(struct istream *input, struct ostream *output,
        }
 
        while ((line = i_stream_next_line(input)) != NULL) {
-               if (proxy_input_line(client, output, line) != 0)
+               if (proxy_input_line(client, line) != 0)
                        break;
        }
 }
 
 int pop3_proxy_new(struct pop3_client *client, const char *host,
                   unsigned int port, const char *user, const char *master_user,
-                  const char *password)
+                  const char *password, enum login_proxy_ssl_flags ssl_flags)
 {
        i_assert(user != NULL);
        i_assert(!client->destroyed);
@@ -253,14 +284,14 @@ int pop3_proxy_new(struct pop3_client *client, const char *host,
                return -1;
        }
 
-       client->proxy = login_proxy_new(&client->common, host, port,
+       client->proxy = login_proxy_new(&client->common, host, port, ssl_flags,
                                        proxy_input, client);
        if (client->proxy == NULL) {
                client_send_line(client, PROXY_FAILURE_MSG);
                return -1;
        }
 
-       client->proxy_state = 0;
+       client->proxy_state = POP3_PROXY_BANNER;
        client->proxy_user = i_strdup(user);
        client->proxy_master_user = i_strdup(master_user);
        client->proxy_password = i_strdup(password);
index 11dcc05be134964d5ece03f9e537cc0914389cb9..035f40ab6ad18610bb3ad78fe2f1e39a8a78b458 100644 (file)
@@ -5,6 +5,6 @@
 
 int pop3_proxy_new(struct pop3_client *client, const char *host,
                   unsigned int port, const char *user, const char *master_user,
-                  const char *password);
+                  const char *password, enum login_proxy_ssl_flags ssl_flags);
 
 #endif