From a659c7b6baac1eeed5b8490ec9ae081de5d26b51 Mon Sep 17 00:00:00 2001 From: Stephan Bosch Date: Mon, 6 Oct 2025 00:19:38 +0200 Subject: [PATCH] lib-sasl: Add GSS-SPNEGO client support --- src/lib-auth/auth-gssapi.c | 4 ++++ src/lib-auth/auth-gssapi.h | 1 + src/lib-sasl/dsasl-client-mech-gssapi.c | 23 +++++++++++++++++++- src/lib-sasl/fuzz-sasl-authentication.c | 7 +++++-- src/lib-sasl/gssapi-dummy.c | 4 ++++ src/lib-sasl/test-sasl-authentication.c | 28 +++++++++++++++++++++++-- 6 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/lib-auth/auth-gssapi.c b/src/lib-auth/auth-gssapi.c index 9eb8777469..9f248d9aec 100644 --- a/src/lib-auth/auth-gssapi.c +++ b/src/lib-auth/auth-gssapi.c @@ -5,9 +5,13 @@ static const gss_OID_desc auth_gssapi_mech_krb5_oid_desc = { 9, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; +static const gss_OID_desc auth_gssapi_mech_spnego_oid_desc = + { 6, "\x2b\x06\x01\x05\x05\x02" }; const gss_OID_desc *auth_gssapi_mech_krb5_oid = &auth_gssapi_mech_krb5_oid_desc; +const gss_OID_desc *auth_gssapi_mech_spnego_oid = + &auth_gssapi_mech_spnego_oid_desc; bool auth_gssapi_oid_equal(const gss_OID_desc *oid1, const gss_OID_desc *oid2) { diff --git a/src/lib-auth/auth-gssapi.h b/src/lib-auth/auth-gssapi.h index 14ceba2438..571facd53a 100644 --- a/src/lib-auth/auth-gssapi.h +++ b/src/lib-auth/auth-gssapi.h @@ -18,6 +18,7 @@ #endif extern const gss_OID_desc *auth_gssapi_mech_krb5_oid; +extern const gss_OID_desc *auth_gssapi_mech_spnego_oid; bool auth_gssapi_oid_equal(const gss_OID_desc *oid1, const gss_OID_desc *oid2); diff --git a/src/lib-sasl/dsasl-client-mech-gssapi.c b/src/lib-sasl/dsasl-client-mech-gssapi.c index 6e0f5e0259..4b0799365d 100644 --- a/src/lib-sasl/dsasl-client-mech-gssapi.c +++ b/src/lib-sasl/dsasl-client-mech-gssapi.c @@ -37,6 +37,9 @@ struct gssapi_sasl_client { buffer_t *out_buf; }; +static const struct dsasl_client_mech dsasl_client_mech_gssapi; +static const struct dsasl_client_mech dsasl_client_mech_gss_spnego; + static void mech_gssapi_error_append(string_t *msg, unsigned int *entries, OM_uint32 status_value, int status_type) @@ -131,9 +134,16 @@ mech_gssapi_sec_context(struct gssapi_sasl_client *gclient, OM_uint32 major_status, minor_status; OM_uint32 req_flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_INTEG_FLAG; - gss_OID_desc mech_oid = *auth_gssapi_mech_krb5_oid; + gss_OID_desc mech_oid; gss_OID ret_mech_oid; + if (client->mech == &dsasl_client_mech_gssapi) + mech_oid = *auth_gssapi_mech_krb5_oid; + else if (client->mech == &dsasl_client_mech_gss_spnego) + mech_oid = *auth_gssapi_mech_spnego_oid; + else + i_unreached(); + major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL, &gclient->gss_ctx, gclient->gss_principal, @@ -362,6 +372,16 @@ static const struct dsasl_client_mech dsasl_client_mech_gssapi = { .free = mech_gssapi_free, }; +static const struct dsasl_client_mech dsasl_client_mech_gss_spnego = { + .name = "GSS-SPNEGO", + .flags = DSASL_MECH_SEC_ALLOW_NULS, + .struct_size = sizeof(struct gssapi_sasl_client), + + .input = mech_gssapi_gs1_input, + .output = mech_gssapi_gs1_output, + .free = mech_gssapi_free, +}; + static bool initialized = FALSE; void dsasl_clients_init_gssapi(void) @@ -371,4 +391,5 @@ void dsasl_clients_init_gssapi(void) initialized = TRUE; dsasl_client_mech_register(&dsasl_client_mech_gssapi); + dsasl_client_mech_register(&dsasl_client_mech_gss_spnego); } diff --git a/src/lib-sasl/fuzz-sasl-authentication.c b/src/lib-sasl/fuzz-sasl-authentication.c index 4d8d5fc77d..7bf0150ae9 100644 --- a/src/lib-sasl/fuzz-sasl-authentication.c +++ b/src/lib-sasl/fuzz-sasl-authentication.c @@ -188,7 +188,8 @@ fuzz_server_request_lookup_credentials( i_zero(&result); #ifdef HAVE_GSSAPI - if (strcmp(fctx->params->mech, SASL_MECH_NAME_GSSAPI) == 0) { + if (strcmp(fctx->params->mech, SASL_MECH_NAME_GSSAPI) == 0 || + strcmp(fctx->params->mech, SASL_MECH_NAME_GSS_SPNEGO) == 0) { i_assert(*scheme == '\0'); result.status = SASL_PASSDB_RESULT_OK; callback(&fctx->ssrctx, &result); @@ -642,6 +643,7 @@ static void fuzz_sasl_run(struct istream *input) i_zero(&gssapi_set); gssapi_set.hostname = "localhost"; sasl_server_mech_register_gssapi(server_inst, &gssapi_set); + sasl_server_mech_register_gss_spnego(server_inst, &gssapi_set); #endif const struct sasl_server_mech *server_mech; @@ -656,7 +658,8 @@ static void fuzz_sasl_run(struct istream *input) e_debug(fuzz_event, "run: %s", str_sanitize(params.mech, 1024)); #ifdef HAVE_GSSAPI - if (strcmp(params.mech, SASL_MECH_NAME_GSSAPI) == 0) { + if (strcmp(params.mech, SASL_MECH_NAME_GSSAPI) == 0 || + strcmp(params.mech, SASL_MECH_NAME_GSS_SPNEGO) == 0) { gss_dummy_add_principal(params.authid); gss_dummy_kinit(params.authid); } diff --git a/src/lib-sasl/gssapi-dummy.c b/src/lib-sasl/gssapi-dummy.c index 85663caac7..75bbbc2d7c 100644 --- a/src/lib-sasl/gssapi-dummy.c +++ b/src/lib-sasl/gssapi-dummy.c @@ -130,6 +130,10 @@ gss_init_sec_context(OM_uint32 *minor_status, ctx->req_flags = req_flags; ctx->mech_type = *mech_type; + /* Morph SPNEGO into normal Kerberos5 */ + if (auth_gssapi_oid_equal(&ctx->mech_type, auth_gssapi_mech_spnego_oid)) + ctx->mech_type = *auth_gssapi_mech_krb5_oid; + size_t src_name_len = strlen(ctx->src_name); size_t cbind_len = 0; if (input_chan_bindings != GSS_C_NO_CHANNEL_BINDINGS) { diff --git a/src/lib-sasl/test-sasl-authentication.c b/src/lib-sasl/test-sasl-authentication.c index 96bfbc3ae5..e25e32b430 100644 --- a/src/lib-sasl/test-sasl-authentication.c +++ b/src/lib-sasl/test-sasl-authentication.c @@ -209,7 +209,8 @@ test_server_request_lookup_credentials( i_zero(&result); #ifdef HAVE_GSSAPI - if (strcmp(tctx->test->mech, SASL_MECH_NAME_GSSAPI) == 0) { + if (strcmp(tctx->test->mech, SASL_MECH_NAME_GSSAPI) == 0 || + strcmp(tctx->test->mech, SASL_MECH_NAME_GSS_SPNEGO) == 0) { i_assert(*scheme == '\0'); result.status = SASL_PASSDB_RESULT_OK; callback(&tctx->ssrctx, &result); @@ -529,6 +530,7 @@ test_sasl_run(const struct test_sasl *test, const char *label, i_zero(&gssapi_set); gssapi_set.hostname = "localhost"; sasl_server_mech_register_gssapi(server_inst, &gssapi_set); + sasl_server_mech_register_gss_spnego(server_inst, &gssapi_set); #endif const struct sasl_server_mech *server_mech; @@ -537,7 +539,8 @@ test_sasl_run(const struct test_sasl *test, const char *label, i_assert(server_mech != NULL); #ifdef HAVE_GSSAPI - if (strcmp(test->mech, SASL_MECH_NAME_GSSAPI) == 0) { + if (strcmp(test->mech, SASL_MECH_NAME_GSSAPI) == 0 || + strcmp(test->mech, SASL_MECH_NAME_GSS_SPNEGO) == 0) { gss_dummy_add_principal(test->server.authid); gss_dummy_kinit(test->client.authid != NULL ? test->client.authid : test->server.authid); @@ -759,6 +762,14 @@ static const struct test_sasl success_tests[] = { .password = "", }, }, + { + .mech = "GSS-SPNEGO", + .authid_type = SASL_SERVER_AUTHID_TYPE_USERNAME, + .server = { + .authid = "user", + .password = "", + }, + }, #endif /* NTLM */ { @@ -1499,6 +1510,19 @@ static const struct test_sasl bad_creds_tests[] = { }, .failure = TRUE, }, + /* GSS-SPNEGO */ + { + .mech = "GSS-SPNEGO", + .authid_type = SASL_SERVER_AUTHID_TYPE_USERNAME, + .server = { + .authid = "user", + .password = "", + }, + .client = { + .authid = "userb", + }, + .failure = TRUE, + }, #endif /* NTLM */ { -- 2.47.3