[exim-dev] PATCH: sasl_user_exists condition

Top Page
Delete this message
Reply to this message
Author: Bob Richmond
Date:  
To: exim-dev
Subject: [exim-dev] PATCH: sasl_user_exists condition
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: