From 7d2f2d360f5a8ac6e0055074db813c3c3cfbeeb4 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Thu, 23 Jan 2020 15:29:31 +0000 Subject: [PATCH 1/1] Dovecot auth: inet socket. Bug 2280 --- doc/doc-txt/NewStuff | 3 + doc/doc-txt/experimental-spec.txt | 30 ++++++++ src/src/auths/dovecot.c | 110 +++++++++++++++++------------ src/src/auths/dovecot.h | 4 +- src/src/functions.h | 4 +- src/src/ip.c | 12 ++-- src/src/malware.c | 4 +- src/src/spam.c | 2 +- test/confs/3901 | 67 ++++++++++++++++++ test/confs/9350 | 2 +- test/log/3901 | 7 ++ test/scripts/3900-Dovecot/3901 | 38 ++++++++++ test/scripts/3900-Dovecot/REQUIRES | 1 + 13 files changed, 228 insertions(+), 56 deletions(-) create mode 100644 test/confs/3901 create mode 100644 test/log/3901 create mode 100644 test/scripts/3900-Dovecot/3901 create mode 100644 test/scripts/3900-Dovecot/REQUIRES diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index e21446533..8ae95a7fb 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -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 ------------ diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt index 2569ad3af..6e47b95c2 100644 --- a/doc/doc-txt/experimental-spec.txt +++ b/doc/doc-txt/experimental-spec.txt @@ -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 -------------------------------------------------------------- diff --git a/src/src/auths/dovecot.c b/src/src/auths/dovecot.c index c337510b5..9b0d437da 100644 --- a/src/src/auths/dovecot.c +++ b/src/src/auths/dovecot.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2004 Andrey Panin - * 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; diff --git a/src/src/auths/dovecot.h b/src/src/auths/dovecot.h index ea3f04dab..373f729bb 100644 --- a/src/src/auths/dovecot.h +++ b/src/src/auths/dovecot.h @@ -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. */ diff --git a/src/src/functions.h b/src/src/functions.h index 57314a677..473fb8759 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -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 *); diff --git a/src/src/ip.c b/src/src/ip.c index bf332b160..a6b7de389 100644 --- a/src/src/ip.c +++ b/src/src/ip.c @@ -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); } /************************************************* diff --git a/src/src/malware.c b/src/src/malware.c index cfff9ee5d..a4080d040 100644 --- a/src/src/malware.c +++ b/src/src/malware.c @@ -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; } diff --git a/src/src/spam.c b/src/src/spam.c index 4cc4a9ae0..6954bee32 100644 --- a/src/src/spam.c +++ b/src/src/spam.c @@ -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 index 000000000..5c7e7298a --- /dev/null +++ b/test/confs/3901 @@ -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 diff --git a/test/confs/9350 b/test/confs/9350 index 1ac5ebe5f..290fadc6d 100644 --- a/test/confs/9350 +++ b/test/confs/9350 @@ -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 index 000000000..e9cef9837 --- /dev/null +++ b/test/log/3901 @@ -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 index 000000000..1fc50017f --- /dev/null +++ b/test/scripts/3900-Dovecot/3901 @@ -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 +<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 index 000000000..575bc6b6b --- /dev/null +++ b/test/scripts/3900-Dovecot/REQUIRES @@ -0,0 +1 @@ +authenticator dovecot -- 2.30.2