Refactor authenticators API to take an (opaque) smtp connection context
[exim.git] / src / src / auths / gsasl_exim.c
index e88bd257824822077a68a04c178b725f37dece7b..0e3a9161947969a00c791f565fff0cc8447aa118 100644 (file)
@@ -2,10 +2,12 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
-/* Copyright (c) Twitter Inc 2012 */
+/* Copyright (c) Twitter Inc 2012
+   Author: Phil Pennock <pdp@exim.org> */
+/* Copyright (c) Phil Pennock 2012 */
 
 /* Interface to GNU SASL library for generic authentication. */
 
@@ -27,7 +29,9 @@ sense in all contexts.  For some, we can do checks at init time.
 
 #ifndef AUTH_GSASL
 /* dummy function to satisfy compilers when we link in an "empty" file. */
-static void dummy(int x) { dummy(x-1); }
+static void dummy(int x);
+static void dummy2(int x) { dummy(x-1); }
+static void dummy(int x) { dummy2(x-1); }
 #else
 
 #include <gsasl.h>
@@ -74,6 +78,20 @@ auth_gsasl_options_block auth_gsasl_option_defaults = {
   FALSE                     /* server_channelbinding */
 };
 
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_gsasl_init(auth_instance *ablock) {}
+int auth_gsasl_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_gsasl_client(auth_instance *ablock, void * sx,
+  int timeout, uschar *buffer, int buffsize) {return 0;}
+void auth_gsasl_version_report(FILE *f) {}
+
+#else   /*!MACRO_PREDEF*/
+
+
+
 /* "Globals" for managing the gsasl interface. */
 
 static Gsasl *gsasl_ctx = NULL;
@@ -139,21 +157,29 @@ auth_gsasl_init(auth_instance *ablock)
               ablock->name,  gsasl_strerror_name(rc), gsasl_strerror(rc));
   HDEBUG(D_auth) debug_printf("GNU SASL supports: %s\n", p);
 
-  supported = gsasl_client_support_p(gsasl_ctx, (const char *)ob->server_mech);
+  supported = gsasl_client_support_p(gsasl_ctx, CCS ob->server_mech);
   if (!supported)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
               "GNU SASL does not support mechanism \"%s\"",
               ablock->name, ob->server_mech);
 
   if ((ablock->server_condition == NULL) &&
-      (strcmpic(ob->server_mech, US"EXTERNAL") ||
-       strcmpic(ob->server_mech, US"ANONYMOUS") ||
-       strcmpic(ob->server_mech, US"PLAIN") ||
-       strcmpic(ob->server_mech, US"LOGIN")))
+      (streqic(ob->server_mech, US"EXTERNAL") ||
+       streqic(ob->server_mech, US"ANONYMOUS") ||
+       streqic(ob->server_mech, US"PLAIN") ||
+       streqic(ob->server_mech, US"LOGIN")))
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
               "Need server_condition for %s mechanism",
               ablock->name, ob->server_mech);
 
+  /* This does *not* scale to new SASL mechanisms.  Need a better way to ask
+  which properties will be needed. */
+  if ((ob->server_realm == NULL) &&
+      streqic(ob->server_mech, US"DIGEST-MD5"))
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
+              "Need server_realm for %s mechanism",
+              ablock->name, ob->server_mech);
+
   /* At present, for mechanisms we don't panic on absence of server_condition;
   need to figure out the most generically correct approach to deciding when
   it's critical and when it isn't.  Eg, for simple validation (PLAIN mechanism,
@@ -176,8 +202,9 @@ main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop)
   struct callback_exim_state *cb_state =
     (struct callback_exim_state *)gsasl_session_hook_get(sctx);
 
-  HDEBUG(D_auth) debug_printf("Callback entered, prop=%d (loop prop=%d)\n",
-      prop, callback_loop);
+  HDEBUG(D_auth)
+    debug_printf("GNU SASL Callback entered, prop=%d (loop prop=%d)\n",
+        prop, callback_loop);
 
   if (cb_state == NULL) {
     HDEBUG(D_auth) debug_printf("  not from our server/client processing.\n");
@@ -231,7 +258,7 @@ auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
     debug_printf("GNU SASL: initialising session for %s, mechanism %s.\n",
         ablock->name, ob->server_mech);
 
-  rc = gsasl_server_start(gsasl_ctx, (const char *)ob->server_mech, &sctx);
+  rc = gsasl_server_start(gsasl_ctx, CCS ob->server_mech, &sctx);
   if (rc != GSASL_OK) {
     auth_defer_msg = string_sprintf("GNU SASL: session start failure: %s (%s)",
         gsasl_strerror_name(rc), gsasl_strerror(rc));
@@ -262,7 +289,7 @@ auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
     /* Some auth mechanisms can ensure that both sides are talking withing the
     same security context; for TLS, this means that even if a bad certificate
     has been accepted, they remain MitM-proof because both sides must be within
-    the same negotiated session; if someone is terminating one sesson and
+    the same negotiated session; if someone is terminating one session and
     proxying data on within a second, authentication will fail.
 
     We might not have this available, depending upon TLS implementation,
@@ -281,7 +308,7 @@ auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
       HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
           ablock->name);
       gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE,
-          (const char *) tls_channelbinding_b64);
+          CCS  tls_channelbinding_b64);
     } else {
       HDEBUG(D_auth)
         debug_printf("Auth %s: Not enabling channel-binding (data available)\n",
@@ -305,7 +332,9 @@ auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
 
     switch (rc) {
       case GSASL_OK:
-        goto STOP_INTERACTION;
+        if (!to_send)
+          goto STOP_INTERACTION;
+        break;
 
       case GSASL_NEEDS_MORE:
         break;
@@ -337,8 +366,16 @@ auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
         goto STOP_INTERACTION;
     }
 
-    exim_error =
-      auth_get_no64_data((uschar **)&received, (uschar *)to_send);
+    if ((rc == GSASL_NEEDS_MORE) ||
+        (to_send && *to_send))
+      exim_error =
+        auth_get_no64_data((uschar **)&received, US to_send);
+
+    if (to_send) {
+      free(to_send);
+      to_send = NULL;
+    }
+
     if (exim_error)
       break; /* handles * cancelled check */
 
