/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
+# define EXIM_GSASL_SCRAM_S_KEY
+# if GSASL_VERSION_MINOR >= 1
+# define EXIM_GSASL_HAVE_EXPORTER
+# elif GSASL_VERSION_PATCH >= 1
+# define EXIM_GSASL_HAVE_EXPORTER
+# endif
+
+# 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
/* Authenticator-specific options. */
/* I did have server_*_condition options for various mechanisms, but since
/* Authenticator-specific options. */
/* I did have server_*_condition options for various mechanisms, but since
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;}
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;}
"GNU SASL does not support mechanism \"%s\"",
ablock->name, 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",
ablock->server = FALSE;
HDEBUG(D_auth) debug_printf("%s authenticator: "
"Need server_condition for %s mechanism\n",
-/* 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.
-*/
-
{
HDEBUG(D_auth) debug_printf("GSASL_CB_TLS_UNIQUE from ctx hook\n");
gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CS s);
{
HDEBUG(D_auth) debug_printf("GSASL_CB_TLS_UNIQUE from ctx hook\n");
gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CS s);
- 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";
+ 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";
- case GSASL_SCRAM_STOREDKEY: return US"SCRAM_STOREDKEY";
- case GSASL_SCRAM_SERVERKEY: return US"SCRAM_SERVERKEY";
+ case GSASL_SCRAM_STOREDKEY: return US"SCRAM_STOREDKEY";
+ case GSASL_SCRAM_SERVERKEY: return US"SCRAM_SERVERKEY";
- 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";
-#ifdef EXIM_GSASL_SCRAM_S_KEY
- case GSASL_SCRAM_CLIENTKEY: return US"SCRAM_CLIENTKEY";
+#ifdef EXIM_GSASL_HAVE_EXPORTER /* v. 2.1.0 */
+ case GSASL_CB_TLS_EXPORTER: return US"CB_TLS_EXPORTER";
- 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";
+static void
+preload_prop(Gsasl_session * sctx, Gsasl_property propcode, const uschar * val)
+{
+DEBUG(D_auth) debug_printf("preloading prop %s val %s\n",
+ gsasl_prop_code_to_name(propcode), val);
+gsasl_property_set(sctx, propcode, CCS val);
+}
+
/*************************************************
* Server entry point *
*************************************************/
/*************************************************
* Server entry point *
*************************************************/
-char *tmps;
-char *to_send, *received;
-Gsasl_session *sctx = NULL;
-auth_gsasl_options_block *ob =
+uschar * tmps;
+char * to_send, * received;
+Gsasl_session * sctx = NULL;
+auth_gsasl_options_block * ob =
(auth_gsasl_options_block *)(ablock->options_block);
struct callback_exim_state cb_state;
int rc, auth_result, exim_error, exim_error_override;
(auth_gsasl_options_block *)(ablock->options_block);
struct callback_exim_state cb_state;
int rc, auth_result, exim_error, exim_error_override;
if (!tls_in.ext_master_secret && tls_in.resumption == RESUME_USED)
{ /* per RFC 7677 section 4 */
HDEBUG(D_auth) debug_printf(
if (!tls_in.ext_master_secret && tls_in.resumption == RESUME_USED)
{ /* per RFC 7677 section 4 */
HDEBUG(D_auth) debug_printf(
-/* 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. */
cb_state.currently = CURRENTLY_SERVER;
gsasl_session_hook_set(sctx, &cb_state);
cb_state.currently = CURRENTLY_SERVER;
gsasl_session_hook_set(sctx, &cb_state);
-tmps = CS expand_string(ob->server_service);
-gsasl_property_set(sctx, GSASL_SERVICE, tmps);
-tmps = CS expand_string(ob->server_hostname);
-gsasl_property_set(sctx, GSASL_HOSTNAME, tmps);
+tmps = expand_string(ob->server_service);
+preload_prop(sctx, GSASL_SERVICE, tmps);
+tmps = expand_string(ob->server_hostname);
+preload_prop(sctx, GSASL_HOSTNAME, tmps);
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*
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)
{
HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
ablock->name);
# ifndef CHANNELBIND_HACK
*/
if (ob->server_channelbinding)
{
HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
ablock->name);
# ifndef CHANNELBIND_HACK
- gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_in.channelbinding);
+ preload_prop(sctx,
+# ifdef EXIM_GSASL_HAVE_EXPORTER
+ tls_in.channelbind_exporter ? GSASL_CB_TLS_EXPORTER :
+# endif
+ GSASL_CB_TLS_UNIQUE,
+ tls_in.channelbinding);
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"";
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;
server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop,
auth_instance *ablock)
{
server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop,
auth_instance *ablock)
{
(auth_gsasl_options_block *)(ablock->options_block);
HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as server\n",
(auth_gsasl_options_block *)(ablock->options_block);
HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as server\n",
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. */
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. */
if (!val) return !!(flags & PROP_OPTIONAL);
if (!(s = expand_string(val)) || !(flags & PROP_OPTIONAL) && !*s)
if (!val) return !!(flags & PROP_OPTIONAL);
if (!(s = expand_string(val)) || !(flags & PROP_OPTIONAL) && !*s)
void * sx, /* connection */
int timeout, /* command timeout */
void * sx, /* connection */
int timeout, /* command timeout */
(auth_gsasl_options_block *)(ablock->options_block);
Gsasl_session * sctx = NULL;
struct callback_exim_state cb_state;
(auth_gsasl_options_block *)(ablock->options_block);
Gsasl_session * sctx = NULL;
struct callback_exim_state cb_state;
string_format(buffer, buffsize, "%s",
"channel binding not usable on resumed TLS without extended-master-secret");
return FAIL;
}
# endif
# ifdef CHANNELBIND_HACK
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. */
-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)
0, buffer, buffsize)
|| !set_client_prop(sctx, GSASL_AUTHID, ob->client_username,
0, buffer, buffsize)
HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
ablock->name);
# ifndef CHANNELBIND_HACK
HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
ablock->name);
# ifndef CHANNELBIND_HACK
- gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding);
+ preload_prop(sctx,
+# ifdef EXIM_GSASL_HAVE_EXPORTER
+ tls_out.channelbind_exporter ? GSASL_CB_TLS_EXPORTER :
+# endif
+ GSASL_CB_TLS_UNIQUE,
+ tls_out.channelbinding);
- fail = initial
- ? smtp_write_command(sx, SCMD_FLUSH,
- outstr ? "AUTH %s %s\r\n" : "AUTH %s\r\n",
- ablock->public_name, outstr) <= 0
- : outstr
- ? smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", outstr) <= 0
- : FALSE;
- if (outstr && *outstr) free(outstr);
- if (fail)
+ if (rc == GSASL_NEEDS_MORE || rc == GSASL_OK)
- yield = FAIL_SEND;
- goto done;
+ fail = initial
+ ? smtp_write_command(sx, SCMD_FLUSH,
+ outstr ? "AUTH %s %s\r\n" : "AUTH %s\r\n",
+ ablock->public_name, outstr) <= 0
+ : outstr
+ ? smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", outstr) <= 0
+ : FALSE;
+ free(outstr);
+ if (fail)
+ {
+ yield = FAIL_SEND;
+ goto done;
+ }
+ initial = FALSE;
- 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);
- case GSASL_CB_TLS_UNIQUE:
- HDEBUG(D_auth)
- debug_printf(" filling in\n");
+#ifdef EXIM_GSASL_HAVE_EXPORTER
+ case GSASL_CB_TLS_EXPORTER: /* Should never get called for this, as pre-set */
+ if (!tls_out.channelbind_exporter) break;
+ HDEBUG(D_auth) debug_printf(" filling in\n");
+ gsasl_property_set(sctx, GSASL_CB_TLS_EXPORTER, CCS tls_out.channelbinding);
+ return GSASL_OK;
+#endif
+ case GSASL_CB_TLS_UNIQUE: /* Should never get called for this, as pre-set */
+#ifdef EXIM_GSASL_HAVE_EXPORTER
+ if (tls_out.channelbind_exporter) break;
+#endif
+ HDEBUG(D_auth) debug_printf(" filling in\n");
+ return GSASL_OK;
+ 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;
+ }
-const char *runtime;
-runtime = gsasl_check_version(NULL);
-fprintf(f, "Library version: GNU SASL: Compile: %s\n"
- " Runtime: %s\n",
- GSASL_VERSION, runtime);
+return string_fmt_append(g, "Library version: GNU SASL: Compile: %s\n"
+ " Runtime: %s\n",
+ GSASL_VERSION, gsasl_check_version(NULL));