Browse Source

[Dovecot] Fixes CVE-2017-15132 - take 2

andre.peters 7 years ago
parent
commit
ae4ccd4d17

+ 3 - 1
data/Dockerfiles/dovecot/Dockerfile

@@ -65,7 +65,9 @@ RUN apt-get update && apt-get -y --no-install-recommends install \
 
 RUN curl https://www.dovecot.org/releases/2.2/dovecot-$DOVECOT_VERSION.tar.gz | tar xvz  \
   && cd dovecot-$DOVECOT_VERSION \
-  && sed '/call_callback(request, AUTH_REQUEST_STATUS_ABORT, NULL, NULL);/a   pool_unref(&request->pool);' src/lib-auth/auth-client-request.c \
+  && curl -o src/lib-auth/auth-client-request.c https://mailcow.email/dovecot-patch1/auth-client-request.c \
+  && curl -o src/lib-auth/auth-server-connection.c https://mailcow.email/dovecot-patch1/auth-server-connection.c \
+  && curl -o src/lib-auth/auth-server-connection.h https://mailcow.email/dovecot-patch1/auth-server-connection.h \
   && ./configure --with-mysql --with-lzma --with-lz4 --with-ssl=openssl --with-notify=inotify --with-storages=mdbox,sdbox,maildir,mbox,imapc,pop3c --with-bzlib --with-zlib \
   && make -j3 \
   && make install \

+ 254 - 0
data/Dockerfiles/dovecot/auth-client-request.c

