Mercurial > dovecot > original-hg > dovecot-1.2
changeset 8985:f43bebab3dac HEAD
imap/pop3 proxy: Support SSL/TLS connections to remote servers.
passdb can return ssl=yes, ssl=any-cert and starttls options.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Tue, 28 Apr 2009 22:31:40 -0400 |
parents | e7bfb3c134f9 |
children | d475e17d01a3 |
files | src/imap-login/client-authenticate.c src/imap-login/client.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.c src/pop3-login/client.h src/pop3-login/pop3-proxy.c src/pop3-login/pop3-proxy.h |
diffstat | 15 files changed, 520 insertions(+), 214 deletions(-) [+] |
line wrap: on
line diff
--- a/src/imap-login/client-authenticate.c Tue Apr 28 19:57:10 2009 -0400 +++ b/src/imap-login/client-authenticate.c Tue Apr 28 22:31:40 2009 -0400 @@ -124,6 +124,8 @@ { 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,33 +133,49 @@ *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 + 7; + 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, "ssl") == 0) { + if (strcmp(value, "yes") == 0) + ssl_flags |= PROXY_SSL_FLAG_YES; + else if (strcmp(value, "any-cert") == 0) { + ssl_flags |= PROXY_SSL_FLAG_YES | + PROXY_SSL_FLAG_ANY_CERT; + } + } else if (strcmp(key, "starttls") == 0) { + ssl_flags |= PROXY_SSL_FLAG_STARTTLS; + } else if (strcmp(key, "user") == 0) { /* already handled in login-common */ } else if (auth_debug) { - i_info("Ignoring unknown passdb extra field: %s", - *args); + i_info("Ignoring unknown passdb extra field: %s", key); } } @@ -173,7 +191,7 @@ 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; }
--- a/src/imap-login/client.c Tue Apr 28 19:57:10 2009 -0400 +++ b/src/imap-login/client.c Tue Apr 28 22:31:40 2009 -0400 @@ -556,10 +556,11 @@ if (client->output != NULL) o_stream_close(client->output); - if (client->common.master_tag != 0) + if (client->common.master_tag != 0) { + i_assert(client->common.auth_request == NULL); + i_assert(client->common.authenticating); master_request_abort(&client->common); - - if (client->common.auth_request != NULL) { + } else if (client->common.auth_request != NULL) { i_assert(client->common.authenticating); sasl_server_auth_client_error(&client->common, NULL); } else {
--- a/src/imap-login/client.h Tue Apr 28 19:57:10 2009 -0400 +++ b/src/imap-login/client.h Tue Apr 28 22:31:40 2009 -0400 @@ -30,7 +30,8 @@ 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;
--- a/src/imap-login/imap-proxy.c Tue Apr 28 19:57:10 2009 -0400 +++ b/src/imap-login/imap-proxy.c Tue Apr 28 22:31:40 2009 -0400 @@ -136,9 +136,35 @@ 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 @@ 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 @@ (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; @@ -306,17 +347,18 @@ } } -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 (client->proxy == NULL) { + /* we're just freeing the proxy */ + return; + } + + input = login_proxy_get_istream(client->proxy); if (input == NULL) { - if (client->proxy == NULL) { - /* we're just freeing the proxy */ - return; - } - if (client->destroyed) { /* we came here from client_destroy() */ return; @@ -343,14 +385,14 @@ } 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 @@ 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);
--- a/src/imap-login/imap-proxy.h Tue Apr 28 19:57:10 2009 -0400 +++ b/src/imap-login/imap-proxy.h Tue Apr 28 22:31:40 2009 -0400 @@ -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
--- a/src/login-common/login-proxy.c Tue Apr 28 19:57:10 2009 -0400 +++ b/src/login-common/login-proxy.c Tue Apr 28 22:31:40 2009 -0400 @@ -7,6 +7,7 @@ #include "llist.h" #include "str-sanitize.h" #include "client-common.h" +#include "ssl-proxy.h" #include "login-proxy.h" #define MAX_PROXY_INPUT_SIZE 4096 @@ -15,19 +16,23 @@ 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 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 @@ 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 @@ 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 @@ 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 */ main_unref(); @@ -209,15 +235,11 @@ 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); @@ -243,6 +265,16 @@ 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; @@ -253,6 +285,12 @@ return proxy->port; } +enum login_proxy_ssl_flags +login_proxy_get_ssl_flags(const struct login_proxy *proxy) +{ + return proxy->ssl_flags; +} + unsigned int login_proxy_get_count(void) { return login_proxy_count; @@ -267,6 +305,7 @@ 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; @@ -296,6 +335,52 @@ main_ref(); } +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;
--- a/src/login-common/login-proxy.h Tue Apr 28 19:57:10 2009 -0400 +++ b/src/login-common/login-proxy.h Tue Apr 28 22:31:40 2009 -0400 @@ -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 @@ 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; /* Return number of active detached login proxies */ unsigned int login_proxy_get_count(void) ATTR_PURE;
--- a/src/login-common/ssl-proxy-openssl.c Tue Apr 28 19:57:10 2009 -0400 +++ b/src/login-common/ssl-proxy-openssl.c Tue Apr 28 22:31:40 2009 -0400 @@ -49,11 +49,15 @@ 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 { @@ -65,7 +69,8 @@ }; 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; @@ -396,16 +401,27 @@ { 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; + 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); - } + 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) @@ -473,7 +489,8 @@ ssl_proxy_unref(proxy); } -int ssl_proxy_new(int fd, struct ip_addr *ip, struct ssl_proxy **proxy_r) +static int ssl_proxy_new_common(SSL_CTX *ssl_ctx, int fd, struct ip_addr *ip, + struct ssl_proxy **proxy_r) { struct ssl_proxy *proxy; SSL *ssl; @@ -523,13 +540,39 @@ ssl_proxy_count++; DLLIST_PREPEND(&ssl_proxies, proxy); - ssl_step(proxy); main_ref(); *proxy_r = proxy; return sfd[1]; } +int ssl_proxy_new(int fd, 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; @@ -737,53 +780,63 @@ return strstr(buf, "PRIVATE KEY---") != NULL; } -void ssl_proxy_init(void) +static void ssl_proxy_ctx_init(SSL_CTX *ssl_ctx) { - static char dovecot[] = "dovecot"; - const char *cafile, *certfile, *keyfile, *cipher_list, *username_field; - char *password; - unsigned char buf; - unsigned long err; - - memset(&ssl_params, 0, sizeof(ssl_params)); - - cafile = getenv("SSL_CA_FILE"); - certfile = getenv("SSL_CERT_FILE"); - keyfile = getenv("SSL_KEY_FILE"); - ssl_params.fname = getenv("SSL_PARAM_FILE"); - password = getenv("SSL_KEY_PASSWORD"); - - if (certfile == NULL || keyfile == NULL || ssl_params.fname == NULL) { - /* SSL support is disabled */ - 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"); + const char *cafile; SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL); - cipher_list = getenv("SSL_CIPHER_LIST"); - if (cipher_list == NULL) - cipher_list = DOVECOT_SSL_DEFAULT_CIPHER_LIST; - if (SSL_CTX_set_cipher_list(ssl_ctx, cipher_list) != 1) { - i_fatal("Can't set cipher list to '%s': %s", - cipher_list, ssl_last_error()); - } - + cafile = getenv("SSL_CA_FILE"); if (cafile != NULL) { if (SSL_CTX_load_verify_locations(ssl_ctx, cafile, NULL) != 1) { i_fatal("Can't load CA file %s: %s", cafile, ssl_last_error()); } } + if (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); +} - if (SSL_CTX_use_certificate_chain_file(ssl_ctx, certfile) != 1) { +static void ssl_proxy_ctx_verify_client(SSL_CTX *ssl_ctx) +{ + const char *cafile; +#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 + cafile = getenv("SSL_CA_FILE"); + 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(cafile)); +} + +static void ssl_proxy_init_server(const char *certfile, const char *keyfile) +{ + const char *cipher_list, *username_field; + char *password; + unsigned long err; + + password = getenv("SSL_KEY_PASSWORD"); + + if ((ssl_server_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) + i_fatal("SSL_CTX_new() failed"); + ssl_proxy_ctx_init(ssl_server_ctx); + + cipher_list = getenv("SSL_CIPHER_LIST"); + if (cipher_list == NULL) + cipher_list = DOVECOT_SSL_DEFAULT_CIPHER_LIST; + if (SSL_CTX_set_cipher_list(ssl_server_ctx, cipher_list) != 1) { + i_fatal("Can't set cipher list to '%s': %s", + cipher_list, ssl_last_error()); + } + + if (SSL_CTX_use_certificate_chain_file(ssl_server_ctx, certfile) != 1) { err = ERR_peek_error(); if (ERR_GET_LIB(err) != ERR_LIB_PEM || ERR_GET_REASON(err) != PEM_R_NO_START_LINE) { @@ -801,35 +854,16 @@ } } - 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, keyfile, + 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, keyfile, SSL_FILETYPE_PEM) != 1) { i_fatal("Can't load private key file %s: %s", keyfile, ssl_last_error()); } - 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 (verbose_ssl) - SSL_CTX_set_info_callback(ssl_ctx, ssl_info_callback); - - if (getenv("SSL_VERIFY_CLIENT_CERT") != NULL) { -#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(cafile)); - } + if (getenv("SSL_VERIFY_CLIENT_CERT") != NULL) + ssl_proxy_ctx_verify_client(ssl_server_ctx); username_field = getenv("SSL_CERT_USERNAME_FIELD"); if (username_field == NULL) @@ -841,6 +875,39 @@ username_field); } } +} + +static void ssl_proxy_init_client(void) +{ + if ((ssl_client_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) + i_fatal("SSL_CTX_new() failed"); + ssl_proxy_ctx_init(ssl_client_ctx); + ssl_proxy_ctx_verify_client(ssl_client_ctx); +} + +void ssl_proxy_init(void) +{ + static char dovecot[] = "dovecot"; + const char *certfile, *keyfile; + unsigned char buf; + + memset(&ssl_params, 0, sizeof(ssl_params)); + + certfile = getenv("SSL_CERT_FILE"); + keyfile = getenv("SSL_KEY_FILE"); + ssl_params.fname = getenv("SSL_PARAM_FILE"); + + if (certfile == NULL || keyfile == NULL || ssl_params.fname == NULL) { + /* SSL support is disabled */ + return; + } + + SSL_library_init(); + SSL_load_error_strings(); + + extdata_index = SSL_get_ex_new_index(0, dovecot, NULL, NULL, NULL); + ssl_proxy_init_server(certfile, keyfile); + ssl_proxy_init_client(); /* PRNG initialization might want to use /dev/urandom, make sure it does it before chrooting. We might not have enough entropy at @@ -862,7 +929,8 @@ 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(); }
--- a/src/login-common/ssl-proxy.c Tue Apr 28 19:57:10 2009 -0400 +++ b/src/login-common/ssl-proxy.c Tue Apr 28 22:31:40 2009 -0400 @@ -16,6 +16,15 @@ 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;
--- a/src/login-common/ssl-proxy.h Tue Apr 28 19:57:10 2009 -0400 +++ b/src/login-common/ssl-proxy.h Tue Apr 28 22:31:40 2009 -0400 @@ -1,15 +1,23 @@ #ifndef SSL_PROXY_H #define SSL_PROXY_H +#include "ioloop.h" + struct ip_addr; struct ssl_proxy; extern bool ssl_initialized; -/* establish SSL connection with the given fd, returns a new fd which you +typedef int ssl_handshake_callback_t(void *context); + +/* establish SSL server 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, struct ip_addr *ip, struct ssl_proxy **proxy_r); +/* establish SSL client connection */ +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);
--- a/src/pop3-login/client-authenticate.c Tue Apr 28 19:57:10 2009 -0400 +++ b/src/pop3-login/client-authenticate.c Tue Apr 28 22:31:40 2009 -0400 @@ -127,37 +127,55 @@ { 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, "ssl") == 0) { + if (strcmp(value, "yes") == 0) + ssl_flags |= PROXY_SSL_FLAG_YES; + else if (strcmp(value, "any-cert") == 0) { + ssl_flags |= PROXY_SSL_FLAG_YES | + PROXY_SSL_FLAG_ANY_CERT; + } + } else if (strcmp(key, "starttls") == 0) { + ssl_flags |= PROXY_SSL_FLAG_STARTTLS; + } else if (strcmp(key, "user") == 0) { /* already handled in login-common */ } else if (auth_debug) { - i_info("Ignoring unknown passdb extra field: %s", - *args); + i_info("Ignoring unknown passdb extra field: %s", key); } } @@ -173,7 +191,7 @@ 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; }
--- a/src/pop3-login/client.c Tue Apr 28 19:57:10 2009 -0400 +++ b/src/pop3-login/client.c Tue Apr 28 22:31:40 2009 -0400 @@ -364,10 +364,11 @@ if (client->output != NULL) o_stream_close(client->output); - if (client->common.master_tag != 0) + if (client->common.master_tag != 0) { + i_assert(client->common.auth_request == NULL); + i_assert(client->common.authenticating); master_request_abort(&client->common); - - if (client->common.auth_request != NULL) { + } else if (client->common.auth_request != NULL) { i_assert(client->common.authenticating); sasl_server_auth_client_error(&client->common, NULL); } else {
--- a/src/pop3-login/client.h Tue Apr 28 19:57:10 2009 -0400 +++ b/src/pop3-login/client.h Tue Apr 28 22:31:40 2009 -0400 @@ -9,6 +9,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; @@ -21,7 +28,7 @@ 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;
--- a/src/pop3-login/pop3-proxy.c Tue Apr 28 19:57:10 2009 -0400 +++ b/src/pop3-login/pop3-proxy.c Tue Apr 28 22:31:40 2009 -0400 @@ -49,15 +49,35 @@ 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 @@ 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 @@ } (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; @@ -184,17 +214,18 @@ 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 (client->proxy == NULL) { + /* we're just freeing the proxy */ + return; + } + + input = login_proxy_get_istream(client->proxy); if (input == NULL) { - if (client->proxy == NULL) { - /* we're just freeing the proxy */ - return; - } - if (client->destroyed) { /* we came here from client_destroy() */ return; @@ -221,14 +252,14 @@ } 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 @@ 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);
--- a/src/pop3-login/pop3-proxy.h Tue Apr 28 19:57:10 2009 -0400 +++ b/src/pop3-login/pop3-proxy.h Tue Apr 28 22:31:40 2009 -0400 @@ -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