]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-sasl: Add GSS-SPNEGO client support
authorStephan Bosch <stephan.bosch@open-xchange.com>
Sun, 5 Oct 2025 22:19:38 +0000 (00:19 +0200)
committertimo.sirainen <timo.sirainen@open-xchange.com>
Thu, 9 Oct 2025 08:41:22 +0000 (08:41 +0000)
src/lib-auth/auth-gssapi.c
src/lib-auth/auth-gssapi.h
src/lib-sasl/dsasl-client-mech-gssapi.c
src/lib-sasl/fuzz-sasl-authentication.c
src/lib-sasl/gssapi-dummy.c
src/lib-sasl/test-sasl-authentication.c

index 9eb877746930b7f0bef5daa1c093d6459a674c5c..9f248d9aec7c0c0471ee24ad502a83a9fbbf5b20 100644 (file)
@@ -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)
 {
index 14ceba2438ca4bbd446243992ab4f7d778b8b874..571facd53aeb1909afcc88e548a061fb011c8fc3 100644 (file)
@@ -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);
 
index 6e0f5e0259d7d565cdbc7221deec431471c4f2e3..4b0799365d9179b3faef476ed5c93c1090b64472 100644 (file)
@@ -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);
 }
index 4d8d5fc77d3fb3a2c4440a4465121aaef69939a5..7bf0150ae9ea33461d57fee0b3b6b0e17c545dda 100644 (file)
@@ -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);
        }
index 85663caac715022920b2908f98ad5020688c1566..75bbbc2d7c286c87ec12615e01ae6ba3246fa7e3 100644 (file)
@@ -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) {
index 96bfbc3ae5dbb38b7a2a54cce1d8123b426a9d32..e25e32b4300c9b9b8fb8ebda57f599b76db886c8 100644 (file)
@@ -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 */
        {