X-Git-Url: https://git.exim.org/users/jgh/exim.git/blobdiff_plain/25bd12fdff615275da6b811570b0f65d57ddc441..49d478067ba22d087c65b35a8bb9b782be1ee173:/src/src/auths/gsasl_exim.c diff --git a/src/src/auths/gsasl_exim.c b/src/src/auths/gsasl_exim.c index 7003b0cbb..1c4af92fc 100644 --- a/src/src/auths/gsasl_exim.c +++ b/src/src/auths/gsasl_exim.c @@ -39,13 +39,13 @@ static void dummy(int x) { dummy2(x-1); } #include #include "gsasl_exim.h" -#ifdef SUPPORT_I18N -# include -#endif - #if GSASL_VERSION_MINOR >= 9 # define EXIM_GSASL_HAVE_SCRAM_SHA_256 + +# if GSASL_VERSION_PATCH >= 1 +# define EXIM_GSASL_SCRAM_S_KEY +# endif #endif @@ -55,34 +55,42 @@ 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. */ optionlist auth_gsasl_options[] = { - { "client_authz", opt_stringptr, + { "client_authz", opt_stringptr, (void *)(offsetof(auth_gsasl_options_block, client_authz)) }, - { "client_channelbinding", opt_bool, + { "client_channelbinding", opt_bool, (void *)(offsetof(auth_gsasl_options_block, client_channelbinding)) }, - { "client_password", opt_stringptr, + { "client_password", opt_stringptr, (void *)(offsetof(auth_gsasl_options_block, client_password)) }, - { "client_username", opt_stringptr, + { "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, + { "server_channelbinding", opt_bool, (void *)(offsetof(auth_gsasl_options_block, server_channelbinding)) }, - { "server_hostname", opt_stringptr, + { "server_hostname", opt_stringptr, (void *)(offsetof(auth_gsasl_options_block, server_hostname)) }, - { "server_mech", opt_stringptr, +#ifdef EXIM_GSASL_SCRAM_S_KEY + { "server_key", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_key)) }, +#endif + { "server_mech", opt_stringptr, (void *)(offsetof(auth_gsasl_options_block, server_mech)) }, - { "server_password", opt_stringptr, + { "server_password", opt_stringptr, (void *)(offsetof(auth_gsasl_options_block, server_password)) }, - { "server_realm", opt_stringptr, + { "server_realm", opt_stringptr, (void *)(offsetof(auth_gsasl_options_block, server_realm)) }, - { "server_scram_iter", opt_stringptr, + { "server_scram_iter", opt_stringptr, (void *)(offsetof(auth_gsasl_options_block, server_scram_iter)) }, - { "server_scram_salt", opt_stringptr, + { "server_scram_salt", opt_stringptr, (void *)(offsetof(auth_gsasl_options_block, server_scram_salt)) }, - { "server_service", opt_stringptr, +#ifdef EXIM_GSASL_SCRAM_S_KEY + { "server_skey", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_s_key)) }, +#endif + { "server_service", opt_stringptr, (void *)(offsetof(auth_gsasl_options_block, server_service)) } }; -/* GSASL_SCRAM_SALTED_PASSWORD documented only for client, so not implementing -hooks to avoid cleartext passwords in the Exim server. */ int auth_gsasl_options_count = sizeof(auth_gsasl_options)/sizeof(optionlist); @@ -97,6 +105,7 @@ auth_gsasl_options_block auth_gsasl_option_defaults = { #ifdef MACRO_PREDEF +# include "../macro_predef.h" /* Dummy values */ void auth_gsasl_init(auth_instance *ablock) {} @@ -111,6 +120,9 @@ auth_gsasl_macros(void) # ifdef EXIM_GSASL_HAVE_SCRAM_SHA_256 builtin_macro_create(US"_HAVE_AUTH_GSASL_SCRAM_SHA_256"); # endif +# ifdef EXIM_GSASL_SCRAM_S_KEY + builtin_macro_create(US"_HAVE_AUTH_GSASL_SCRAM_S_KEY"); +# endif } #else /*!MACRO_PREDEF*/ @@ -292,6 +304,56 @@ return rc; } +/************************************************* +* Debug service function * +*************************************************/ +static const uschar * +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"; +#ifdef EXIM_GSASL_SCRAM_S_KEY + case GSASL_SCRAM_CLIENTKEY: return US"SCRAM_CLIENTKEY"; +#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"; + } +return CUS string_sprintf("(unknown prop: %d)", (int)prop); +} + /************************************************* * Server entry point * *************************************************/ @@ -444,6 +506,7 @@ do { goto STOP_INTERACTION; } + /*XXX having our caller send the final smtp "235" is unfortunate; wastes a roundtrip */ if ((rc == GSASL_NEEDS_MORE) || (to_send && *to_send)) exim_error = auth_get_no64_data(USS &received, US to_send); @@ -461,6 +524,21 @@ do { STOP_INTERACTION: auth_result = rc; +HDEBUG(D_auth) + { + const uschar * s; + if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_ITER))) + debug_printf(" - itercnt: '%s'\n", s); + if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SALT))) + debug_printf(" - salt: '%s'\n", s); +#ifdef EXIM_GSASL_SCRAM_S_KEY + if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SERVERKEY))) + debug_printf(" - ServerKey: '%s'\n", s); + if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_STOREDKEY))) + debug_printf(" - StoredKey: '%s'\n", s); +#endif + } + gsasl_finish(sctx); /* Can return: OK DEFER FAIL CANCELLED BAD64 UNEXPECTED */ @@ -504,19 +582,66 @@ switch (exim_rc) return GSASL_AUTHENTICATION_ERROR; } + +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; +expand_nlength[j] = Ustrlen(propval); +expand_nmax = j; +} + +static void +set_exim_authvars_from_a_az_r_props(Gsasl_session * sctx) +{ +if (expand_nmax > 0 ) return; + +/* 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. */ + +set_exim_authvar_from_prop(sctx, GSASL_AUTHID); +set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); +set_exim_authvar_from_prop(sctx, GSASL_REALM); +} + + +static int +prop_from_option(Gsasl_session * sctx, Gsasl_property prop, + const uschar * option) +{ +HDEBUG(D_auth) debug_printf(" %s\n", gsasl_prop_code_to_name(prop)); +if (option) + { + set_exim_authvars_from_a_az_r_props(sctx); + option = expand_cstring(option); + HDEBUG(D_auth) debug_printf(" '%s'\n", option); + if (*option) + gsasl_property_set(sctx, prop, CCS option); + return GSASL_OK; + } +HDEBUG(D_auth) debug_printf(" option not set\n"); +return GSASL_NO_CALLBACK; +} + static int server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock) { char *tmps; -uschar *propval; +uschar *s, *propval; int cbrc = GSASL_NO_CALLBACK; auth_gsasl_options_block *ob = (auth_gsasl_options_block *)(ablock->options_block); -HDEBUG(D_auth) - debug_printf("GNU SASL callback %d for %s/%s as server\n", - prop, ablock->name, ablock->public_name); +HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as server\n", + gsasl_prop_code_to_name(prop), ablock->name, ablock->public_name); for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; expand_nmax = 0; @@ -524,36 +649,23 @@ expand_nmax = 0; switch (prop) { case GSASL_VALIDATE_SIMPLE: - HDEBUG(D_auth) debug_printf(" VALIDATE_SIMPLE\n"); /* GSASL_AUTHID, GSASL_AUTHZID, and GSASL_PASSWORD */ - propval = US gsasl_property_fast(sctx, GSASL_AUTHID); - auth_vars[0] = expand_nstring[1] = propval ? string_copy(propval) : US""; - propval = US gsasl_property_fast(sctx, GSASL_AUTHZID); - auth_vars[1] = expand_nstring[2] = propval ? string_copy(propval) : US""; - propval = US gsasl_property_fast(sctx, GSASL_PASSWORD); - auth_vars[2] = expand_nstring[3] = propval ? string_copy(propval) : US""; - expand_nmax = 3; - for (int i = 1; i <= 3; ++i) - expand_nlength[i] = Ustrlen(expand_nstring[i]); + set_exim_authvar_from_prop(sctx, GSASL_AUTHID); + set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); + set_exim_authvar_from_prop(sctx, GSASL_PASSWORD); cbrc = condition_check(ablock, US"server_condition", ablock->server_condition); checked_server_condition = TRUE; break; case GSASL_VALIDATE_EXTERNAL: - HDEBUG(D_auth) debug_printf(" VALIDATE_EXTERNAL\n"); if (!ablock->server_condition) { HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate EXTERNAL\n"); cbrc = GSASL_AUTHENTICATION_ERROR; break; } - 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 ? string_copy(propval) : US""; - expand_nlength[1] = Ustrlen(expand_nstring[1]); - expand_nmax = 1; + set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); cbrc = condition_check(ablock, US"server_condition (EXTERNAL)", ablock->server_condition); @@ -561,20 +673,13 @@ switch (prop) break; case GSASL_VALIDATE_ANONYMOUS: - HDEBUG(D_auth) debug_printf(" VALIDATE_ANONYMOUS\n"); if (!ablock->server_condition) { HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate ANONYMOUS\n"); cbrc = GSASL_AUTHENTICATION_ERROR; break; } - 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 ? string_copy(propval) : US""; - expand_nlength[1] = Ustrlen(expand_nstring[1]); - expand_nmax = 1; + set_exim_authvar_from_prop(sctx, GSASL_ANONYMOUS_TOKEN); cbrc = condition_check(ablock, US"server_condition (ANONYMOUS)", ablock->server_condition); @@ -582,7 +687,6 @@ switch (prop) break; case GSASL_VALIDATE_GSSAPI: - HDEBUG(D_auth) debug_printf(" VALIDATE_GSSAPI\n"); /* 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 @@ -592,13 +696,8 @@ switch (prop) 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 ? string_copy(propval) : US""; - propval = US gsasl_property_fast(sctx, GSASL_AUTHZID); - auth_vars[1] = expand_nstring[2] = propval ? string_copy(propval) : US""; - expand_nmax = 2; - for (int i = 1; i <= 2; ++i) - expand_nlength[i] = Ustrlen(expand_nstring[i]); + set_exim_authvar_from_prop(sctx, GSASL_GSSAPI_DISPLAY_NAME); + set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); /* In this one case, it perhaps makes sense to default back open? But for consistency, let's just mandate server_condition here too. */ @@ -609,67 +708,45 @@ switch (prop) break; case GSASL_SCRAM_ITER: - HDEBUG(D_auth) debug_printf(" SCRAM_ITER\n"); - if (ob->server_scram_iter) - { - tmps = CS expand_string(ob->server_scram_iter); - gsasl_property_set(sctx, GSASL_SCRAM_ITER, tmps); - cbrc = GSASL_OK; - } + cbrc = prop_from_option(sctx, prop, ob->server_scram_iter); break; case GSASL_SCRAM_SALT: - HDEBUG(D_auth) debug_printf(" SCRAM_SALT\n"); - if (ob->server_scram_iter) - { - tmps = CS expand_string(ob->server_scram_salt); - gsasl_property_set(sctx, GSASL_SCRAM_SALT, tmps); - cbrc = GSASL_OK; - } + cbrc = prop_from_option(sctx, prop, ob->server_scram_salt); + break; + +#ifdef EXIM_GSASL_SCRAM_S_KEY + case GSASL_SCRAM_STOREDKEY: + cbrc = prop_from_option(sctx, prop, ob->server_s_key); break; + case GSASL_SCRAM_SERVERKEY: + cbrc = prop_from_option(sctx, prop, ob->server_key); + break; +#endif + case GSASL_PASSWORD: - HDEBUG(D_auth) debug_printf(" PASSWORD\n"); - /* DIGEST-MD5: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM + /* SCRAM-*: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM + DIGEST-MD5: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM CRAM-MD5: GSASL_AUTHID PLAIN: GSASL_AUTHID and GSASL_AUTHZID LOGIN: GSASL_AUTHID */ - if (ob->server_scram_iter) - { - tmps = CS expand_string(ob->server_scram_iter); - gsasl_property_set(sctx, GSASL_SCRAM_ITER, tmps); - } - if (ob->server_scram_salt) + set_exim_authvars_from_a_az_r_props(sctx); + + if (!(s = ob->server_password)) { - tmps = CS expand_string(ob->server_scram_salt); - gsasl_property_set(sctx, GSASL_SCRAM_SALT, tmps); + HDEBUG(D_auth) debug_printf("option not set\n"); + break; } - - /* 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 = US gsasl_property_fast(sctx, GSASL_AUTHID); - auth_vars[0] = expand_nstring[1] = propval ? string_copy(propval) : US""; - propval = US gsasl_property_fast(sctx, GSASL_AUTHZID); - auth_vars[1] = expand_nstring[2] = propval ? string_copy(propval) : US""; - propval = US gsasl_property_fast(sctx, GSASL_REALM); - auth_vars[2] = expand_nstring[3] = propval ? string_copy(propval) : US""; - expand_nmax = 3; - for (int i = 1; i <= 3; ++i) - expand_nlength[i] = Ustrlen(expand_nstring[i]); - - if (!(tmps = CS expand_string(ob->server_password))) + if (!(tmps = CS expand_string(s))) { - sasl_error_should_defer = f.expand_string_forcedfail ? FALSE : TRUE; + sasl_error_should_defer = !f.expand_string_forcedfail; 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; } + HDEBUG(D_auth) debug_printf(" set\n"); gsasl_property_set(sctx, GSASL_PASSWORD, tmps); /* This is inadequate; don't think Exim's store stacks are geared @@ -695,41 +772,26 @@ return cbrc; /******************************************************************************/ #define PROP_OPTIONAL BIT(0) -#define PROP_STRINGPREP BIT(1) - static BOOL -client_prop(Gsasl_session * sctx, Gsasl_property propnum, uschar * val, - const uschar * why, unsigned flags, uschar * buffer, int buffsize) +set_client_prop(Gsasl_session * sctx, Gsasl_property prop, uschar * val, + unsigned flags, uschar * buffer, int buffsize) { -uschar * s, * t; +uschar * s; int rc; -if (flags & PROP_OPTIONAL && !val) return TRUE; +if (!val) return !!(flags & PROP_OPTIONAL); if (!(s = expand_string(val)) || !(flags & PROP_OPTIONAL) && !*s) { string_format(buffer, buffsize, "%s", expand_string_message); return FALSE; } -if (!*s) return TRUE; - -#ifdef SUPPORT_I18N -if (flags & PROP_STRINGPREP) +if (*s) { - if (gsasl_saslprep(CCS s, 0, CSS &t, &rc) != GSASL_OK) - { - string_format(buffer, buffsize, "Bad result from saslprep(%s): %s\n", - why, stringprep_strerror(rc)); - HDEBUG(D_auth) debug_printf("%s\n", buffer); - return FALSE; - } - gsasl_property_set(sctx, propnum, CS t); - - free(t); + HDEBUG(D_auth) debug_printf("%s: set %s = '%s'\n", __FUNCTION__, + gsasl_prop_code_to_name(prop), s); + gsasl_property_set(sctx, prop, CS s); } -else -#endif - gsasl_property_set(sctx, propnum, CS s); return TRUE; } @@ -753,8 +815,8 @@ auth_gsasl_options_block *ob = Gsasl_session * sctx = NULL; struct callback_exim_state cb_state; uschar * s; -BOOL initial = TRUE, do_stringprep; -int rc, yield = FAIL, flags; +BOOL initial = TRUE; +int rc, yield = FAIL; HDEBUG(D_auth) debug_printf("GNU SASL: initialising session for %s, mechanism %s\n", @@ -797,14 +859,15 @@ gsasl_session_hook_set(sctx, &cb_state); /* Set properties */ -flags = Ustrncmp(ob->server_mech, "SCRAM-", 5) == 0 ? PROP_STRINGPREP : 0; - -if ( !client_prop(sctx, GSASL_PASSWORD, ob->client_password, US"password", - flags, buffer, buffsize) - || !client_prop(sctx, GSASL_AUTHID, ob->client_username, US"username", - flags, buffer, buffsize) - || !client_prop(sctx, GSASL_AUTHZID, ob->client_authz, US"authz", - flags | PROP_OPTIONAL, buffer, buffsize) +if ( !set_client_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD, ob->client_spassword, + 0, buffer, buffsize) + && + !set_client_prop(sctx, GSASL_PASSWORD, ob->client_password, + 0, buffer, buffsize) + || !set_client_prop(sctx, GSASL_AUTHID, ob->client_username, + 0, buffer, buffsize) + || !set_client_prop(sctx, GSASL_AUTHZID, ob->client_authz, + PROP_OPTIONAL, buffer, buffsize) ) return ERROR; @@ -877,6 +940,12 @@ for(s = NULL; ;) } done: +HDEBUG(D_auth) + { + const uschar * s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SALTED_PASSWORD); + if (s) debug_printf(" - SaltedPassword: '%s'\n", s); + } + gsasl_finish(sctx); return yield; } @@ -884,22 +953,19 @@ return yield; static int client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock) { -HDEBUG(D_auth) debug_printf("GNU SASL callback %d for %s/%s as client\n", - prop, ablock->name, ablock->public_name); +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_AUTHZID: - HDEBUG(D_auth) debug_printf(" inquired for AUTHZID; not providing one\n"); - break; - case GSASL_SCRAM_SALTED_PASSWORD: - HDEBUG(D_auth) - debug_printf(" inquired for SCRAM_SALTED_PASSWORD; not providing one\n"); - break; case GSASL_CB_TLS_UNIQUE: HDEBUG(D_auth) - debug_printf(" inquired for CB_TLS_UNIQUE, filling in\n"); + debug_printf(" filling in\n"); gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding); break; + default: + HDEBUG(D_auth) + debug_printf(" not providing one\n"); + break; } return GSASL_NO_CALLBACK; }