]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
login-common: Implement post-login proxying and use it with SSL connections
authorTimo Sirainen <timo.sirainen@dovecot.fi>
Wed, 1 Nov 2017 00:19:51 +0000 (02:19 +0200)
committerTimo Sirainen <tss@dovecot.fi>
Mon, 6 Nov 2017 23:09:00 +0000 (01:09 +0200)
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.

src/login-common/client-common.c
src/login-common/client-common.h
src/login-common/sasl-server.c

index f57b264fb861da96bec2db0219ce7910921cf76d..136dd7a90cabfd8cecf982bfc4f7d520ddf57753 100644 (file)
@@ -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;
index e49fca13d254d365e1affc2584baaef77af3a442..0307382ee3494e661fd31c3ff0d8f0c27d7be0be 100644 (file)
@@ -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,
index b27232e6296fd3f36f90701ac852ca3f221c0c20..83963925c92619b58198d74ef9ce3011690f3aef 100644 (file)
@@ -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(&params);
-       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, &params, 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);
        }