@@ -0,0 +1,254 @@
+/* Copyright (c) 2003-2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "strescape.h"
+#include "ostream.h"
+#include "auth-client-private.h"
+#include "auth-server-connection.h"
+#include "auth-client-request.h"
+
+
+struct auth_client_request {
+  pool_t pool;
+
+  struct auth_server_connection *conn;
+  unsigned int id;
+  time_t created;
+
+  struct auth_request_info request_info;
+
+  auth_request_callback_t *callback;
+  void *context;
+};
+
+static void auth_server_send_new_request(struct auth_server_connection *conn,
+           struct auth_client_request *request)
+{
+  struct auth_request_info *info = &request->request_info;
+  string_t *str;
+
+  str = t_str_new(512);
+  str_printfa(str, "AUTH\t%u\t", request->id);
+  str_append_tabescaped(str, info->mech);
+  str_append(str, "\tservice=");
+  str_append_tabescaped(str, info->service);
+
+  if ((info->flags & AUTH_REQUEST_FLAG_SUPPORT_FINAL_RESP) != 0)
+    str_append(str, "\tfinal-resp-ok");
+  if ((info->flags & AUTH_REQUEST_FLAG_SECURED) != 0)
+    str_append(str, "\tsecured");
+  if ((info->flags & AUTH_REQUEST_FLAG_NO_PENALTY) != 0)
+    str_append(str, "\tno-penalty");
+  if ((info->flags & AUTH_REQUEST_FLAG_VALID_CLIENT_CERT) != 0)
+    str_append(str, "\tvalid-client-cert");
+  if ((info->flags & AUTH_REQUEST_FLAG_DEBUG) != 0)
+    str_append(str, "\tdebug");
+
+  if (info->session_id != NULL) {
+    str_append(str, "\tsession=");
+    str_append_tabescaped(str, info->session_id);
+  }
+  if (info->cert_username != NULL) {
+    str_append(str, "\tcert_username=");
+    str_append_tabescaped(str, info->cert_username);
+  }
+  if (info->local_ip.family != 0)
+    str_printfa(str, "\tlip=%s", net_ip2addr(&info->local_ip));
+  if (info->remote_ip.family != 0)
+    str_printfa(str, "\trip=%s", net_ip2addr(&info->remote_ip));
+  if (info->local_port != 0)
+    str_printfa(str, "\tlport=%u", info->local_port);
+  if (info->remote_port != 0)
+    str_printfa(str, "\trport=%u", info->remote_port);
+
+  /* send the real_* variants only when they differ from the unreal
+     ones */
+  if (info->real_local_ip.family != 0 &&
+      !net_ip_compare(&info->real_local_ip, &info->local_ip)) {
+    str_printfa(str, "\treal_lip=%s",
+          net_ip2addr(&info->real_local_ip));
+  }
+  if (info->real_remote_ip.family != 0 &&
+      !net_ip_compare(&info->real_remote_ip, &info->remote_ip)) {
+    str_printfa(str, "\treal_rip=%s",
+          net_ip2addr(&info->real_remote_ip));
+  }
+  if (info->real_local_port != 0 &&
+      info->real_local_port != info->local_port)
+    str_printfa(str, "\treal_lport=%u", info->real_local_port);
+  if (info->real_remote_port != 0 &&
+      info->real_remote_port != info->remote_port)
+    str_printfa(str, "\treal_rport=%u", info->real_remote_port);
+  if (info->local_name != NULL &&
+      *info->local_name != '\0') {
+    str_append(str, "\tlocal_name=");
+    str_append_tabescaped(str, info->local_name);
+  }
+  if (info->client_id != NULL &&
+      *info->client_id != '\0') {
+    str_append(str, "\tclient_id=");
+    str_append_tabescaped(str, info->client_id);
+  }
+  if (info->forward_fields != NULL &&
+      *info->forward_fields != '\0') {
+    str_append(str, "\tforward_fields=");
+    str_append_tabescaped(str, info->forward_fields);
+  }
+  if (info->initial_resp_base64 != NULL) {
+    str_append(str, "\tresp=");
+    str_append_tabescaped(str, info->initial_resp_base64);
+  }
+  str_append_c(str, '\n');
+
+  if (o_stream_send(conn->output, str_data(str), str_len(str)) < 0)
+    i_error("Error sending request to auth server: %m");
+}
+
+struct auth_client_request *
+auth_client_request_new(struct auth_client *client,
+      const struct auth_request_info *request_info,
+      auth_request_callback_t *callback, void *context)
+{
+  struct auth_client_request *request;
+  pool_t pool;
+
+  pool = pool_alloconly_create("auth client request", 512);
+  request = p_new(pool, struct auth_client_request, 1);
+  request->pool = pool;
+  request->conn = client->conn;
+
+  request->request_info = *request_info;
+  request->request_info.mech = p_strdup(pool, request_info->mech);
+  request->request_info.service = p_strdup(pool, request_info->service);
+  request->request_info.session_id =
+    p_strdup_empty(pool, request_info->session_id);
+  request->request_info.cert_username =
+    p_strdup_empty(pool, request_info->cert_username);
+  request->request_info.initial_resp_base64 =
+    p_strdup_empty(pool, request_info->initial_resp_base64);
+  
+  request->callback = callback;
+  request->context = context;
+
+  request->id =
+    auth_server_connection_add_request(request->conn, request);
+  request->created = ioloop_time;
+  T_BEGIN {
+    auth_server_send_new_request(request->conn, request);
+  } T_END;
+  return request;
+}
+
+void auth_client_request_continue(struct auth_client_request *request,
+                                  const char *data_base64)
+{
+  struct const_iovec iov[3];
+  const char *prefix;
+
+  prefix = t_strdup_printf("CONT\t%u\t", request->id);
+
+  iov[0].iov_base = prefix;
+  iov[0].iov_len = strlen(prefix);
+  iov[1].iov_base = data_base64;
+  iov[1].iov_len = strlen(data_base64);
+  iov[2].iov_base = "\n";
+  iov[2].iov_len = 1;
+
+  if (o_stream_sendv(request->conn->output, iov, 3) < 0)
+    i_error("Error sending continue request to auth server: %m");
+}
+
+static void ATTR_NULL(3, 4)
+call_callback(struct auth_client_request *request,
+        enum auth_request_status status,
+        const char *data_base64,
+        const char *const *args)
+{
+  auth_request_callback_t *callback = request->callback;
+
+  if (status != AUTH_REQUEST_STATUS_CONTINUE)
+    request->callback = NULL;
+  callback(request, status, data_base64, args, request->context);
+}
+
+void auth_client_request_abort(struct auth_client_request **_request)
+{
+  struct auth_client_request *request = *_request;
+
+  *_request = NULL;
+
+  auth_client_send_cancel(request->conn->client, request->id);
+  call_callback(request, AUTH_REQUEST_STATUS_ABORT, NULL, NULL);
+  auth_server_connection_remove_request(request->conn, request->id);
+  pool_unref(&request->pool);
+}
+
+unsigned int auth_client_request_get_id(struct auth_client_request *request)
+{
+  return request->id;
+}
+
+unsigned int
+auth_client_request_get_server_pid(struct auth_client_request *request)
+{
+  return request->conn->server_pid;
+}
+
+const char *auth_client_request_get_cookie(struct auth_client_request *request)
+{
+  return request->conn->cookie;
+}
+
+bool auth_client_request_is_aborted(struct auth_client_request *request)
+{
+  return request->callback == NULL;
+}
+
+time_t auth_client_request_get_create_time(struct auth_client_request *request)
+{
+  return request->created;
+}
+
+void auth_client_request_server_input(struct auth_client_request *request,
+              enum auth_request_status status,
+              const char *const *args)
+{
+  const char *const *tmp, *base64_data = NULL;
+
+  if (request->callback == NULL) {
+    /* aborted already */
+    return;
+  }
+
+  switch (status) {
+  case AUTH_REQUEST_STATUS_OK:
+    for (tmp = args; *tmp != NULL; tmp++) {
+      if (strncmp(*tmp, "resp=", 5) == 0) {
+        base64_data = *tmp + 5;
+        break;
+      }
+    }
+    break;
+  case AUTH_REQUEST_STATUS_CONTINUE:
+    base64_data = args[0];
+    args = NULL;
+    break;
+  case AUTH_REQUEST_STATUS_FAIL:
+  case AUTH_REQUEST_STATUS_INTERNAL_FAIL:
+  case AUTH_REQUEST_STATUS_ABORT:
+    break;
+  }
+
+  call_callback(request, status, base64_data, args);
+  if (status != AUTH_REQUEST_STATUS_CONTINUE)
+    pool_unref(&request->pool);
+}
+
+void auth_client_send_cancel(struct auth_client *client, unsigned int id)
+{
+  const char *str = t_strdup_printf("CANCEL\t%u\n", id);
+
+  if (o_stream_send_str(client->conn->output, str) < 0)
+    i_error("Error sending request to auth server: %m");
+}

