OpenSSL: use nondeprecated EC-group functions under 3.0.0.
[exim.git] / src / src / auths / gsasl_exim.c
index 1c4af92fcd51b28f46c7942ff47b1d53112dc32a..26505446a06c6a8c3d0250798246b2970c8556ed 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2019 */
+/* Copyright (c) The Exim Maintainers 2019 - 2021 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
@@ -27,7 +27,6 @@ sense in all contexts.  For some, we can do checks at init time.
 */
 
 #include "../exim.h"
-#define CHANNELBIND_HACK
 
 #ifndef AUTH_GSASL
 /* dummy function to satisfy compilers when we link in an "empty" file. */
@@ -40,56 +39,57 @@ static void dummy(int x) { dummy2(x-1); }
 #include "gsasl_exim.h"
 
 
-#if GSASL_VERSION_MINOR >= 9
+#if GSASL_VERSION_MINOR >= 10
+# define EXIM_GSASL_HAVE_SCRAM_SHA_256
+# define EXIM_GSASL_SCRAM_S_KEY
+
+#elif GSASL_VERSION_MINOR == 9
 # define EXIM_GSASL_HAVE_SCRAM_SHA_256
 
 # if GSASL_VERSION_PATCH >= 1
 #  define EXIM_GSASL_SCRAM_S_KEY
 # endif
+# if GSASL_VERSION_PATCH < 2
+#  define CHANNELBIND_HACK
+# endif
+
+#else
+# define CHANNELBIND_HACK
 #endif
 
+/* Convenience for testing strings */
+
+#define STREQIC(Foo, Bar) (strcmpic((Foo), (Bar)) == 0)
+
 
 /* Authenticator-specific options. */
 /* I did have server_*_condition options for various mechanisms, but since
 we only ever handle one mechanism at a time, I didn't see the point in keeping
 that.  In case someone sees a point, I've left the condition_check() API
 alone. */
+#define LOFF(field) OPT_OFF(auth_gsasl_options_block, field)
+
 optionlist auth_gsasl_options[] = {
-  { "client_authz",            opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, client_authz)) },
-  { "client_channelbinding",   opt_bool,
-      (void *)(offsetof(auth_gsasl_options_block, client_channelbinding)) },
-  { "client_password",         opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, client_password)) },
-  { "client_spassword",                opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, client_spassword)) },
-  { "client_username",         opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, client_username)) },
-
-  { "server_channelbinding",   opt_bool,
-      (void *)(offsetof(auth_gsasl_options_block, server_channelbinding)) },
-  { "server_hostname",         opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_hostname)) },
+  { "client_authz",            opt_stringptr,  LOFF(client_authz) },
+  { "client_channelbinding",   opt_bool,       LOFF(client_channelbinding) },
+  { "client_password",         opt_stringptr,  LOFF(client_password) },
+  { "client_spassword",                opt_stringptr,  LOFF(client_spassword) },
+  { "client_username",         opt_stringptr,  LOFF(client_username) },
+
+  { "server_channelbinding",   opt_bool,       LOFF(server_channelbinding) },
+  { "server_hostname",         opt_stringptr,  LOFF(server_hostname) },
 #ifdef EXIM_GSASL_SCRAM_S_KEY
-  { "server_key",              opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_key)) },
+  { "server_key",              opt_stringptr,  LOFF(server_key) },
 #endif
-  { "server_mech",             opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_mech)) },
-  { "server_password",         opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_password)) },
-  { "server_realm",            opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_realm)) },
-  { "server_scram_iter",       opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_scram_iter)) },
-  { "server_scram_salt",       opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_scram_salt)) },
+  { "server_mech",             opt_stringptr,  LOFF(server_mech) },
+  { "server_password",         opt_stringptr,  LOFF(server_password) },
+  { "server_realm",            opt_stringptr,  LOFF(server_realm) },
+  { "server_scram_iter",       opt_stringptr,  LOFF(server_scram_iter) },
+  { "server_scram_salt",       opt_stringptr,  LOFF(server_scram_salt) },
 #ifdef EXIM_GSASL_SCRAM_S_KEY
