Exim currently has support to perform authentication tests using
libsasl2, for the purposes of SMTP authentication. It's missing the
ability to query whether a local user exists, for the purpose of
determining whether to reject a RCPT.
In general, the thing to do in this case is to perform an LMTP callout
in order to determine whether there is a mailbox that exists for the
given recipient.
However, Cyrus IMAP now has a feature that allows mailboxes to be
automatically created upon successful login, or a delivery is made to a
mailbox that doesn't exist yet.
It would be exceedingly convenient to test for the existence of a local
user in the same SASL DB that Cyrus looks for users, rather than via
LMTP so that we can turn this automatic mailbox creation feature on
without arbitrarily creating a new mailbox for addresses no one will
ever log into. This patch for your consideration, allows for that by
introducing a new string expansion called "cyrus_sasl_user_exists", that
expects a username, optional service, and optional realm. Adding the
condition to your LMTP router will let you specify:
acl_check_rcpt:
accept domains = +local_domains
verify = recipient
begin routers
cyrus:
driver = accept
domains = +local_domains
condition = ${if cyrus_sasl_user_exists{{$local_part}{$domain}}{1}}
transport = lmtp
no_more
--- exim-4.85/src/auths/cyrus_sasl.h 2015-01-05 15:40:11.000000000 -0800
+++ exim-4.85/src/auths/cyrus_sasl.h 2015-08-05 16:28:40.829967985 -0700
@@ -32,5 +32,6 @@
extern int auth_cyrus_sasl_client(auth_instance *, smtp_inblock *,
smtp_outblock *, int, uschar *, int);
extern void auth_cyrus_sasl_version_report(FILE *f);
+extern int auth_cyrus_sasl_user_exists(uschar *, uschar *, uschar *);
/* End of cyrus_sasl.h */
--- exim-4.85/src/auths/cyrus_sasl.c 2015-01-05 15:40:11.000000000 -0800
+++ exim-4.85/src/auths/cyrus_sasl.c 2015-08-05 15:46:34.389231554 -0700
@@ -525,6 +525,58 @@
return FAIL;
}
+int
+auth_cyrus_sasl_user_exists(
+ uschar *username,
+ uschar *service,
+ uschar *realm)
+{
+ sasl_conn_t *conn;
+ sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
+ uschar *hostname;
+ int rc;
+
+ if (service == NULL)
+ service = US"smtp";
+ hostname = expand_string(US"$primary_hostname");
+
+ if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK)
+ {
+ HDEBUG(D_auth)
+ debug_printf("Failed to initialise Cyrus SASL library; err=\"%d\"\n", rc);
+ return ERROR;
+ }
+ else if ((rc = sasl_server_new(CS service, CS hostname,
+ CS realm, NULL, NULL, NULL, 0, &conn)) != SASL_OK)
+ {
+ HDEBUG(D_auth)
+ debug_printf("Failed to initialise Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\" err=\"%d\"\n",
+ service, hostname, realm, rc);
+ sasl_done();
+ return ERROR;
+ }
+ else
+ {
+ HDEBUG(D_auth)
+ debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
+ service, hostname, realm);
+ rc = sasl_user_exists(conn, "exim", CS realm, CS username);
+ sasl_dispose(&conn);
+ sasl_done();
+ if (rc == SASL_OK)
+ return OK;
+ else if (rc == SASL_NOUSER)
+ return FAIL;
+ else
+ {
+ HDEBUG(D_auth)
+ debug_printf("Cyrus SASL server failed to look up user; service=\"%s\" fqdn=\"%s\" realm=\"%s\" err=\"%d\"\n",
+ service, hostname, realm, rc);
+ return ERROR;
+ }
+ }
+}
+
#endif /* AUTH_CYRUS_SASL */
/* End of cyrus_sasl.c */
--- exim-4.85/src/expand.c 2015-01-05 15:40:11.000000000 -0800
+++ exim-4.85/src/expand.c 2015-08-05 17:15:14.539411195 -0700
@@ -26,6 +26,10 @@
#include "lookups/ldap.h"
#endif
+#ifdef AUTH_CYRUS_SASL
+#include "auths/cyrus_sasl.h"
+#endif
+
#ifdef SUPPORT_CRYPTEQ
#ifdef CRYPT_H
#include <crypt.h>
@@ -270,6 +274,7 @@
US"bool",
US"bool_lax",
US"crypteq",
+ US"cyrus_sasl_user_exists",
US"def",
US"eq",
US"eqi",
@@ -316,6 +321,7 @@
ECOND_BOOL,
ECOND_BOOL_LAX,
ECOND_CRYPTEQ,
+ ECOND_CYRUS_SASL_USER_EXISTS,
ECOND_DEF,
ECOND_STR_EQ,
ECOND_STR_EQI,
@@ -2342,6 +2348,45 @@
return s;
}
+ /* cyrus_sasl_user_exists: determines whether a user exists according to Cyrus SASL:
+
+ ${if cyrus_sasl_user_exists {{username}{service}{realm}} {yes}{no}}
+
+ However, the last two are optional. That is why the whole set is enclosed
+ in their own set of braces. */
+
+ case ECOND_CYRUS_SASL_USER_EXISTS:
+ #ifndef AUTH_CYRUS_SASL
+ goto COND_FAILED_NOT_COMPILED;
+ #else
+ while (isspace(*s)) s++;
+ if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
+ switch(read_subs(sub, 3, 1, &s, yield == NULL, TRUE, US"cyrus_sasl_user_exists", resetok))
+ {
+ case 1: expand_string_message = US"too few arguments or bracketing "
+ "error for cyrus_sasl_user_exists";
+ case 2:
+ case 3: return NULL;
+ }
+ if (sub[1] == NULL)
+ {
+ /* realm if no service */
+ sub[1] = sub[2];
+ sub[2] = NULL;
+ }
+ if (yield != NULL)
+ {
+ int rc;
+ rc = auth_cyrus_sasl_user_exists(sub[0], sub[1], sub[2]);
+ if (rc == ERROR || rc == DEFER)
+ {
+ expand_string_message = US"failed to perform cyrus_sasl_user_exists test";
+ return NULL;
+ }
+ *yield = (rc == OK) == testfor;
+ }
+ return s;
+ #endif /* AUTH_CYRUS_SASL */
/* saslauthd: does Cyrus saslauthd authentication. Four parameters are used: