[exim-dev] Exim's cyrus_sasl authenticator: enhancement requ…

Top Page
Delete this message
Reply to this message
Author: Dennis Davis
Date:  
To: exim-dev
Subject: [exim-dev] Exim's cyrus_sasl authenticator: enhancement request.
I've recently been looking at email authentication as described
in RFC 24554 and the exim manual. I'm using the plaintext
authenticator on my exim servers.

One problem I've come across is that authorisation isn't
mentioned anywhere. And it's often the next step to take after
authentication. Much in the same way your passport may "prove"
who you are (authentication) but you won't be allowed into some
countries without a valid visa (authorisation).

The server_condition expansion variable in the plaintext
authenticator is sufficiently versatile for me to combine
authentication and authorisation. In my case I'm using KerberosV
so I can check username & password authentication using the Cyrus
saslauthd and then see if the username is authorised to use the
server by a simple database lookup. I'm using a server_condition
expansion of the form:

  server_condition = ${if and \
    { \
      {saslauthd{{$auth1}{$auth2}}} \
      {eq {${lookup{$auth1}SEARCH{KRB5_PRINCIPALS}{yes}fail}} {yes}} \
    }}


As I'm using KerberosV, the cyrus_sasl authenticator with the
gssapi mechanism looks attractive. However this authenticator
doesn't include any authorisation hooks. The only way I can see to
implement authorisation is to expire KerberosV principals. And that
could have unwanted global implications.

I'd like to propose that a server_condition expansion variable is
added to the cyrus_sasl authenticator. By default it would be
unset. If set it would be expanded and authentication succeed if
the expansion is successful. This would happen after the relevant
server_mech had done its work. So it would be an "and" condition
along the lines of:

If {server_mech reports success} and {server_condition expansion succeeds}

Documentation for the manual (largely stolen from existing
documentation) could be along the lines of:

server_condition is expanded if it is set. If the expansion is
forced to fail, authentication fails. Any other expansion failure
causes a temporary error code to be returned. If the result of a
successful expansion is an empty string, "0", "no", or "false",
authentication fails. If the result of the expansion is "1",
"yes", or "true", authentication succeeds.

One possible use for server_condition is an authorisation "hook"
to ensure authenticated users are entitled to use this facility.

Code to implement the above as patches against exim-4.63 are
attached. I've stolen almost all of this code from the plaintext
authenticator. I've tested this with:

client: pine-4.64 built against openssl-0.9.8d and MIT kerberosV,
        version 1.3.6, on a Solaris 5.5.1 machine.


server: exim-4.63 built against openssl-0.9.8d, cyrus-sasl-2.1.22
        and MIT kerberosV, version 1.5.1, on a Solaris 5.8 machine.


using a GSSAPI authenticator of the form:

# A GSSAPI authenticator. Very few clients will be able to use
# this. Just Unix clients such as pine and mutt spring to mind.
cyrus_sasl_authenticator:
driver = cyrus_sasl
public_name = GSSAPI
server_mech = gssapi
# KerberosV GSSAPI, usernames/passwords not sent in cleartext. So
# we don't need to be in a TLS-encrypted session.
server_advertise_condition = yes
server_condition = ${if eq {${lookup{$auth1}SEARCH{KRB5_PRINCIPALS}{yes}fail}}
{yes}}
server_set_id = $auth1

Everything seems to work, or fail, as expected. The client
automatically obtains a KerberosV ticket for the server's smtp
service. The exim logs on the server indicate the use of
cyrus_sasl_authenticator.