-  { "server_skey",             opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_s_key)) },
+  { "server_skey",             opt_stringptr,  LOFF(server_s_key) },
 #endif
-  { "server_service",          opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_service)) }
+  { "server_service",          opt_stringptr,  LOFF(server_service) }
 };
 
 int auth_gsasl_options_count =
@@ -204,15 +204,21 @@ if (!gsasl_client_support_p(gsasl_ctx, CCS ob->server_mech))
            "GNU SASL does not support mechanism \"%s\"",
            ablock->name, ob->server_mech);
 
-ablock->server = TRUE;
-
-if (  !ablock->server_condition
-   && (  streqic(ob->server_mech, US"EXTERNAL")
-      || streqic(ob->server_mech, US"ANONYMOUS")
-      || streqic(ob->server_mech, US"PLAIN")
-      || streqic(ob->server_mech, US"LOGIN")
-   )  )
+if (ablock->server_condition)
+  ablock->server = TRUE;
+else if(  ob->server_mech
+       && !STREQIC(ob->server_mech, US"EXTERNAL")
+       && !STREQIC(ob->server_mech, US"ANONYMOUS")
+       && !STREQIC(ob->server_mech, US"PLAIN")
+       && !STREQIC(ob->server_mech, US"LOGIN")
+       )
   {
+  /* 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,
+  etc) it clearly is critical.
+  */
+
   ablock->server = FALSE;
   HDEBUG(D_auth) debug_printf("%s authenticator:  "
            "Need server_condition for %s mechanism\n",
@@ -223,7 +229,7 @@ if (  !ablock->server_condition
 which properties will be needed. */
 
 if (  !ob->server_realm
-   && streqic(ob->server_mech, US"DIGEST-MD5"))
+   && STREQIC(ob->server_mech, US"DIGEST-MD5"))
   {
   ablock->server = FALSE;
   HDEBUG(D_auth) debug_printf("%s authenticator:  "
@@ -231,12 +237,6 @@ if (  !ob->server_realm
            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,
-etc) it clearly is critical.
-*/
-
 ablock->client = ob->client_username && ob->client_password;
 }
 
@@ -312,44 +312,41 @@ gsasl_prop_code_to_name(Gsasl_property prop)
 {
 switch (prop)
   {
-  case GSASL_AUTHID: return US"AUTHID";
-  case GSASL_AUTHZID: return US"AUTHZID";
-  case GSASL_PASSWORD: return US"PASSWORD";
-  case GSASL_ANONYMOUS_TOKEN: return US"ANONYMOUS_TOKEN";
-  case GSASL_SERVICE: return US"SERVICE";
-  case GSASL_HOSTNAME: return US"HOSTNAME";
-  case GSASL_GSSAPI_DISPLAY_NAME: return US"GSSAPI_DISPLAY_NAME";
-  case GSASL_PASSCODE: return US"PASSCODE";
-  case GSASL_SUGGESTED_PIN: return US"SUGGESTED_PIN";
-  case GSASL_PIN: return US"PIN";
-  case GSASL_REALM: return US"REALM";
-  case GSASL_DIGEST_MD5_HASHED_PASSWORD: return US"DIGEST_MD5_HASHED_PASSWORD";
-  case GSASL_QOPS: return US"QOPS";
-  case GSASL_QOP: return US"QOP";
-  case GSASL_SCRAM_ITER: return US"SCRAM_ITER";
-  case GSASL_SCRAM_SALT: return US"SCRAM_SALT";
-  case GSASL_SCRAM_SALTED_PASSWORD: return US"SCRAM_SALTED_PASSWORD";
-#ifdef EXIM_GSASL_SCRAM_S_KEY
-  case GSASL_SCRAM_STOREDKEY: return US"SCRAM_STOREDKEY";
-  case GSASL_SCRAM_SERVERKEY: return US"SCRAM_SERVERKEY";
-#endif
-  case GSASL_CB_TLS_UNIQUE: return US"CB_TLS_UNIQUE";
-  case GSASL_SAML20_IDP_IDENTIFIER: return US"SAML20_IDP_IDENTIFIER";
-  case GSASL_SAML20_REDIRECT_URL: return US"SAML20_REDIRECT_URL";
-  case GSASL_OPENID20_REDIRECT_URL: return US"OPENID20_REDIRECT_URL";
-  case GSASL_OPENID20_OUTCOME_DATA: return US"OPENID20_OUTCOME_DATA";
-  case GSASL_SAML20_AUTHENTICATE_IN_BROWSER: return US"SAML20_AUTHENTICATE_IN_BROWSER";
-  case GSASL_OPENID20_AUTHENTICATE_IN_BROWSER: return US"OPENID20_AUTHENTICATE_IN_BROWSER";
+  case GSASL_AUTHID:                   return US"AUTHID";
+  case GSASL_AUTHZID:                  return US"AUTHZID";
+  case GSASL_PASSWORD:                 return US"PASSWORD";
+  case GSASL_ANONYMOUS_TOKEN:          return US"ANONYMOUS_TOKEN";
+  case GSASL_SERVICE:                  return US"SERVICE";
+  case GSASL_HOSTNAME:                 return US"HOSTNAME";
+  case GSASL_GSSAPI_DISPLAY_NAME:      return US"GSSAPI_DISPLAY_NAME";
+  case GSASL_PASSCODE:                 return US"PASSCODE";
+  case GSASL_SUGGESTED_PIN:            return US"SUGGESTED_PIN";
+  case GSASL_PIN:                      return US"PIN";
+  case GSASL_REALM:                    return US"REALM";
+  case GSASL_DIGEST_MD5_HASHED_PASSWORD:       return US"DIGEST_MD5_HASHED_PASSWORD";
+  case GSASL_QOPS:                     return US"QOPS";
+  case GSASL_QOP:                      return US"QOP";
+  case GSASL_SCRAM_ITER:               return US"SCRAM_ITER";
+  case GSASL_SCRAM_SALT:               return US"SCRAM_SALT";
+  case GSASL_SCRAM_SALTED_PASSWORD:    return US"SCRAM_SALTED_PASSWORD";
 #ifdef EXIM_GSASL_SCRAM_S_KEY
-  case GSASL_SCRAM_CLIENTKEY: return US"SCRAM_CLIENTKEY";
+  case GSASL_SCRAM_STOREDKEY:          return US"SCRAM_STOREDKEY";
+  case GSASL_SCRAM_SERVERKEY:          return US"SCRAM_SERVERKEY";
 #endif
-  case GSASL_VALIDATE_SIMPLE: return US"VALIDATE_SIMPLE";
-  case GSASL_VALIDATE_EXTERNAL: return US"VALIDATE_EXTERNAL";
-  case GSASL_VALIDATE_ANONYMOUS: return US"VALIDATE_ANONYMOUS";
-  case GSASL_VALIDATE_GSSAPI: return US"VALIDATE_GSSAPI";
-  case GSASL_VALIDATE_SECURID: return US"VALIDATE_SECURID";
-  case GSASL_VALIDATE_SAML20: return US"VALIDATE_SAML20";
-  case GSASL_VALIDATE_OPENID20: return US"VALIDATE_OPENID20";
+  case GSASL_CB_TLS_UNIQUE:            return US"CB_TLS_UNIQUE";
+  case GSASL_SAML20_IDP_IDENTIFIER:    return US"SAML20_IDP_IDENTIFIER";
+  case GSASL_SAML20_REDIRECT_URL:      return US"SAML20_REDIRECT_URL";
+  case GSASL_OPENID20_REDIRECT_URL:    return US"OPENID20_REDIRECT_URL";
+  case GSASL_OPENID20_OUTCOME_DATA:    return US"OPENID20_OUTCOME_DATA";
+  case GSASL_SAML20_AUTHENTICATE_IN_BROWSER:   return US"SAML20_AUTHENTICATE_IN_BROWSER";
+  case GSASL_OPENID20_AUTHENTICATE_IN_BROWSER: return US"OPENID20_AUTHENTICATE_IN_BROWSER";
+  case GSASL_VALIDATE_SIMPLE:          return US"VALIDATE_SIMPLE";
+  case GSASL_VALIDATE_EXTERNAL:                return US"VALIDATE_EXTERNAL";
+  case GSASL_VALIDATE_ANONYMOUS:       return US"VALIDATE_ANONYMOUS";
+  case GSASL_VALIDATE_GSSAPI:          return US"VALIDATE_GSSAPI";
+  case GSASL_VALIDATE_SECURID:         return US"VALIDATE_SECURID";
+  case GSASL_VALIDATE_SAML20:          return US"VALIDATE_SAML20";
+  case GSASL_VALIDATE_OPENID20:                return US"VALIDATE_OPENID20";
   }
 return CUS string_sprintf("(unknown prop: %d)", (int)prop);
 }
@@ -378,7 +375,7 @@ HDEBUG(D_auth)
 #ifndef DISABLE_TLS
 if (tls_in.channelbinding && ob->server_channelbinding)
   {
-# ifdef EXPERIMENTAL_TLS_RESUME
+# ifndef DISABLE_TLS_RESUME
   if (!tls_in.ext_master_secret && tls_in.resumption == RESUME_USED)
     {          /* per RFC 7677 section 4 */
     HDEBUG(D_auth) debug_printf(
@@ -387,9 +384,9 @@ if (tls_in.channelbinding && ob->server_channelbinding)
     }
 # endif
 # ifdef CHANNELBIND_HACK
-/* This is a gross hack to get around the library a) requiring that
-c-b was already set, at the _start() call, and b) caching a b64'd
-version of the binding then which it never updates. */
+/* This is a gross hack to get around the library before 1.9.2
+a) requiring that c-b was already set, at the _start() call, and
+b) caching a b64'd version of the binding then which it never updates. */
 
   gsasl_callback_hook_set(gsasl_ctx, tls_in.channelbinding);
 # endif
@@ -442,6 +439,12 @@ if (tls_in.channelbinding)
   would then result in mechanism name changes on a library update, we
   have little choice but to default it off and let the admin choose to
   enable it.  *sigh*
+
+  Earlier library versions need this set early, during the _start() call,
+  so we had to misuse gsasl_callback_hook_set/get() as a data transfer
+  mech for the callback done at that time to get the bind-data.  More recently
+  the callback is done (if needed) during the first gsasl_stop().  We know
+  the bind-data here so can set it (and should not get a callback).
   */
   if (ob->server_channelbinding)
     {
@@ -583,14 +586,19 @@ return GSASL_AUTHENTICATION_ERROR;
 }
 
 
+/* Set the "next" $auth[n] and increment expand_nmax */
+
 static void
 set_exim_authvar_from_prop(Gsasl_session * sctx, Gsasl_property prop)
 {
 uschar * propval = US gsasl_property_fast(sctx, prop);
 int i = expand_nmax, j = i + 1;
 propval = propval ? string_copy(propval) : US"";
-auth_vars[i] = expand_nstring[j] = propval;
+HDEBUG(D_auth) debug_printf("auth[%d] <=  %s'%s'\n",
+                           j, gsasl_prop_code_to_name(prop), propval);
+expand_nstring[j] = propval;
 expand_nlength[j] = Ustrlen(propval);
+if (i < AUTH_VARS) auth_vars[i] = propval;
 expand_nmax = j;
 }
 
@@ -634,10 +642,10 @@ static int
 server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop,
   auth_instance *ablock)
 {
-char *tmps;
-uschar *s, *propval;
+char * tmps;
+uschar * s;
 int cbrc = GSASL_NO_CALLBACK;
-auth_gsasl_options_block *ob =
+auth_gsasl_options_block * ob =
   (auth_gsasl_options_block *)(ablock->options_block);
 
 HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as server\n",
@@ -753,7 +761,7 @@ switch (prop)
     for memory wiping, so expanding strings will leave stuff laying around.
     But no need to compound the problem, so get rid of the one we can. */
 
-    memset(tmps, '\0', strlen(tmps));
+    if (US tmps != s) memset(tmps, '\0', strlen(tmps));
     cbrc = GSASL_OK;
     break;
 
@@ -778,7 +786,6 @@ set_client_prop(Gsasl_session * sctx, Gsasl_property prop, uschar * val,
   unsigned flags, uschar * buffer, int buffsize)
 {
 uschar * s;
-int rc;
 
 if (!val) return !!(flags & PROP_OPTIONAL);
 if (!(s = expand_string(val)) || !(flags & PROP_OPTIONAL) && !*s)
@@ -827,18 +834,19 @@ HDEBUG(D_auth)
 #ifndef DISABLE_TLS
 if (tls_out.channelbinding && ob->client_channelbinding)
   {
-# ifdef EXPERIMENTAL_TLS_RESUME
+# ifndef DISABLE_TLS_RESUME
   if (!tls_out.ext_master_secret && tls_out.resumption == RESUME_USED)
-    {          /* per RFC 7677 section 4 */
+    {  /* Per RFC 7677 section 4.  See also RFC 7627, "Triple Handshake"
+       vulnerability, and https://www.mitls.org/pages/attacks/3SHAKE */
     string_format(buffer, buffsize, "%s",
       "channel binding not usable on resumed TLS without extended-master-secret");
     return FAIL;
     }
 # endif
 # ifdef CHANNELBIND_HACK
-  /* This is a gross hack to get around the library a) requiring that
-  c-b was already set, at the _start() call, and b) caching a b64'd
-  version of the binding then which it never updates. */
+  /* This is a gross hack to get around the library before 1.9.2
+  a) requiring that c-b was already set, at the _start() call, and
+  b) caching a b64'd version of the binding then which it never updates. */
 
   gsasl_callback_hook_set(gsasl_ctx, tls_out.channelbinding);
 # endif
@@ -859,10 +867,7 @@ gsasl_session_hook_set(sctx, &cb_state);
 
 /* Set properties */
 
-if (  !set_client_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD, ob->client_spassword,
-                 0, buffer, buffsize)
-      &&
-      !set_client_prop(sctx, GSASL_PASSWORD, ob->client_password,
+if (  !set_client_prop(sctx, GSASL_PASSWORD, ob->client_password,
                  0, buffer, buffsize)
    || !set_client_prop(sctx, GSASL_AUTHID, ob->client_username,
                  0, buffer, buffsize)
@@ -940,10 +945,13 @@ for(s = NULL; ;)
   }
 
 done:
-HDEBUG(D_auth)
+if (yield == OK)
   {
-  const uschar * s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SALTED_PASSWORD);
-  if (s) debug_printf(" - SaltedPassword: '%s'\n", s);
+  expand_nmax = 0;
+  set_exim_authvar_from_prop(sctx, GSASL_AUTHID);
+  set_exim_authvar_from_prop(sctx, GSASL_SCRAM_ITER);
+  set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALT);
+  set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD);
   }
 
 gsasl_finish(sctx);
@@ -957,11 +965,31 @@ HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as client\n",
            gsasl_prop_code_to_name(prop), ablock->name, ablock->public_name);
 switch (prop)
   {
-  case GSASL_CB_TLS_UNIQUE:
+  case GSASL_CB_TLS_UNIQUE:    /*XXX should never get called for this */
     HDEBUG(D_auth)
       debug_printf(" filling in\n");
     gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding);
     break;
+  case GSASL_SCRAM_SALTED_PASSWORD:
+    {
+    uschar * client_spassword =
+      ((auth_gsasl_options_block *) ablock->options_block)->client_spassword;
+    uschar dummy[4];
+    HDEBUG(D_auth) if (!client_spassword)
+      debug_printf(" client_spassword option unset\n");
+    if (client_spassword)
+      {
+      expand_nmax = 0;
+      set_exim_authvar_from_prop(sctx, GSASL_AUTHID);
+      set_exim_authvar_from_prop(sctx, GSASL_SCRAM_ITER);
+      set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALT);
+      set_client_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD, client_spassword,
+                 0, dummy, sizeof(dummy));
+      for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
+      expand_nmax = 0;
+      }
+    break;
+    }
   default:
     HDEBUG(D_auth)
       debug_printf(" not providing one\n");