@@ -412,11 +449,11 @@ server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_insta
   switch (prop) {
     case GSASL_VALIDATE_SIMPLE:
       /* GSASL_AUTHID, GSASL_AUTHZID, and GSASL_PASSWORD */
-      propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHID);
+      propval = US  gsasl_property_fast(sctx, GSASL_AUTHID);
       auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
-      propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
+      propval = US  gsasl_property_fast(sctx, GSASL_AUTHZID);
       auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
-      propval = (uschar *) gsasl_property_get(sctx, GSASL_PASSWORD);
+      propval = US  gsasl_property_fast(sctx, GSASL_PASSWORD);
       auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
       expand_nmax = 3;
       for (i = 1; i <= 3; ++i)
@@ -432,7 +469,7 @@ server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_insta
         cbrc = GSASL_AUTHENTICATION_ERROR;
         break;
       }
-      propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
+      propval = US  gsasl_property_fast(sctx, GSASL_AUTHZID);
       /* We always set $auth1, even if only to empty string. */
       auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
       expand_nlength[1] = Ustrlen(expand_nstring[1]);
@@ -449,7 +486,7 @@ server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_insta
         cbrc = GSASL_AUTHENTICATION_ERROR;
         break;
       }
-      propval = (uschar *) gsasl_property_get(sctx, GSASL_ANONYMOUS_TOKEN);
+      propval = US  gsasl_property_fast(sctx, GSASL_ANONYMOUS_TOKEN);
       /* We always set $auth1, even if only to empty string. */
       auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
       expand_nlength[1] = Ustrlen(expand_nstring[1]);
@@ -461,10 +498,18 @@ server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_insta
       break;
 
     case GSASL_VALIDATE_GSSAPI:
-      /* GSASL_AUTHZID and GSASL_GSSAPI_DISPLAY_NAME */
-      propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
+      /* GSASL_AUTHZID and GSASL_GSSAPI_DISPLAY_NAME
+      The display-name is authenticated as part of GSS, the authzid is claimed
+      by the SASL integration after authentication; protected against tampering
+      (if the SASL mechanism supports that, which Kerberos does) but is
+      unverified, same as normal for other mechanisms.
+
+      First coding, we had these values swapped, but for consistency and prior
+      to the first release of Exim with this authenticator, they've been
+      switched to match the ordering of GSASL_VALIDATE_SIMPLE. */
+      propval = US  gsasl_property_fast(sctx, GSASL_GSSAPI_DISPLAY_NAME);
       auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
-      propval = (uschar *) gsasl_property_get(sctx, GSASL_GSSAPI_DISPLAY_NAME);
+      propval = US  gsasl_property_fast(sctx, GSASL_AUTHZID);
       auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
       expand_nmax = 2;
       for (i = 1; i <= 2; ++i)
@@ -491,16 +536,17 @@ server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_insta
         tmps = CS expand_string(ob->server_scram_salt);
         gsasl_property_set(sctx, GSASL_SCRAM_SALT, tmps);
       }
-      /* Asking for GSASL_AUTHZID will probably call back into us.
+      /* Asking for GSASL_AUTHZID calls back into us if we use
+      gsasl_property_get(), thus the use of gsasl_property_fast().
       Do we really want to hardcode limits per mechanism?  What happens when
       a new mechanism is added to the library.  It *shouldn't* result in us
       needing to add more glue, since avoiding that is a large part of the
       point of SASL. */
-      propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHID);
+      propval = US  gsasl_property_fast(sctx, GSASL_AUTHID);
       auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
-      propval = (uschar *) gsasl_property_get(sctx, GSASL_AUTHZID);
+      propval = US  gsasl_property_fast(sctx, GSASL_AUTHZID);
       auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
-      propval = (uschar *) gsasl_property_get(sctx, GSASL_REALM);
+      propval = US  gsasl_property_fast(sctx, GSASL_REALM);
       auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
       expand_nmax = 3;
       for (i = 1; i <= 3; ++i)
@@ -508,7 +554,7 @@ server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_insta
 
       tmps = CS expand_string(ob->server_password);
       if (tmps == NULL) {
-        sasl_error_should_defer = expand_string_forcedfail ? FALSE : TRUE;
+        sasl_error_should_defer = f.expand_string_forcedfail ? FALSE : TRUE;
         HDEBUG(D_auth) debug_printf("server_password expansion failed, so "
             "can't tell GNU SASL library the password for %s\n", auth_vars[0]);
         return GSASL_AUTHENTICATION_ERROR;
@@ -542,8 +588,7 @@ server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_insta
 int
 auth_gsasl_client(
   auth_instance *ablock,                 /* authenticator block */
-  smtp_inblock *inblock,                 /* connection inblock */
-  smtp_outblock *outblock,               /* connection outblock */
+  void * sx,                            /* connection */
   int timeout,                           /* command timeout */
   uschar *buffer,                        /* buffer for reading response */
   int buffsize)                          /* size of buffer */
@@ -582,6 +627,7 @@ auth_gsasl_version_report(FILE *f)
           GSASL_VERSION, runtime);
 }
 
+#endif   /*!MACRO_PREDEF*/
 #endif  /* AUTH_GSASL */
 
 /* End of gsasl_exim.c */