Dovecot auth: inet socket. Bug 2280
authorJeremy Harris <jgh146exb@wizmail.org>
Thu, 23 Jan 2020 15:29:31 +0000 (15:29 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Thu, 23 Jan 2020 16:48:23 +0000 (16:48 +0000)
13 files changed:
doc/doc-txt/NewStuff
doc/doc-txt/experimental-spec.txt
src/src/auths/dovecot.c
src/src/auths/dovecot.h
src/src/functions.h
src/src/ip.c
src/src/malware.c
src/src/spam.c
test/confs/3901 [new file with mode: 0644]
test/confs/9350
test/log/3901 [new file with mode: 0644]
test/scripts/3900-Dovecot/3901 [new file with mode: 0644]
test/scripts/3900-Dovecot/REQUIRES [new file with mode: 0644]

index e214465..8ae95a7 100644 (file)
@@ -30,6 +30,9 @@ Version 4.94
  7. Named-list definitions can now be prefixed "hide" so that "-bP" commands do
     not output the content.  Previously this could only be done on options.
 
+ 8. As an exerimental feature, the dovecot authenticatino driver supports inet
+    sockets.  Previously it was unix-domain sockets only.
+
 
 Version 4.93
 ------------
index 2569ad3..6e47b95 100644 (file)
@@ -808,6 +808,36 @@ Issues:
    hosts_require_ocsp will fail
 
 
+
+Dovecot authenticator via inet socket
+------------------------------------
+If Dovecot is configured similar to :-
+
+service auth {
+...
+#SASL
+  inet_listener {
+    name = exim
+    port = 12345
+    ssl = yes
+  }
+...
+}
+
+then an Exim authenticator can be configured :-
+
+  dovecot-plain:
+    driver =           dovecot
+    public_name =      PLAIN
+    server_socket =    dovecot_server_name 12345
+    server_tls =       true
+    server_set_id =    $auth1
+
+If the server_socket does not start with a / it is taken as a hostname (or IP);
+and a whitespace-separated port number must be given.
+
+
+
 --------------------------------------------------------------
 End of file
 --------------------------------------------------------------
index c337510..9b0d437 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 2004 Andrey Panin <pazke@donpac.ru>
- * Copyright (c) 2006-2017 The Exim Maintainers
+ * Copyright (c) 2006-2019 The Exim Maintainers
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published
@@ -51,9 +51,8 @@ The cost is the length of an array of pointers on the stack.
 
 /* Options specific to the authentication mechanism. */
 optionlist auth_dovecot_options[] = {
-       { "server_socket", opt_stringptr,
-        OPT_OFF(auth_dovecot_options_block, server_socket)
-       },
+  { "server_socket", opt_stringptr, OPT_OFF(auth_dovecot_options_block, server_socket) },
+/*{ "server_tls", opt_bool, OPT_OFF(auth_dovecot_options_block, server_tls) },*/
 };
 
 /* Size of the options list. An extern variable has to be used so that its
@@ -64,7 +63,8 @@ int auth_dovecot_options_count = nelem(auth_dovecot_options);
 /* Default private options block for the authentication method. */
 
 auth_dovecot_options_block auth_dovecot_option_defaults = {
-       NULL,                           /* server_socket */
+       .server_socket = NULL,
+/*     .server_tls =   FALSE,*/
 };
 
 
@@ -197,7 +197,7 @@ else
 C-style buffered I/O gave trouble. */
 
 static uschar *
-dc_gets(uschar *s, int n, int fd)
+dc_gets(uschar *s, int n, client_conn_ctx * cctx)
 {
 int p = 0;
 int count = 0;
@@ -206,8 +206,15 @@ for (;;)
   {
   if (socket_buffer_left == 0)
     {
-    if ((socket_buffer_left = read(fd, sbuffer, sizeof(sbuffer))) <= 0)
-      if (count == 0) return NULL; else break;
+    if ((socket_buffer_left =
+#ifndef DISABLE_TLS
+       cctx->tls_ctx ? tls_read(cctx->tls_ctx, sbuffer, sizeof(sbuffer)) :
+#endif
+       read(cctx->sock, sbuffer, sizeof(sbuffer))) <= 0)
+      if (count == 0)
+       return NULL;
+      else
+       break;
     p = 0;
     }
 
@@ -240,14 +247,15 @@ auth_dovecot_server(auth_instance * ablock, uschar * data)
 {
 auth_dovecot_options_block *ob =
        (auth_dovecot_options_block *) ablock->options_block;
-struct sockaddr_un sa;
 uschar buffer[DOVECOT_AUTH_MAXLINELEN];
 uschar *args[DOVECOT_AUTH_MAXFIELDCOUNT];
 uschar *auth_command;
 uschar *auth_extra_data = US"";
 uschar *p;
 int nargs, tmp;
-int crequid = 1, cont = 1, fd = -1, ret = DEFER;
+int crequid = 1, ret = DEFER;
+host_item host;
+client_conn_ctx cctx = {.sock = -1, .tls_ctx = NULL};
 BOOL found = FALSE, have_mech_line = FALSE;
 
 HDEBUG(D_auth) debug_printf("dovecot authentication\n");
@@ -258,50 +266,48 @@ if (!data)
   goto out;
   }
 
-memset(&sa, 0, sizeof(sa));
-sa.sun_family = AF_UNIX;
+/*XXX timeout? */
+cctx.sock = ip_streamsocket(ob->server_socket, &auth_defer_msg, 5, &host);
+if (cctx.sock < 0)
+ goto out;
 
-/* This was the original code here: it is nonsense because strncpy()
-does not return an integer. I have converted this to use the function
-that formats and checks length. PH */
-
-/*
-if (strncpy(sa.sun_path, ob->server_socket, sizeof(sa.sun_path)) < 0) {
-}
-*/
-
-if (!string_format(US sa.sun_path, sizeof(sa.sun_path), "%s",
-                 ob->server_socket))
+#ifdef notdef
+# ifndef DISABLE_TLS
+if (ob->server_tls)
   {
-  auth_defer_msg = US"authentication socket path too long";
-  return DEFER;
-  }
-
-auth_defer_msg = US"authentication socket connection error";
+  uschar * s;
+  smtp_connect_args conn_args = { .host = &host };
+  tls_support tls_dummy = {.sni=NULL};
+  uschar * errstr;
 
-if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
-  return DEFER;
-
-if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0)
-  goto out;
+  if (!tls_client_start(&cctx, &conn_args, NULL, &tls_dummy, &errstr))
+    {
+    auth_defer_msg = string_sprintf("TLS connect failed: %s", errstr);
+    goto out;
+    }
+  }
+# endif
+#endif
 
 auth_defer_msg = US"authentication socket protocol error";
 
 socket_buffer_left = 0;  /* Global, used to read more than a line but return by line */
-while (cont)
+for (;;)
   {
-  if (!dc_gets(buffer, sizeof(buffer), fd))
+debug_printf("%s %d\n", __FUNCTION__, __LINE__);
+  if (!dc_gets(buffer, sizeof(buffer), &cctx))
     OUT("authentication socket read error or premature eof");
+debug_printf("%s %d\n", __FUNCTION__, __LINE__);
   p = buffer + Ustrlen(buffer) - 1;
   if (*p != '\n')
     OUT("authentication socket protocol line too long");
 
   *p = '\0';
-  HDEBUG(D_auth) debug_printf("received: %s\n", buffer);
+  HDEBUG(D_auth) debug_printf("received: '%s'\n", buffer);
 
   nargs = strcut(buffer, args, nelem(args));
 
-  /* HDEBUG(D_auth) debug_strcut(args, nargs, nelem(args)); */
+  HDEBUG(D_auth) debug_strcut(args, nargs, nelem(args));
 
   /* Code below rewritten by Kirill Miazine (km@krot.org). Only check commands that
     Exim will need. Original code also failed if Dovecot server sent unknown
@@ -344,7 +350,7 @@ while (cont)
   else if (Ustrcmp(args[0], US"DONE") == 0)
     {
     CHECK_COMMAND("DONE", 0, 0);
-    cont = 0;
+    break;
     }
   }
 
@@ -399,26 +405,31 @@ auth_command = string_sprintf("VERSION\t%d\t%d\nCPID\t%d\n"
        ablock->public_name, auth_extra_data, sender_host_address,
        interface_address, data);
 
-if (write(fd, auth_command, Ustrlen(auth_command)) < 0)
+if ((
+#ifndef DISABLE_TLS
+    cctx.tls_ctx ? tls_write(cctx.tls_ctx, auth_command, Ustrlen(auth_command), FALSE) :
+#endif
+    write(cctx.sock, auth_command, Ustrlen(auth_command))) < 0)
   HDEBUG(D_auth) debug_printf("error sending auth_command: %s\n",
     strerror(errno));
 
-HDEBUG(D_auth) debug_printf("sent: %s", auth_command);
+HDEBUG(D_auth) debug_printf("sent: '%s'\n", auth_command);
 
 while (1)
   {
   uschar *temp;
   uschar *auth_id_pre = NULL;
 
-  if (!dc_gets(buffer, sizeof(buffer), fd))
+  if (!dc_gets(buffer, sizeof(buffer), &cctx))
     {
     auth_defer_msg = US"authentication socket read error or premature eof";
     goto out;
     }
 
   buffer[Ustrlen(buffer) - 1] = 0;
-  HDEBUG(D_auth) debug_printf("received: %s\n", buffer);
+  HDEBUG(D_auth) debug_printf("received: '%s'\n", buffer);
   nargs = strcut(buffer, args, nelem(args));
+  HDEBUG(D_auth) debug_strcut(args, nargs, nelem(args));
 
   if (Uatoi(args[1]) != crequid)
     OUT("authentication socket connection id mismatch");
@@ -444,7 +455,11 @@ while (1)
        }
 
       temp = string_sprintf("CONT\t%d\t%s\n", crequid, data);
-      if (write(fd, temp, Ustrlen(temp)) < 0)
+      if ((
+#ifndef DISABLE_TLS
+         cctx.tls_ctx ? tls_write(cctx.tls_ctx, temp, Ustrlen(temp), FALSE) :
+#endif
+         write(cctx.sock, temp, Ustrlen(temp))) < 0)
        OUT("authentication socket write error");
       break;
 
@@ -480,6 +495,7 @@ while (1)
       if (!auth_id_pre)
         OUT("authentication socket protocol error, username missing");
 
+      auth_defer_msg = NULL;
       ret = OK;
       /* fallthrough */
 
@@ -490,8 +506,12 @@ while (1)
 
 out:
 /* close the socket used by dovecot */
-if (fd >= 0)
-  close(fd);
+#ifndef DISABLE_TLS
+if (cctx.tls_ctx)
+  tls_close(cctx.tls_ctx, TRUE);
+#endif
+if (cctx.sock >= 0)
+  close(cctx.sock);
 
 /* Expand server_condition as an authorization check */
 return ret == OK ? auth_check_serv_cond(ablock) : ret;
index ea3f04d..373f729 100644 (file)
@@ -3,12 +3,14 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) The Exim Maintainters 2019 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Private structure for the private options. */
 
 typedef struct {
-  uschar *server_socket;
+  uschar *     server_socket;
+  BOOL         server_tls;
 } auth_dovecot_options_block;
 
 /* Data for reading the private options. */
index 57314a6..473fb87 100644 (file)
@@ -287,9 +287,9 @@ extern void    ip_keepalive(int, const uschar *, BOOL);
 extern int     ip_recv(client_conn_ctx *, uschar *, int, time_t);
 extern int     ip_socket(int, int);
 
-extern int     ip_tcpsocket(const uschar *, uschar **, int);
+extern int     ip_tcpsocket(const uschar *, uschar **, int, host_item *);
 extern int     ip_unixsocket(const uschar *, uschar **);
-extern int     ip_streamsocket(const uschar *, uschar **, int);
+extern int     ip_streamsocket(const uschar *, uschar **, int, host_item *);
 
 extern int     ipv6_nmtoa(int *, uschar *);
 
index bf332b1..a6b7de3 100644 (file)
@@ -482,7 +482,8 @@ bad:
 
 /*XXX TFO? */
 int
-ip_tcpsocket(const uschar * hostport, uschar ** errstr, int tmo)
+ip_tcpsocket(const uschar * hostport, uschar ** errstr, int tmo,
+  host_item * connhost)
 {
 int scan;
 uschar hostname[256];
@@ -501,7 +502,7 @@ if (scan != 3)
   }
 
 return ip_connectedsocket(SOCK_STREAM, hostname, portlow, porthigh,
-                         tmo, NULL, errstr, NULL);
+                         tmo, connhost, errstr, NULL);
 }
 
 int