I suspect it would be simple, and possibly useful to some, to add
a server_condition expansion variable to exim's cram_md5 and spa
authenticators.  Especially if the above "code-theft" method is
used!  However I'm not currently able to test these authenticators.
I'm not sure that code submissions on the basis of "well, it
compiles..." are all that useful.
-- 
Dennis Davis, BUCS, University of Bath, Bath, BA2 7AY, UK
D.H.Davis@???               Phone: +44 1225 386101
--- exim-4.63/src/auths/cyrus_sasl.h.orig    Mon Jul 31 15:19:48 2006
+++ exim-4.63/src/auths/cyrus_sasl.h    Thu Oct  5 10:47:46 2006
@@ -12,6 +12,7 @@
 /* Private structure for the private options. */
 
 typedef struct {
+  uschar *server_condition;
   uschar *server_service;
   uschar *server_hostname;
   uschar *server_realm;
--- exim-4.63/src/auths/cyrus_sasl.c.orig    Mon Jul 31 15:19:48 2006
+++ exim-4.63/src/auths/cyrus_sasl.c    Wed Oct 11 14:30:29 2006
@@ -37,6 +37,8 @@
 /* Options specific to the cyrus_sasl authentication mechanism. */
 
 optionlist auth_cyrus_sasl_options[] = {
+  { "server_condition",   opt_stringptr,
+      (void *)(offsetof(auth_cyrus_sasl_options_block, server_condition)) },
   { "server_hostname",      opt_stringptr,
       (void *)(offsetof(auth_cyrus_sasl_options_block, server_hostname)) },
   { "server_mech",          opt_stringptr,
@@ -53,9 +55,10 @@
 int auth_cyrus_sasl_options_count =
   sizeof(auth_cyrus_sasl_options)/sizeof(optionlist);
 
-/* Default private options block for the contidion authentication method. */
+/* Default private options block for the cyrus_sasl authentication method. */
 
 auth_cyrus_sasl_options_block auth_cyrus_sasl_option_defaults = {
+  NULL,             /* server_condition */
   US"smtp",         /* server_service */
   US"$primary_hostname", /* server_hostname */
   NULL,             /* server_realm */
@@ -182,7 +185,7 @@
 {
 auth_cyrus_sasl_options_block *ob =
   (auth_cyrus_sasl_options_block *)(ablock->options_block);
-uschar *output, *out2, *input, *clear, *hname;
+uschar *output, *out2, *input, *clear, *hname, *cond;
 uschar *debug = NULL;   /* Stops compiler complaining */
 sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
 sasl_conn_t *conn;
@@ -332,10 +335,67 @@
     expand_nmax = 1;
 
     HDEBUG(D_auth)
-      debug_printf("Cyrus SASL %s authentiction succeeded for %s\n", ob->server_mech, out2);
+      debug_printf("Cyrus SASL %s authentication succeeded for %s\n", ob->server_mech, out2);
     /* close down the connection, freeing up library's memory */
     sasl_dispose(&conn);
     sasl_done();
+
+    /* Finally check server_condition if it's set. */
+    
+    if (ob->server_condition != NULL)
+      {
+      cond = expand_string(ob->server_condition);
+      
+      HDEBUG(D_auth)
+        {
+        int i;
+        debug_printf("%s authenticator:\n", ablock->name);
+        for (i = 0; i < AUTH_VARS; i++)
+          {
+          if (auth_vars[i] != NULL)
+            debug_printf("  $auth%d = %s\n", i + 1, auth_vars[i]);
+          }
+        for (i = 1; i <= expand_nmax; i++)
+          debug_printf("  $%d = %.*s\n", i, expand_nlength[i], expand_nstring[i]);
+        debug_print_string(ablock->server_debug_string);    /* customized debug */
+        if (cond == NULL)
+          debug_printf("expansion failed: %s\n", expand_string_message);
+        else
+          debug_printf("expanded string: %s\n", cond);
+        }
+
+      /* A forced expansion failure causes authentication to fail. Other expansion
+      failures yield DEFER, which will cause a temporary error code to be returned to
+      the AUTH command. The problem is at the server end, so the client should try
+      again later. */
+
+      if (cond == NULL)
+        {
+        if (expand_string_forcedfail) return FAIL;
+        auth_defer_msg = expand_string_message;
+        return DEFER;
+        }
+
+      /* Return FAIL for empty string, "0", "no", and "false"; return OK for
+      "1", "yes", and "true"; return DEFER for anything else, with the string
+      available as an error text for the user. */
+
+      if (*cond == 0 ||
+          Ustrcmp(cond, "0") == 0 ||
+          strcmpic(cond, US"no") == 0 ||
+          strcmpic(cond, US"false") == 0)
+        return FAIL;
+
+      if (Ustrcmp(cond, "1") == 0 ||
+          strcmpic(cond, US"yes") == 0 ||
+          strcmpic(cond, US"true") == 0)
+        return OK;
+
+      auth_defer_msg = cond;
+      auth_defer_user_msg = string_sprintf(": %s", cond);
+      return DEFER;
+      }
+
     return OK;
     }
   }