From: Timo Sirainen Date: Wed, 1 Nov 2017 00:19:51 +0000 (+0200) Subject: login-common: Implement post-login proxying and use it with SSL connections X-Git-Tag: 2.3.0.rc1~519 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=87dbf3e85526ccde5908a611eb9a798f1d0ccac3;p=thirdparty%2Fdovecot%2Fcore.git login-common: Implement post-login proxying and use it with SSL connections Note: This temporarily breaks the SSL connections a bit. If post-login process disconnects the client, it's not noticed by the login process. Client connections are noticed by the post-login though. --- diff --git a/src/login-common/client-common.c b/src/login-common/client-common.c index f57b264fb8..136dd7a90c 100644 --- a/src/login-common/client-common.c +++ b/src/login-common/client-common.c @@ -6,6 +6,7 @@ #include "llist.h" #include "istream.h" #include "ostream.h" +#include "iostream-proxy.h" #include "iostream-rawlog.h" #include "process-title.h" #include "hook-build.h" @@ -252,8 +253,11 @@ void client_destroy(struct client *client, const char *reason) if (client->output != NULL) o_stream_uncork(client->output); if (!client->login_success) { + io_remove(&client->io); if (client->ssl_proxy != NULL) ssl_proxy_destroy(client->ssl_proxy); + if (client->iostream_fd_proxy != NULL) + iostream_proxy_unref(&client->iostream_fd_proxy); i_stream_close(client->input); o_stream_close(client->output); i_close_fd(&client->fd); @@ -276,7 +280,6 @@ void client_destroy(struct client *client, const char *reason) i_assert(!client->authenticating); } - io_remove(&client->io); timeout_remove(&client->to_disconnect); timeout_remove(&client->to_auth_waiting); if (client->auth_response != NULL) @@ -341,6 +344,8 @@ bool client_unref(struct client **_client) if (client->ssl_proxy != NULL) ssl_proxy_free(&client->ssl_proxy); + if (client->iostream_fd_proxy != NULL) + iostream_proxy_unref(&client->iostream_fd_proxy); i_stream_unref(&client->input); o_stream_unref(&client->output); i_close_fd(&client->fd); @@ -497,6 +502,64 @@ void client_cmd_starttls(struct client *client) } } +static void +iostream_fd_proxy_finished(enum iostream_proxy_side side ATTR_UNUSED, + enum iostream_proxy_status status ATTR_UNUSED, + struct client *client) +{ + /* Destroy the proxy now. The other side of the proxy is still + unfinished and we don't want to get back here and unreference + the client twice. */ + iostream_proxy_unref(&client->iostream_fd_proxy); + client_unref(&client); +} + +int client_get_plaintext_fd(struct client *client, int *fd_r, bool *close_fd_r) +{ + int fds[2]; + + if (!client->tls) { + /* Plaintext connection - We can send the fd directly to + the post-login process without any proxying. */ + *fd_r = client->fd; + *close_fd_r = FALSE; + return 0; + } + + /* We'll have to start proxying from now on until either side + disconnects. Create a socketpair where login process is proxying on + one side and the other side is sent to the post-login process. */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) { + client_log_err(client, t_strdup_printf("socketpair() failed: %m")); + return -1; + } + fd_set_nonblock(fds[0], TRUE); + fd_set_nonblock(fds[1], TRUE); + + struct ostream *output = o_stream_create_fd(fds[0], IO_BLOCK_SIZE); + struct istream *input = + i_stream_create_fd_autoclose(&fds[0], IO_BLOCK_SIZE); + o_stream_set_no_error_handling(output, TRUE); + + i_assert(client->io == NULL); + + client_ref(client); + client->iostream_fd_proxy = + iostream_proxy_create(input, output, + client->input, client->output); + i_stream_unref(&input); + o_stream_unref(&output); + + iostream_proxy_set_completion_callback(client->iostream_fd_proxy, + iostream_fd_proxy_finished, + client); + iostream_proxy_start(client->iostream_fd_proxy); + + *fd_r = fds[1]; + *close_fd_r = TRUE; + return 0; +} + unsigned int clients_get_count(void) { return clients_count; diff --git a/src/login-common/client-common.h b/src/login-common/client-common.h index e49fca13d2..0307382ee3 100644 --- a/src/login-common/client-common.h +++ b/src/login-common/client-common.h @@ -156,6 +156,7 @@ struct client { struct istream *input; struct ostream *output; struct io *io; + struct iostream_proxy *iostream_fd_proxy; struct timeout *to_auth_waiting; struct timeout *to_disconnect; @@ -244,6 +245,8 @@ client_alloc(int fd, pool_t pool, const struct master_service_ssl_settings *ssl_set); void client_init(struct client *client, void **other_sets); void client_destroy(struct client *client, const char *reason); +/* Destroy the client after a successful login. Either the client fd was + sent to the post-login process, or the connection will be proxied. */ void client_destroy_success(struct client *client, const char *reason); void client_ref(struct client *client); @@ -252,6 +255,8 @@ bool client_unref(struct client **client) ATTR_NOWARN_UNUSED_RESULT; int client_init_ssl(struct client *client); void client_cmd_starttls(struct client *client); +int client_get_plaintext_fd(struct client *client, int *fd_r, bool *close_fd_r); + unsigned int clients_get_count(void) ATTR_PURE; void client_add_forward_field(struct client *client, const char *key, diff --git a/src/login-common/sasl-server.c b/src/login-common/sasl-server.c index b27232e629..83963925c9 100644 --- a/src/login-common/sasl-server.c +++ b/src/login-common/sasl-server.c @@ -115,7 +115,7 @@ master_auth_callback(const struct master_auth_reply *reply, void *context) call_client_callback(client, sasl_reply, data, NULL); } -static void master_send_request(struct anvil_request *anvil_request) +static int master_send_request(struct anvil_request *anvil_request) { struct client *client = anvil_request->client; struct master_auth_request_params params; @@ -124,6 +124,11 @@ static void master_send_request(struct anvil_request *anvil_request) size_t size; buffer_t *buf; const char *session_id = client_get_session_id(client); + int fd; + bool close_fd; + + if (client_get_plaintext_fd(client, &fd, &close_fd) < 0) + return -1; i_zero(&req); req.auth_pid = anvil_request->auth_pid; @@ -151,12 +156,15 @@ static void master_send_request(struct anvil_request *anvil_request) client->master_auth_id = req.auth_id; i_zero(¶ms); - params.client_fd = client->fd; + params.client_fd = fd; params.socket_path = client->postlogin_socket_path; params.request = req; params.data = buf->data; master_auth_request_full(master_auth, ¶ms, master_auth_callback, client, &client->master_tag); + if (close_fd) + i_close_fd(&fd); + return 0; } static void ATTR_NULL(1) @@ -167,6 +175,7 @@ anvil_lookup_callback(const char *reply, void *context) const struct login_settings *set = client->set; const char *errmsg; unsigned int conn_count; + int ret; conn_count = 0; if (reply != NULL && str_to_uint(reply, &conn_count) < 0) @@ -174,13 +183,17 @@ anvil_lookup_callback(const char *reply, void *context) /* reply=NULL if we didn't need to do anvil lookup, or if the anvil lookup failed. allow failed anvil lookups in. */ - if (reply == NULL || conn_count < set->mail_max_userip_connections) - master_send_request(req); - else { - client->authenticating = FALSE; - auth_client_send_cancel(auth_client, req->auth_id); + if (reply == NULL || conn_count < set->mail_max_userip_connections) { + ret = master_send_request(req); + errmsg = NULL; /* client will see internal error */ + } else { + ret = -1; errmsg = t_strdup_printf(ERR_TOO_MANY_USERIP_CONNECTIONS, set->mail_max_userip_connections); + } + if (ret < 0) { + client->authenticating = FALSE; + auth_client_send_cancel(auth_client, req->auth_id); call_client_callback(client, SASL_SERVER_REPLY_MASTER_FAILED, errmsg, NULL); }