@@ -534,12 +535,15 @@ return sock;
 /* spec is either an absolute path (with a leading /), or
 a host (name or IP) and port (whitespace-separated).
 The port can be a range, dash-separated, or a single number.
+
+For a TCP socket, optionally fill in a  host_item.
 */
 int
-ip_streamsocket(const uschar * spec, uschar ** errstr, int tmo)
+ip_streamsocket(const uschar * spec, uschar ** errstr, int tmo,
+  host_item * connhost)
 {
 return *spec == '/'
-  ? ip_unixsocket(spec, errstr) : ip_tcpsocket(spec, errstr, tmo);
+  ? ip_unixsocket(spec, errstr) : ip_tcpsocket(spec, errstr, tmo, connhost);
 }
 
 /*************************************************
index cfff9ee..a4080d0 100644 (file)
@@ -654,11 +654,11 @@ if (!malware_ok)
     switch(scanent->conn)
     {
     case MC_TCP:
-      malware_daemon_ctx.sock = ip_tcpsocket(scanner_options, &errstr, 5);     break;
+      malware_daemon_ctx.sock = ip_tcpsocket(scanner_options, &errstr, 5, NULL); break;
     case MC_UNIX:
       malware_daemon_ctx.sock = ip_unixsocket(scanner_options, &errstr);       break;
     case MC_STRM:
-      malware_daemon_ctx.sock = ip_streamsocket(scanner_options, &errstr, 5);  break;
+      malware_daemon_ctx.sock = ip_streamsocket(scanner_options, &errstr, 5, NULL); break;
     default:
       /* compiler quietening */ break;
     }