+ 489 - 0
data/Dockerfiles/dovecot/auth-server-connection.c

@@ -0,0 +1,489 @@
+/* Copyright (c) 2003-2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "hostpid.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "net.h"
+#include "strescape.h"
+#include "eacces-error.h"
+#include "auth-client-private.h"
+#include "auth-client-request.h"
+#include "auth-server-connection.h"
+
+#include <unistd.h>
+
+#define AUTH_SERVER_CONN_MAX_LINE_LENGTH AUTH_CLIENT_MAX_LINE_LENGTH
+#define AUTH_HANDSHAKE_TIMEOUT (30*1000)
+#define AUTH_SERVER_RECONNECT_TIMEOUT_SECS 5
+
+static void
+auth_server_connection_reconnect(struct auth_server_connection *conn,
+         const char *disconnect_reason);
+
+static int
+auth_server_input_mech(struct auth_server_connection *conn,
+           const char *const *args)
+{
+  struct auth_mech_desc mech_desc;
+
+  if (conn->handshake_received) {
+    i_error("BUG: Authentication server already sent handshake");
+    return -1;
+  }
+  if (args[0] == NULL) {
+    i_error("BUG: Authentication server sent broken MECH line");
+    return -1;
+  }
+
+  i_zero(&mech_desc);
+  mech_desc.name = p_strdup(conn->pool, args[0]);
+
+  if (strcmp(mech_desc.name, "PLAIN") == 0)
+    conn->has_plain_mech = TRUE;
+
+  for (args++; *args != NULL; args++) {
+    if (strcmp(*args, "private") == 0)
+      mech_desc.flags |= MECH_SEC_PRIVATE;
+    else if (strcmp(*args, "anonymous") == 0)
+      mech_desc.flags |= MECH_SEC_ANONYMOUS;
+    else if (strcmp(*args, "plaintext") == 0)
+      mech_desc.flags |= MECH_SEC_PLAINTEXT;
+    else if (strcmp(*args, "dictionary") == 0)
+      mech_desc.flags |= MECH_SEC_DICTIONARY;
+    else if (strcmp(*args, "active") == 0)
+      mech_desc.flags |= MECH_SEC_ACTIVE;
+    else if (strcmp(*args, "forward-secrecy") == 0)
+      mech_desc.flags |= MECH_SEC_FORWARD_SECRECY;
+    else if (strcmp(*args, "mutual-auth") == 0)
+      mech_desc.flags |= MECH_SEC_MUTUAL_AUTH;
+  }
+  array_append(&conn->available_auth_mechs, &mech_desc, 1);
+  return 0;
+}
+
+static int
+auth_server_input_spid(struct auth_server_connection *conn,
+           const char *const *args)
+{
+  if (conn->handshake_received) {
+    i_error("BUG: Authentication server already sent handshake");
+    return -1;
+  }
+
+  if (str_to_uint(args[0], &conn->server_pid) < 0) {
+    i_error("BUG: Authentication server sent invalid PID");
+    return -1;
+  }
+  return 0;
+}
+
+static int
+auth_server_input_cuid(struct auth_server_connection *conn,
+           const char *const *args)
+{
+  if (conn->handshake_received) {
+    i_error("BUG: Authentication server already sent handshake");
+    return -1;
+  }
+  if (args[0] == NULL ||
+      str_to_uint(args[0], &conn->connect_uid) < 0) {
+    i_error("BUG: Authentication server sent broken CUID line");
+    return -1;
+  }
+  return 0;
+}
+
+static int
+auth_server_input_cookie(struct auth_server_connection *conn,
+       const char *const *args)
+{
+  if (conn->cookie != NULL) {
+    i_error("BUG: Authentication server already sent cookie");
+    return -1;
+  }
+  conn->cookie = p_strdup(conn->pool, args[0]);
+  return 0;
+}
+
+static int auth_server_input_done(struct auth_server_connection *conn)
+{
+  if (array_count(&conn->available_auth_mechs) == 0) {
+    i_error("BUG: Authentication server returned no mechanisms");
+    return -1;
+  }
+  if (conn->cookie == NULL) {
+    i_error("BUG: Authentication server didn't send a cookie");
+    return -1;
+  }
+
+  if (conn->to != NULL)
+    timeout_remove(&conn->to);
+
+  conn->handshake_received = TRUE;
+  if (conn->client->connect_notify_callback != NULL) {
+    conn->client->connect_notify_callback(conn->client, TRUE,
+        conn->client->connect_notify_context);
+  }
+  return 0;
+}
+
+static int
+auth_server_lookup_request(struct auth_server_connection *conn,
+         const char *id_arg, bool remove,
+         struct auth_client_request **request_r)
+{
+  struct auth_client_request *request;
+  unsigned int id;
+
+  if (id_arg == NULL || str_to_uint(id_arg, &id) < 0) {
+    i_error("BUG: Authentication server input missing ID");
+    return -1;
+  }
+
+  request = hash_table_lookup(conn->requests, POINTER_CAST(id));
+  if (request == NULL) {
+    i_error("BUG: Authentication server sent unknown id %u", id);
+    return -1;
+  }
+  if (remove || auth_client_request_is_aborted(request))
+    hash_table_remove(conn->requests, POINTER_CAST(id));
+
+  *request_r = request;
+  return 0;
+}
+
+static int
+auth_server_input_ok(struct auth_server_connection *conn,
+         const char *const *args)
+{
+  struct auth_client_request *request;
+
+  if (auth_server_lookup_request(conn, args[0], TRUE, &request) < 0)
+    return -1;
+  auth_client_request_server_input(request, AUTH_REQUEST_STATUS_OK,
+           args + 1);
+  return 0;
+}
+
+static int auth_server_input_cont(struct auth_server_connection *conn,
+          const char *const *args)
+{
+  struct auth_client_request *request;
+
+  if (str_array_length(args) < 2) {
+    i_error("BUG: Authentication server sent broken CONT line");
+    return -1;
+  }
+
+  if (auth_server_lookup_request(conn, args[0], FALSE, &request) < 0)
+    return -1;
+  auth_client_request_server_input(request, AUTH_REQUEST_STATUS_CONTINUE,
+           args + 1);
+  return 0;
+}
+
+static int auth_server_input_fail(struct auth_server_connection *conn,
+          const char *const *args)
+{
+  struct auth_client_request *request;
+
+  if (auth_server_lookup_request(conn, args[0], TRUE, &request) < 0)
+    return -1;
+  auth_client_request_server_input(request, AUTH_REQUEST_STATUS_FAIL,
+           args + 1);
+  return 0;
+}
+
+static int
+auth_server_connection_input_line(struct auth_server_connection *conn,
+          const char *line)
+{
+  const char *const *args;
+
+  if (conn->client->debug)
+    i_debug("auth input: %s", line);
+
+  args = t_strsplit_tabescaped(line);
+  if (args[0] == NULL) {
+    i_error("Auth server sent empty line");
+    return -1;
+  }
+  if (strcmp(args[0], "OK") == 0)
+    return auth_server_input_ok(conn, args + 1);
+  else if (strcmp(args[0], "CONT") == 0)
+    return auth_server_input_cont(conn, args + 1);
+  else if (strcmp(args[0], "FAIL") == 0)
+    return auth_server_input_fail(conn, args + 1);
+  else if (strcmp(args[0], "MECH") == 0)
+    return auth_server_input_mech(conn, args + 1);
+  else if (strcmp(args[0], "SPID") == 0)
+    return auth_server_input_spid(conn, args + 1);
+  else if (strcmp(args[0], "CUID") == 0)
+    return auth_server_input_cuid(conn, args + 1);
+  else if (strcmp(args[0], "COOKIE") == 0)
+    return auth_server_input_cookie(conn, args + 1);
+  else if (strcmp(args[0], "DONE") == 0)
+    return auth_server_input_done(conn);
+  else {
+    i_error("Auth server sent unknown command: %s", args[0]);
+    return -1;
+  }
+}
+
+static void auth_server_connection_input(struct auth_server_connection *conn)
+{
+  struct istream *input;
+  const char *line, *error;
+  int ret;
+
+  switch (i_stream_read(conn->input)) {
+  case 0:
+    return;
+  case -1:
+    /* disconnected */
+    error = conn->input->stream_errno != 0 ?
+      strerror(conn->input->stream_errno) : "EOF";
+    auth_server_connection_reconnect(conn, error);
+    return;
+  case -2:
+    /* buffer full - can't happen unless auth is buggy */
+    i_error("BUG: Auth server sent us more than %d bytes of data",
+      AUTH_SERVER_CONN_MAX_LINE_LENGTH);
+    auth_server_connection_disconnect(conn, "buffer full");
+    return;
+  }
+
+  if (!conn->version_received) {
+    line = i_stream_next_line(conn->input);
+    if (line == NULL)
+      return;
+
+    /* make sure the major version matches */
+    if (strncmp(line, "VERSION\t", 8) != 0 ||
+        !str_uint_equals(t_strcut(line + 8, '\t'),
+             AUTH_CLIENT_PROTOCOL_MAJOR_VERSION)) {
+      i_error("Authentication server not compatible with "
+        "this client (mixed old and new binaries?)");
+      auth_server_connection_disconnect(conn,
+        "incompatible server");
+      return;
+    }
+    conn->version_received = TRUE;
+  }
+
+  input = conn->input;
+  i_stream_ref(input);
+  while ((line = i_stream_next_line(input)) != NULL && !input->closed) {
+    T_BEGIN {
+      ret = auth_server_connection_input_line(conn, line);
+    } T_END;
+
+    if (ret < 0) {
+      auth_server_connection_disconnect(conn, t_strdup_printf(
+        "Received broken input: %s", line));
+      break;
+    }
+  }
+  i_stream_unref(&input);
+}
+
+struct auth_server_connection *
+auth_server_connection_init(struct auth_client *client)
+{
+  struct auth_server_connection *conn;
+  pool_t pool;
+
+  pool = pool_alloconly_create("auth server connection", 1024);
+  conn = p_new(pool, struct auth_server_connection, 1);
+  conn->pool = pool;
+
+  conn->client = client;
+  conn->fd = -1;
+  hash_table_create_direct(&conn->requests, pool, 100);
+  i_array_init(&conn->available_auth_mechs, 8);
+  return conn;
+}
+
+static void
+auth_server_connection_remove_requests(struct auth_server_connection *conn,
+               const char *disconnect_reason)
+{
+  static const char *const temp_failure_args[] = { "temp", NULL };
+  struct hash_iterate_context *iter;
+  void *key;
+  struct auth_client_request *request;
+  time_t created, oldest = 0;
+  unsigned int request_count = 0;
+
+  if (hash_table_count(conn->requests) == 0)
+    return;
+
+  iter = hash_table_iterate_init(conn->requests);
+  while (hash_table_iterate(iter, conn->requests, &key, &request)) {
+    if (!auth_client_request_is_aborted(request)) {
+      request_count++;
+      created = auth_client_request_get_create_time(request);
+      if (oldest > created || oldest == 0)
+        oldest = created;
+    }
+
+    auth_client_request_server_input(request,
+      AUTH_REQUEST_STATUS_INTERNAL_FAIL,
+      temp_failure_args);
+  }
+  hash_table_iterate_deinit(&iter);
+  hash_table_clear(conn->requests, FALSE);
+
+  if (request_count > 0) {
+    i_warning("Auth connection closed with %u pending requests "
+        "(max %u secs, pid=%s, %s)", request_count,
+        (unsigned int)(ioloop_time - oldest),
+        my_pid, disconnect_reason);
+  }
+}
+
+void auth_server_connection_disconnect(struct auth_server_connection *conn,
+               const char *reason)
+{
+  conn->handshake_received = FALSE;
+  conn->version_received = FALSE;
+  conn->has_plain_mech = FALSE;
+  conn->server_pid = 0;
+  conn->connect_uid = 0;
+  conn->cookie = NULL;
+  array_clear(&conn->available_auth_mechs);
+
+  if (conn->to != NULL)
+    timeout_remove(&conn->to);
+  if (conn->io != NULL)
+    io_remove(&conn->io);
+  if (conn->fd != -1) {
+    i_stream_destroy(&conn->input);
+    o_stream_destroy(&conn->output);
+
+    if (close(conn->fd) < 0)
+      i_error("close(auth server connection) failed: %m");
+    conn->fd = -1;
+  }
+
+  auth_server_connection_remove_requests(conn, reason);
+
+  if (conn->client->connect_notify_callback != NULL) {
+    conn->client->connect_notify_callback(conn->client, FALSE,
+        conn->client->connect_notify_context);
+  }
+}
+
+static void auth_server_reconnect_timeout(struct auth_server_connection *conn)
+{
+  (void)auth_server_connection_connect(conn);
+}
+
+static void
+auth_server_connection_reconnect(struct auth_server_connection *conn,
+         const char *disconnect_reason)
+{
+  time_t next_connect;
+
+  auth_server_connection_disconnect(conn, disconnect_reason);
+
+  next_connect = conn->last_connect + AUTH_SERVER_RECONNECT_TIMEOUT_SECS;
+  conn->to = timeout_add(ioloop_time >= next_connect ? 0 :
+             (next_connect - ioloop_time) * 1000,
+             auth_server_reconnect_timeout, conn);
+}
+
+void auth_server_connection_deinit(struct auth_server_connection **_conn)
+{
+        struct auth_server_connection *conn = *_conn;
+
+  *_conn = NULL;
+
+  auth_server_connection_disconnect(conn, "deinitializing");
+  i_assert(hash_table_count(conn->requests) == 0);
+  hash_table_destroy(&conn->requests);
+  array_free(&conn->available_auth_mechs);
+  pool_unref(&conn->pool);
+}
+
+static void auth_client_handshake_timeout(struct auth_server_connection *conn)
+{
+  i_error("Timeout waiting for handshake from auth server. "
+    "my pid=%u, input bytes=%"PRIuUOFF_T,
+    conn->client->client_pid, conn->input->v_offset);
+  auth_server_connection_reconnect(conn, "auth server timeout");
+}
+
+int auth_server_connection_connect(struct auth_server_connection *conn)
+{
+  const char *handshake;
+  int fd;
+
+  i_assert(conn->fd == -1);
+
+  conn->last_connect = ioloop_time;
+  if (conn->to != NULL)
+    timeout_remove(&conn->to);
+
+  /* max. 1 second wait here. */
+  fd = net_connect_unix_with_retries(conn->client->auth_socket_path,
+             1000);
+  if (fd == -1) {
+    if (errno == EACCES) {
+      i_error("auth: %s",
+        eacces_error_get("connect",
+          conn->client->auth_socket_path));
+    } else {
+      i_error("auth: connect(%s) failed: %m",
+        conn->client->auth_socket_path);
+    }
+    return -1;
+  }
+  conn->fd = fd;
+  conn->io = io_add(fd, IO_READ, auth_server_connection_input, conn);
+  conn->input = i_stream_create_fd(fd, AUTH_SERVER_CONN_MAX_LINE_LENGTH,
+           FALSE);
+  conn->output = o_stream_create_fd(fd, (size_t)-1, FALSE);
+
+  handshake = t_strdup_printf("VERSION\t%u\t%u\nCPID\t%u\n",
+            AUTH_CLIENT_PROTOCOL_MAJOR_VERSION,
+                                    AUTH_CLIENT_PROTOCOL_MINOR_VERSION,
+            conn->client->client_pid);
+  if (o_stream_send_str(conn->output, handshake) < 0) {
+    i_warning("Error sending handshake to auth server: %s",
+        o_stream_get_error(conn->output));
+    auth_server_connection_disconnect(conn,
+      o_stream_get_error(conn->output));
+    return -1;
+  }
+
+  conn->to = timeout_add(AUTH_HANDSHAKE_TIMEOUT,
+             auth_client_handshake_timeout, conn);
+  return 0;
+}
+
+unsigned int
+auth_server_connection_add_request(struct auth_server_connection *conn,
+           struct auth_client_request *request)
+{
+  unsigned int id;
+
+  id = ++conn->client->request_id_counter;
+  if (id == 0) {
+    /* wrapped - ID 0 not allowed */
+    id = ++conn->client->request_id_counter;
+  }
+  i_assert(hash_table_lookup(conn->requests, POINTER_CAST(id)) == NULL);
+  hash_table_insert(conn->requests, POINTER_CAST(id), request);
+  return id;
+}
+
+void auth_server_connection_remove_request(struct auth_server_connection *conn, unsigned int id)
+{
+  i_assert(conn->handshake_received);
+  hash_table_remove(conn->requests, POINTER_CAST(id));
+}