index 4cc4a9a..6954bee 100644 (file)
@@ -344,7 +344,7 @@ start = time(NULL);
     for (;;)
       {
       /*XXX could potentially use TFO early-data here */
-      if (  (spamd_cctx.sock = ip_streamsocket(sd->hostspec, &errstr, 5)) >= 0
+      if (  (spamd_cctx.sock = ip_streamsocket(sd->hostspec, &errstr, 5, NULL)) >= 0
          || sd->retry <= 0
         )
        break;
diff --git a/test/confs/3901 b/test/confs/3901
new file mode 100644 (file)
index 0000000..5c7e729
--- /dev/null
@@ -0,0 +1,67 @@
+# Exim test configuration 9351
+
+SERVER=
+
+.include DIR/aux-var/std_conf_prefix
+
+primary_hostname = myhost.test.ex
+
+# ----- Main settings -----
+
+acl_smtp_rcpt = check_recipient
+
+tls_certificate = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail}
+tls_privatekey = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail}
+
+tls_verify_hosts = HOSTIPV4
+tls_verify_certificates = ${if eq {SERVER}{server}{DIR/aux-fixed/cert2}fail}
+
+queue_only
+
+# ----- ACL -----
+
+begin acl
+
+check_recipient:
+  deny     message = authentication required
+          !authenticated = *
+  accept
+
+
+# ----- Route -----
+
+begin routers
+
+all:
+  driver = accept
+  transport = server
+  errors_to =
+
+begin transports
+
+server:
+  driver =             smtp
+  hosts =              127.0.0.1
+  allow_localhost
+  port =               PORT_D
+  hosts_require_auth = *
+
+# ----- Authentication -----
+
+begin authenticators
+
+dovecot:
+  driver = dovecot
+  public_name = PLAIN
+  server_socket = 127.0.0.1 PORT_S
+.ifdef TRUSTED
+  server_tls = true
+.endif
+  server_set_id = $auth1
+
+client:
+  driver = plaintext
+  public_name = PLAIN
+  client_send = ^username^mysecret
+
+# End
index 1ac5ebe..290fadc 100644 (file)
@@ -1,4 +1,4 @@
-# Exim test configuration 3650
+# Exim test configuration 9350
 
 SERVER=
 
diff --git a/test/log/3901 b/test/log/3901
new file mode 100644 (file)
index 0000000..e9cef98
--- /dev/null
@@ -0,0 +1,7 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => a@test.ex R=all T=server H=127.0.0.1 [127.0.0.1] A=client C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= <> H=localhost (myhost.test.ex) [127.0.0.1] P=esmtpa A=dovecot:goodman S=sss id=E10HmaX-0005vi-00@myhost.test.ex
diff --git a/test/scripts/3900-Dovecot/3901 b/test/scripts/3900-Dovecot/3901
new file mode 100644 (file)
index 0000000..1fc5001
--- /dev/null
@@ -0,0 +1,38 @@
+# dovecot server, inet, PLAIN method
+#
+# This uses a script emulating dovecot so has potential to be wrong.
+# We could do with an independent testcase against a real Dovecot,
+# but that needs to be conditioned on finding that on the test
+# platform and configuring it to match the testcase.
+# See 9350 for a start.
+#
+exim -bd -DSERVER=server -oX PORT_D
+****
+server PORT_S
+>LF>VERSION\x091\x090
+>LF>MECH\x09PLAIN
+>LF>DONE
+<<VERSION\x091\x090
+<CPID
+<AUTH\x091\x09PLAIN\x09service=smtp
+>LF>OK\x091\x09user=goodman
+*eof
+****
+#
+exim -odi a@test.ex
+****
+#
+killdaemon
+#
+#exim -d+all -bd -DSERVER=server -DTRUSTED -oX PORT_D
+#****
+#background
+#perl -e "system('socat OPENSSL-LISTEN:PORT_S,reuseaddr,fork,cert=DIR/aux-fixed/cert1,verify=0 EXEC:\'/bin/echo VERSION\\t1\\t0\\nAUTH\\t1\\tPLAIN\\tservice=smtp\'');"
+#****
+##
+#exim -odi a@test.ex
+#****
+##
+#killdaemon
+no_stdout_check
+no_msglog_check
diff --git a/test/scripts/3900-Dovecot/REQUIRES b/test/scripts/3900-Dovecot/REQUIRES
new file mode 100644 (file)
index 0000000..575bc6b
--- /dev/null
@@ -0,0 +1 @@
+authenticator dovecot