+ 43 - 0
data/Dockerfiles/dovecot/auth-server-connection.h

@@ -0,0 +1,43 @@
+#ifndef AUTH_SERVER_CONNECTION_H
+#define AUTH_SERVER_CONNECTION_H
+
+struct auth_server_connection {
+  pool_t pool;
+
+  struct auth_client *client;
+  int fd;
+  time_t last_connect;
+
+  struct io *io;
+  struct timeout *to;
+  struct istream *input;
+  struct ostream *output;
+
+  unsigned int server_pid;
+  unsigned int connect_uid;
+  char *cookie;
+
+  ARRAY(struct auth_mech_desc) available_auth_mechs;
+
+  /* id => request */
+  HASH_TABLE(void *, struct auth_client_request *) requests;
+
+  unsigned int version_received:1;
+  unsigned int handshake_received:1;
+  unsigned int has_plain_mech:1;
+};
+
+struct auth_server_connection *
+auth_server_connection_init(struct auth_client *client);
+void auth_server_connection_deinit(struct auth_server_connection **conn);
+
+int auth_server_connection_connect(struct auth_server_connection *conn);
+void auth_server_connection_disconnect(struct auth_server_connection *conn,
+               const char *reason);
+
+unsigned int
+auth_server_connection_add_request(struct auth_server_connection *conn,
+           struct auth_client_request *request);
+void auth_server_connection_remove_request(struct auth_server_connection *conn, unsigned int id);
+#endif
+

+ 1 - 1
docker-compose.yml

@@ -148,7 +148,7 @@ services:
             - sogo
 
     dovecot-mailcow:
-      image: mailcow/dovecot:1.19
+      image: mailcow/dovecot:1.20
       build: ./data/Dockerfiles/dovecot
       cap_add:
         - NET_BIND_SERVICE