]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-sasl: Add GSSAPI client support
authorStephan Bosch <stephan.bosch@open-xchange.com>
Sun, 5 Oct 2025 16:58:37 +0000 (18:58 +0200)
committertimo.sirainen <timo.sirainen@open-xchange.com>
Thu, 9 Oct 2025 08:41:22 +0000 (08:41 +0000)
src/lib-sasl/Makefile.am
src/lib-sasl/dsasl-client-mech-gssapi.c [new file with mode: 0644]
src/lib-sasl/dsasl-client.h
src/lib-sasl/fuzz-sasl-authentication.c
src/lib-sasl/gssapi-dummy.c [new file with mode: 0644]
src/lib-sasl/gssapi-dummy.h [new file with mode: 0644]
src/lib-sasl/krb5-dummy.c [new file with mode: 0644]
src/lib-sasl/test-sasl-authentication.c

index 9cf297712a8a0b2ea968896c38fb612e5382eee9..70d9765d38bd7f6fd1da8a8b1e2d3712ac737d8d 100644 (file)
@@ -53,6 +53,7 @@ libsasl_la_DEPENDENCIES = \
 
 if HAVE_GSSAPI
 libsasl_gssapi_la_SOURCES = \
+       dsasl-client-mech-gssapi.c \
        sasl-server-mech-gssapi.c
 libsasl_gssapi_la_LIBADD = $(KRB5_LIBS)
 libsasl_gssapi_la_CPPFLAGS = $(AM_CPPFLAGS) $(KRB5_CFLAGS)
@@ -77,7 +78,8 @@ pkginc_libdir=$(pkgincludedir)
 pkginc_lib_HEADERS = $(headers)
 
 noinst_HEADERS =\
-       dsasl-client-mech-ntlm-dummy.h
+       dsasl-client-mech-ntlm-dummy.h \
+       gssapi-dummy.h
 
 test_programs = \
        test-sasl-oauth2 \
@@ -117,6 +119,17 @@ test_sasl_authentication_SOURCES = \
        test-sasl-authentication.c
 test_sasl_authentication_LDADD = $(test_libs)
 test_sasl_authentication_DEPENDENCIES = $(test_deps)
+if HAVE_GSSAPI
+test_sasl_authentication_SOURCES += \
+       gssapi-dummy.c \
+       krb5-dummy.c \
+       dsasl-client-mech-gssapi.c \
+       sasl-server-mech-gssapi.c
+test_sasl_authentication_LDADD += \
+       ../lib-auth/libauth-gssapi.la
+test_sasl_authentication_DEPENDENCIES += \
+       ../lib-auth/libauth-gssapi.la
+endif
 
 ntlm_dummy_SOURCES = ntlm_dummy.c
 ntlm_dummy_LDADD = ../lib/liblib.la
@@ -130,6 +143,17 @@ fuzz_sasl_authentication_SOURCES = \
        fuzz-sasl-authentication.c
 fuzz_sasl_authentication_LDADD = $(test_libs)
 fuzz_sasl_authentication_DEPENDENCIES = $(test_deps)
+if HAVE_GSSAPI
+fuzz_sasl_authentication_SOURCES += \
+       gssapi-dummy.c \
+       krb5-dummy.c \
+       dsasl-client-mech-gssapi.c \
+       sasl-server-mech-gssapi.c
+fuzz_sasl_authentication_LDADD += \
+       ../lib-auth/libauth-gssapi.la
+fuzz_sasl_authentication_DEPENDENCIES += \
+       ../lib-auth/libauth-gssapi.la
+endif
 
 check-local:
        for bin in $(test_programs); do \
diff --git a/src/lib-sasl/dsasl-client-mech-gssapi.c b/src/lib-sasl/dsasl-client-mech-gssapi.c
new file mode 100644 (file)
index 0000000..6e0f5e0
--- /dev/null
@@ -0,0 +1,374 @@
+/* Copyright (c) 2025 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "buffer.h"
+#include "auth-gssapi.h"
+
+#include "dsasl-client-private.h"
+
+/* Non-zero flags defined in RFC 2222 */
+enum sasl_gssapi_qop {
+       SASL_GSSAPI_QOP_UNSPECIFIED = 0x00,
+       SASL_GSSAPI_QOP_AUTH_ONLY   = 0x01,
+       SASL_GSSAPI_QOP_AUTH_INT    = 0x02,
+       SASL_GSSAPI_QOP_AUTH_CONF   = 0x04
+};
+
+enum gssapi_gs1_client_state {
+       GSSAPI_GS1_CLIENT_STATE_INIT = 0,
+       GSSAPI_GS1_CLIENT_STATE_SEC_CONTEXT,
+       GSSAPI_GS1_CLIENT_STATE_UNWRAP,
+       GSSAPI_GS1_CLIENT_STATE_WRAP,
+       GSSAPI_GS1_CLIENT_STATE_END,
+};
+
+struct gssapi_sasl_client {
+       struct dsasl_client client;
+
+       union {
+               enum gssapi_gs1_client_state gs1;
+       } state;
+
+       gss_name_t gss_principal;
+       gss_ctx_id_t gss_ctx;
+
+       buffer_t *out_buf;
+};
+
+static void
+mech_gssapi_error_append(string_t *msg, unsigned int *entries,
+                        OM_uint32 status_value, int status_type)
+{
+       OM_uint32 major_status, minor_status;
+       OM_uint32 message_context = 0;
+       gss_buffer_desc status_string;
+
+       do {
+               major_status = gss_display_status(&minor_status, status_value,
+                                                 status_type, GSS_C_NO_OID,
+                                                 &message_context,
+                                                 &status_string);
+               if (major_status == GSS_S_COMPLETE &&
+                   status_string.length > 0) {
+                       if ((*entries)++ > 0)
+                               str_append(msg, "; ");
+                       str_append(msg, str_sanitize(status_string.value,
+                                                    status_string.length));
+               }
+               (void)gss_release_buffer(&minor_status, &status_string);
+       } while (GSS_ERROR(major_status) == 0 && message_context != 0);
+}
+
+static const char *
+mech_gssapi_error(const char *description, OM_uint32 major_status,
+                 OM_uint32 minor_status)
+{
+       string_t *msg = t_str_new(128);
+       unsigned int entries = 0;
+
+       str_append(msg, "While ");
+       str_append(msg, description);
+       str_append(msg, ": ");
+
+       if (major_status != GSS_S_FAILURE) {
+               mech_gssapi_error_append(msg, &entries, major_status,
+                                        GSS_C_GSS_CODE);
+       }
+       mech_gssapi_error_append(msg, &entries, minor_status, GSS_C_MECH_CODE);
+
+       return str_c(msg);
+}
+
+static enum dsasl_client_result
+mech_gssapi_gs1_init(struct gssapi_sasl_client *gclient, gss_buffer_t in_buf,
+                    const char **error_r)
+{
+       struct dsasl_client *client = &gclient->client;
+       OM_uint32 major_status, minor_status;
+       string_t *principal_name;
+       gss_buffer_desc namebuf;
+
+       gclient->gss_ctx = GSS_C_NO_CONTEXT;
+       gclient->gss_principal = GSS_C_NO_NAME;
+
+       if (in_buf->length > 0) {
+               *error_r = "Unexpected initial server challenge";
+               return DSASL_CLIENT_RESULT_ERR_PROTOCOL;
+       }
+
+       principal_name = t_str_new(128);
+       str_append(principal_name, client->set.protocol);
+       str_append_c(principal_name, '@');
+       str_append(principal_name, client->set.host);
+
+       namebuf.length = str_len(principal_name);
+       namebuf.value = str_c_modifiable(principal_name);
+
+       major_status = gss_import_name(&minor_status, &namebuf,
+                                      GSS_C_NT_HOSTBASED_SERVICE,
+                                      &gclient->gss_principal);
+       if (GSS_ERROR(major_status) != 0) {
+               *error_r = mech_gssapi_error("importing principal name",
+                                            major_status, minor_status);
+               return DSASL_CLIENT_RESULT_ERR_PROTOCOL;
+       }
+
+       e_debug(client->event, "Successfully imported principal name");
+
+       gclient->out_buf = buffer_create_dynamic(default_pool, 1024);
+       gclient->state.gs1 = GSSAPI_GS1_CLIENT_STATE_SEC_CONTEXT;
+       return DSASL_CLIENT_RESULT_OK;
+}
+
+static int
+mech_gssapi_sec_context(struct gssapi_sasl_client *gclient,
+                       gss_buffer_t in_buf, gss_buffer_t out_buf,
+                       const char **error_r)
+{
+       struct dsasl_client *client = &gclient->client;
+       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 ret_mech_oid;
+
+       major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL,
+                                           &gclient->gss_ctx,
+                                           gclient->gss_principal,
+                                           &mech_oid, req_flags,
+                                           0, GSS_C_NO_CHANNEL_BINDINGS,
+                                           in_buf, &ret_mech_oid, out_buf,
+                                           NULL, NULL);
+       if(GSS_ERROR(major_status) != 0) {
+               (void)gss_release_buffer(&minor_status, out_buf);
+               *error_r = mech_gssapi_error((in_buf->length == 0 ?
+                                             "initializing security context" :
+                                             "processing incoming data"),
+                                            major_status, minor_status);
+               return -1;
+       }
+
+       switch (major_status) {
+       case GSS_S_COMPLETE:
+               i_assert(ret_mech_oid != NULL);
+               if (!auth_gssapi_oid_equal(ret_mech_oid,
+                                          auth_gssapi_mech_krb5_oid)) {
+                       *error_r = "GSSAPI mechanism not Kerberos5";
+                       return -1;
+               }
+               e_debug(client->event,
+                       "Security context state completed");
+               break;
+       case GSS_S_CONTINUE_NEEDED:
+               e_debug(client->event,
+                       "Processed incoming packet correctly, "
+                       "waiting for another");
+               return 0;
+       default:
+               i_unreached();
+       }
+
+       return 1;
+}
+
+static enum dsasl_client_result
+mech_gssapi_gs1_sec_context(struct gssapi_sasl_client *gclient,
+                           gss_buffer_t in_buf, const char **error_r)
+{
+       OM_uint32 minor_status;
+       gss_buffer_desc out_buf;
+       int ret;
+
+       ret = mech_gssapi_sec_context(gclient, in_buf, &out_buf, error_r);
+       if (ret < 0)
+               return DSASL_CLIENT_RESULT_ERR_PROTOCOL;
+
+       buffer_append(gclient->out_buf, out_buf.value, out_buf.length);
+       (void)gss_release_buffer(&minor_status, &out_buf);
+
+       if (ret > 0)
+               gclient->state.gs1 = GSSAPI_GS1_CLIENT_STATE_UNWRAP;
+
+       return DSASL_CLIENT_RESULT_OK;
+}
+
+static enum dsasl_client_result
+mech_gssapi_gs1_unwrap(struct gssapi_sasl_client *gclient,
+                      gss_buffer_t in_buf, const char **error_r)
+{
+       struct dsasl_client *client = &gclient->client;
+       OM_uint32 major_status, minor_status;
+       gss_buffer_desc in_buf_wrap = GSS_C_EMPTY_BUFFER;
+       gss_buffer_desc out_buf = GSS_C_EMPTY_BUFFER;
+       gss_qop_t qop = GSS_C_QOP_DEFAULT;
+       unsigned char *data;
+       unsigned int sec_layer = 0;
+       unsigned int max_size = 0;
+       buffer_t *buf;
+       size_t authzid_size = (client->set.authzid == NULL ?
+                              0 : strlen(client->set.authzid));
+
+       /* Decrypt the inbound challenge and obtain the qop */
+       major_status = gss_unwrap(&minor_status, gclient->gss_ctx, in_buf,
+                                 &out_buf, NULL, &qop);
+       if(GSS_ERROR(major_status) != 0) {
+               *error_r = mech_gssapi_error(
+                       "receiving security layer negotiation",
+                       major_status, minor_status);
+               return DSASL_CLIENT_RESULT_ERR_PROTOCOL;
+       }
+
+       if (out_buf.length != 4) {
+               *error_r = "Bad server message: Invalid security data";
+               return DSASL_CLIENT_RESULT_ERR_PROTOCOL;
+       }
+
+       data = out_buf.value;
+       sec_layer = data[0];
+       max_size = (data[1] << 16) | (data[2] << 8) | data[3];
+       (void)gss_release_buffer(&minor_status, &out_buf);
+
+       /* Check server parameters */
+       if((sec_layer & SASL_GSSAPI_QOP_AUTH_ONLY) == 0) {
+               *error_r = "Server demands unsupported security parameters";
+               return DSASL_CLIENT_RESULT_ERR_PROTOCOL;
+       }
+       sec_layer = SASL_GSSAPI_QOP_AUTH_ONLY;
+       max_size = 0;
+
+       buf = t_buffer_create(4 + authzid_size);
+       buffer_append_c(buf, sec_layer);
+       buffer_append_c(buf, max_size >> 16);
+       buffer_append_c(buf, max_size >> 8);
+       buffer_append_c(buf, max_size >> 0);
+       if (client->set.authzid != NULL)
+               buffer_append(buf, client->set.authzid, authzid_size);
+
+       /* Setup the "authentication data" security buffer */
+       in_buf_wrap.value =
+               buffer_get_modifiable_data(buf, &in_buf_wrap.length);
+
+       /* Encrypt the data */
+       major_status = gss_wrap(&minor_status, gclient->gss_ctx, 0,
+                               GSS_C_QOP_DEFAULT, &in_buf_wrap, NULL,
+                               &out_buf);
+       if (GSS_ERROR(major_status) != 0) {
+               *error_r = mech_gssapi_error(
+                       "sending security layer negotiation",
+                       major_status, minor_status);
+               return DSASL_CLIENT_RESULT_ERR_INTERNAL;
+       }
+
+       e_debug(client->event, "Negotiated security layer");
+
+       buffer_clear(gclient->out_buf);
+       buffer_append(gclient->out_buf, out_buf.value, out_buf.length);
+       (void)gss_release_buffer(&minor_status, &out_buf);
+
+       gclient->state.gs1 = GSSAPI_GS1_CLIENT_STATE_WRAP;
+       return DSASL_CLIENT_RESULT_OK;
+}
+
+static enum dsasl_client_result
+mech_gssapi_gs1_input(struct dsasl_client *client,
+                     const unsigned char *input, size_t input_len,
+                     const char **error_r)
+{
+       struct gssapi_sasl_client *gclient =
+               container_of(client, struct gssapi_sasl_client, client);
+       gss_buffer_desc in_buf;
+       enum dsasl_client_result result = DSASL_CLIENT_RESULT_ERR_INTERNAL;
+
+       in_buf.value = (void *)input;
+       in_buf.length = input_len;
+
+       switch (gclient->state.gs1) {
+       case GSSAPI_GS1_CLIENT_STATE_INIT:
+               result = mech_gssapi_gs1_init(gclient, &in_buf, error_r);
+               break;
+       case GSSAPI_GS1_CLIENT_STATE_SEC_CONTEXT:
+               result = mech_gssapi_gs1_sec_context(gclient, &in_buf, error_r);
+               break;
+       case GSSAPI_GS1_CLIENT_STATE_UNWRAP:
+               result = mech_gssapi_gs1_unwrap(gclient, &in_buf, error_r);
+               break;
+       case GSSAPI_GS1_CLIENT_STATE_WRAP:
+       case GSSAPI_GS1_CLIENT_STATE_END:
+       default:
+               i_unreached();
+       }
+       return result;
+}
+
+static enum dsasl_client_result
+mech_gssapi_gs1_output(struct dsasl_client *client,
+                      const unsigned char **output_r, size_t *output_len_r,
+                      const char **error_r)
+{
+       struct gssapi_sasl_client *gclient =
+               container_of(client, struct gssapi_sasl_client, client);
+       gss_buffer_desc in_buf = GSS_C_EMPTY_BUFFER;
+       enum dsasl_client_result result = DSASL_CLIENT_RESULT_ERR_INTERNAL;
+
+       switch (gclient->state.gs1) {
+       case GSSAPI_GS1_CLIENT_STATE_INIT:
+               result = mech_gssapi_gs1_init(gclient, &in_buf, error_r);
+               if (result != DSASL_CLIENT_RESULT_OK)
+                       return result;
+               i_assert(gclient->state.gs1 ==
+                        GSSAPI_GS1_CLIENT_STATE_SEC_CONTEXT);
+               result = mech_gssapi_gs1_sec_context(gclient, &in_buf, error_r);
+               if (result != DSASL_CLIENT_RESULT_OK)
+                       return result;
+               break;
+       case GSSAPI_GS1_CLIENT_STATE_SEC_CONTEXT:
+       case GSSAPI_GS1_CLIENT_STATE_UNWRAP:
+       case GSSAPI_GS1_CLIENT_STATE_WRAP:
+               break;
+       case GSSAPI_GS1_CLIENT_STATE_END:
+       default:
+               i_unreached();
+       }
+
+       *output_r = gclient->out_buf->data;
+       *output_len_r = gclient->out_buf->used;
+       return DSASL_CLIENT_RESULT_OK;
+}
+
+static void mech_gssapi_free(struct dsasl_client *client)
+{
+       struct gssapi_sasl_client *gclient =
+               container_of(client, struct gssapi_sasl_client, client);
+       OM_uint32 minor_status;
+
+       if (gclient->gss_ctx != GSS_C_NO_CONTEXT) {
+               (void)gss_delete_sec_context(&minor_status, &gclient->gss_ctx,
+                                            GSS_C_NO_BUFFER);
+       }
+       if (gclient->gss_principal != GSS_C_NO_NAME)
+               (void)gss_release_name(&minor_status, &gclient->gss_principal);
+       buffer_free(&gclient->out_buf);
+}
+
+static const struct dsasl_client_mech dsasl_client_mech_gssapi = {
+       .name = "GSSAPI",
+       .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)
+{
+       if (initialized)
+               return;
+
+       initialized = TRUE;
+       dsasl_client_mech_register(&dsasl_client_mech_gssapi);
+}
index aa055ec0078e6b65e1286ecac1d40111cd84d91f..d6c384241ecb23b9ba11e7c13c90b1c770424c86 100644 (file)
@@ -83,4 +83,6 @@ int dsasl_client_get_result(struct dsasl_client *client,
 void dsasl_clients_init(void);
 void dsasl_clients_deinit(void);
 
+void dsasl_clients_init_gssapi(void);
+
 #endif
index ae45db39ad860c457b27d67afc07b26015aaf47d..4d8d5fc77d3fb3a2c4440a4465121aaef69939a5 100644 (file)
@@ -9,7 +9,9 @@
 #include "ioloop.h"
 #include "istream.h"
 #include "password-scheme.h"
+#include "gssapi-dummy.h"
 #include "sasl-server.h"
+#include "sasl-server-gssapi.h"
 #include "sasl-server-oauth2.h"
 #include "dsasl-client.h"
 #include "dsasl-client-mech-ntlm-dummy.h"
@@ -183,17 +185,28 @@ fuzz_server_request_lookup_credentials(
                container_of(rctx, struct fuzz_sasl_context, ssrctx);
        struct sasl_passdb_result result;
 
-       const struct password_generate_params params = {
-               .user = fctx->params->authid,
-       };
-
        i_zero(&result);
+
+#ifdef HAVE_GSSAPI
+       if (strcmp(fctx->params->mech, SASL_MECH_NAME_GSSAPI) == 0) {
+               i_assert(*scheme == '\0');
+               result.status = SASL_PASSDB_RESULT_OK;
+               callback(&fctx->ssrctx, &result);
+               return;
+       }
+#endif
+
        if (null_strcmp(fctx->authid, fctx->params->authid) != 0 ||
            null_strcmp(fctx->authzid, fctx->params->authzid) != 0) {
                result.status = SASL_PASSDB_RESULT_USER_UNKNOWN;
                callback(&fctx->ssrctx, &result);
                return;
        }
+
+       const struct password_generate_params params = {
+               .user = fctx->params->authid,
+       };
+
        if (!password_generate(fctx->params->server_password, &params, scheme,
                               &result.credentials.data,
                               &result.credentials.size)) {
@@ -623,6 +636,14 @@ static void fuzz_sasl_run(struct istream *input)
        winbind_set.helper_path = TEST_WINBIND_HELPER_PATH;
        sasl_server_mech_register_winbind_ntlm(server_inst, &winbind_set);
 
+#ifdef HAVE_GSSAPI
+       struct sasl_server_gssapi_settings gssapi_set;
+
+       i_zero(&gssapi_set);
+       gssapi_set.hostname = "localhost";
+       sasl_server_mech_register_gssapi(server_inst, &gssapi_set);
+#endif
+
        const struct sasl_server_mech *server_mech;
 
        server_mech = sasl_server_mech_find(server_inst, params.mech);
@@ -634,6 +655,13 @@ 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) {
+               gss_dummy_add_principal(params.authid);
+               gss_dummy_kinit(params.authid);
+       }
+#endif
+
        const struct dsasl_client_mech *client_mech;
        struct fuzz_sasl_context fctx;
 
@@ -680,6 +708,9 @@ static void fuzz_sasl_run(struct istream *input)
        }
 
        pool_unref(&fctx.pool);
+#ifdef HAVE_GSSAPI
+       gss_dummy_deinit();
+#endif
 }
 
 FUZZ_BEGIN_DATA(const unsigned char *data, size_t size)
@@ -690,6 +721,9 @@ FUZZ_BEGIN_DATA(const unsigned char *data, size_t size)
        password_schemes_init();
        dsasl_clients_init();
        dsasl_client_mech_ntlm_init_dummy();
+#ifdef HAVE_GSSAPI
+       dsasl_clients_init_gssapi();
+#endif
 
        struct istream *input = i_stream_create_from_data(data, size);
        struct ioloop *ioloop = io_loop_create();
diff --git a/src/lib-sasl/gssapi-dummy.c b/src/lib-sasl/gssapi-dummy.c
new file mode 100644 (file)
index 0000000..85663ca
--- /dev/null
@@ -0,0 +1,514 @@
+/* Copyright (c) 2023 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "auth-gssapi.h"
+#include "gssapi-dummy.h"
+
+#define GSS_KRB5_DUMMY_FAIL_CODE 1
+
+struct gss_name_struct {
+       char *name;
+};
+
+struct gss_cred_id_struct {
+       pool_t pool;
+       const char *desired_name;
+};
+
+struct gss_ctx_id_struct {
+       pool_t pool;
+
+       const char *src_name;
+       const char *dst_name;
+       OM_uint32 req_flags;
+
+       gss_OID_desc mech_type;
+};
+
+gss_OID GSS_C_NT_HOSTBASED_SERVICE = NULL;
+const gss_OID GSS_KRB5_NT_PRINCIPAL_NAME = NULL;
+
+static void gss_alloc_buffer(gss_buffer_t buffer, size_t length);
+
+char *client_principal = NULL;
+char *server_principal = NULL;
+
+void gss_dummy_kinit(const char *principal)
+{
+       i_free(client_principal);
+       client_principal = i_strdup(principal);
+}
+
+void gss_dummy_add_principal(const char *principal)
+{
+       i_free(server_principal);
+       server_principal = i_strdup(principal);
+}
+
+void gss_dummy_deinit(void)
+{
+       i_free(client_principal);
+       i_free(server_principal);
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_acquire_cred(OM_uint32 *minor_status, gss_name_t desired_name,
+                OM_uint32 time_req, gss_OID_set desired_mechs,
+                gss_cred_usage_t cred_usage, gss_cred_id_t *output_cred_handle,
+                gss_OID_set *actual_mechs, OM_uint32 *time_rec)
+{
+       pool_t pool;
+       gss_cred_id_t cred_handle;
+
+       i_assert(time_req == 0); // Not implemented
+       i_assert(desired_mechs == GSS_C_NULL_OID_SET); // Not implemented
+       i_assert(actual_mechs == NULL); // Not implemented
+       i_assert(time_rec == NULL); // Not implemented
+       i_assert(cred_usage == GSS_C_ACCEPT); // Not implemented
+
+       pool = pool_alloconly_create(MEMPOOL_GROWING"gss_cred_id", 256);
+       cred_handle = p_new(pool, struct gss_cred_id_struct, 1);
+       cred_handle->pool = pool;
+       cred_handle->desired_name = p_strdup(pool, desired_name->name);
+
+       *output_cred_handle = cred_handle;
+       *minor_status = 0;
+       return 0;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_release_cred(OM_uint32 *minor_status, gss_cred_id_t *cred_handle)
+{
+       pool_unref(&(*cred_handle)->pool);
+       *cred_handle = NULL;
+
+       *minor_status = 0;
+       return 0;
+}
+
+static void _encode_uint32(buffer_t *output, OM_uint32 num)
+{
+       buffer_append_c(output, (uint8_t)(num >> 24));
+       buffer_append_c(output, (uint8_t)(num >> 16));
+       buffer_append_c(output, (uint8_t)(num >> 8));
+       buffer_append_c(output, (uint8_t)(num >> 0));
+}
+
+static void _encode_buffer(buffer_t *output, gss_buffer_t buf)
+{
+       _encode_uint32(output, buf->length);
+       buffer_append(output, buf->value, buf->length);
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_init_sec_context(OM_uint32 *minor_status,
+                    gss_cred_id_t claimant_cred_handle,
+                    gss_ctx_id_t *context_handle, gss_name_t target_name,
+                    gss_OID mech_type, OM_uint32 req_flags,
+                    OM_uint32 time_req,
+                    gss_channel_bindings_t input_chan_bindings,
+                    gss_buffer_t input_token ATTR_UNUSED,
+                    gss_OID *actual_mech_type,
+                    gss_buffer_t output_token, OM_uint32 *ret_flags,
+                    OM_uint32 *time_rec)
+{
+       pool_t pool;
+       struct gss_ctx_id_struct *ctx;
+
+       i_assert(claimant_cred_handle == GSS_C_NO_CREDENTIAL); // Not implemented
+       i_assert(time_req == 0); // Not implemented
+       i_assert(time_req == 0); // Not implemented
+       i_assert(ret_flags == NULL); // Not implemented
+       i_assert(time_rec == NULL); // Not implemented
+
+       pool = pool_alloconly_create(MEMPOOL_GROWING"gss_ctx_id", 256);
+       ctx = p_new(pool, struct gss_ctx_id_struct, 1);
+       ctx->pool = pool;
+       ctx->src_name = p_strdup(pool, client_principal);
+       ctx->dst_name = p_strdup(pool, target_name->name);
+       ctx->req_flags = req_flags;
+       ctx->mech_type = *mech_type;
+
+       size_t src_name_len = strlen(ctx->src_name);
+       size_t cbind_len = 0;
+       if (input_chan_bindings != GSS_C_NO_CHANNEL_BINDINGS) {
+               cbind_len = 4 + 4 +
+                       input_chan_bindings->initiator_address.length +
+                       input_chan_bindings->acceptor_address.length +
+                       input_chan_bindings->application_data.length;
+       }
+       buffer_t *output = t_buffer_create(
+               4 + ctx->mech_type.length + 4 + src_name_len + cbind_len);
+       _encode_uint32(output, ctx->mech_type.length);
+       buffer_append(output, ctx->mech_type.elements, ctx->mech_type.length);
+       _encode_uint32(output, src_name_len);
+       buffer_append(output, ctx->src_name, src_name_len);
+       if (input_chan_bindings != GSS_C_NO_CHANNEL_BINDINGS) {
+               _encode_uint32(output, input_chan_bindings->initiator_addrtype);
+               _encode_uint32(output, input_chan_bindings->acceptor_addrtype);
+               _encode_buffer(output, &input_chan_bindings->initiator_address);
+               _encode_buffer(output, &input_chan_bindings->acceptor_address);
+               _encode_buffer(output, &input_chan_bindings->application_data);
+       }
+
+       gss_alloc_buffer(output_token, output->used);
+       memcpy(output_token->value, output->data, output->used);
+
+       *context_handle = ctx;
+       if (actual_mech_type != NULL)
+               *actual_mech_type = &ctx->mech_type;
+
+       *minor_status = 0;
+       return 0;
+}
+
+static int
+_decode_uint32(const unsigned char **_input, size_t *_input_left,
+              OM_uint32 *num_r)
+{
+       const unsigned char *input = *_input;
+       size_t input_left = *_input_left;
+
+       if (input_left < 4)
+               return -1;
+
+       *num_r = (OM_uint32)(input[0] << 24) | (OM_uint32)(input[1] << 16) |
+                (OM_uint32)(input[2] << 8) | (OM_uint32)(input[3] << 0);
+
+       *_input += 4;
+       *_input_left -= 4;
+       return 0;
+}
+
+static int
+_decode_buffer(const unsigned char **_input, size_t *_input_left,
+              gss_buffer_t out)
+{
+       OM_uint32 len;
+
+       if (_decode_uint32(_input, _input_left, &len) < 0)
+               return -1;
+       i_zero(out);
+       if (len > 0) {
+               out->value = t_memdup_noconst(*_input, len);
+               out->length = len;
+               *_input += len;
+               *_input_left -= len;
+       }
+       return 0;
+}
+
+static bool
+_gss_buffer_cmp(gss_buffer_t buf1, gss_buffer_t buf2)
+{
+       if (buf1->length == 0 || buf2->length == 0)
+               return (buf1->length == buf2->length);
+       return (memcmp(buf1->value, buf2->value,
+                      I_MIN(buf1->length, buf2->length)) == 0);
+}
+
+static OM_uint32
+gss_parse_sec_context(struct gss_ctx_id_struct *ctx,
+                     gss_buffer_t input_token_buffer,
+                     gss_channel_bindings_t input_chan_bindings)
+{
+       const unsigned char *input = input_token_buffer->value;
+       size_t input_left = input_token_buffer->length;
+
+       if (_decode_uint32(&input, &input_left, &ctx->mech_type.length) < 0)
+           return GSS_S_BAD_MECH;
+       if (ctx->mech_type.length > input_left)
+           return GSS_S_BAD_MECH;
+       ctx->mech_type.elements = p_malloc(ctx->pool, ctx->mech_type.length);
+       memcpy(ctx->mech_type.elements, input, ctx->mech_type.length);
+       input += ctx->mech_type.length;
+       input_left -= ctx->mech_type.length;
+
+       OM_uint32 src_name_len;
+       if (_decode_uint32(&input, &input_left, &src_name_len) < 0)
+           return GSS_S_BAD_MECH;
+       if (input_left < src_name_len)
+           return GSS_S_BAD_NAME;
+       ctx->src_name = p_strndup(ctx->pool, (const char *)input, src_name_len);
+       input += src_name_len;
+       input_left -= src_name_len;
+
+       if (input_chan_bindings == GSS_C_NO_CHANNEL_BINDINGS) {
+               if (input_left != 0)
+                       return GSS_S_BAD_MECH;
+       } else {
+               OM_uint32 initiator_addrtype;
+               OM_uint32 acceptor_addrtype;
+               gss_buffer_desc initiator_address;
+               gss_buffer_desc acceptor_address;
+               gss_buffer_desc application_data;
+
+               if (_decode_uint32(&input, &input_left,
+                                  &initiator_addrtype) < 0)
+                       return GSS_S_BAD_MECH;
+               if (_decode_uint32(&input, &input_left,
+                                  &acceptor_addrtype) < 0)
+                       return GSS_S_BAD_MECH;
+
+               if (_decode_buffer(&input, &input_left, &initiator_address) < 0)
+                       return GSS_S_BAD_MECH;
+               if (_decode_buffer(&input, &input_left, &acceptor_address) < 0)
+                       return GSS_S_BAD_MECH;
+               if (_decode_buffer(&input, &input_left, &application_data) < 0)
+                       return GSS_S_BAD_MECH;
+
+               if (initiator_addrtype !=
+                   input_chan_bindings->initiator_addrtype)
+                       return GSS_S_BAD_BINDINGS;
+               if (acceptor_addrtype !=
+                   input_chan_bindings->acceptor_addrtype)
+                       return GSS_S_BAD_BINDINGS;
+               if (!_gss_buffer_cmp(&initiator_address,
+                                    &input_chan_bindings->initiator_address))
+                       return GSS_S_BAD_BINDINGS;
+               if (!_gss_buffer_cmp(&acceptor_address,
+                                    &input_chan_bindings->acceptor_address))
+                       return GSS_S_BAD_BINDINGS;
+               if (!_gss_buffer_cmp(&application_data,
+                                    &input_chan_bindings->application_data))
+                       return GSS_S_BAD_BINDINGS;
+       }
+       return 0;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_accept_sec_context(OM_uint32 *minor_status, gss_ctx_id_t *context_handle,
+                      gss_cred_id_t acceptor_cred_handle,
+                      gss_buffer_t input_token_buffer,
+                      gss_channel_bindings_t input_chan_bindings,
+                      gss_name_t *src_name, gss_OID *mech_type,
+                      gss_buffer_t output_token, OM_uint32 *ret_flags,
+                      OM_uint32 *time_rec,
+                      gss_cred_id_t *delegated_cred_handle)
+{
+       pool_t pool;
+       struct gss_ctx_id_struct *ctx;
+       OM_uint32 major_status;
+
+       *minor_status = 0;
+
+       i_assert(ret_flags == NULL);
+       i_assert(time_rec == NULL);
+       i_assert(delegated_cred_handle == NULL);
+
+       pool = pool_alloconly_create(MEMPOOL_GROWING"gss_ctx_id", 256);
+       ctx = p_new(pool, struct gss_ctx_id_struct, 1);
+       ctx->pool = pool;
+       ctx->dst_name = p_strdup(pool, acceptor_cred_handle->desired_name);
+
+       major_status = gss_parse_sec_context(ctx, input_token_buffer,
+                                            input_chan_bindings);
+       if (major_status != 0) {
+               pool_unref(&pool);
+               return major_status;
+       }
+
+       if (server_principal == NULL ||
+           strcmp(ctx->src_name, server_principal) != 0) {
+               pool_unref(&pool);
+               *minor_status = GSS_KRB5_DUMMY_FAIL_CODE;
+               return GSS_S_NO_CRED;
+       }
+
+       struct gss_name_struct *name = i_new(struct gss_name_struct, 1);
+       name->name = i_strdup(ctx->src_name);
+
+       *context_handle = ctx;
+       *src_name = name;
+       *mech_type = &ctx->mech_type;
+       gss_alloc_buffer(output_token, 0);
+
+       return 0;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_delete_sec_context(OM_uint32 *minor_status, gss_ctx_id_t *context_handle,
+                      gss_buffer_t output_token)
+{
+       struct gss_ctx_id_struct *ctx = *context_handle;
+
+       i_assert(output_token == GSS_C_NO_BUFFER);
+
+       pool_unref(&ctx->pool);
+       *context_handle = NULL;
+
+       *minor_status = 0;
+       return 0;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_wrap(OM_uint32 * minor_status, gss_ctx_id_t context_handle ATTR_UNUSED,
+        int conf_req_flag, gss_qop_t qop_req,
+        gss_buffer_t input_message_buffer, int *conf_state,
+        gss_buffer_t output_message_buffer)
+{
+       i_assert(conf_req_flag == 0);
+       i_assert(qop_req == GSS_C_QOP_DEFAULT);
+       i_assert(conf_state == NULL);
+
+       gss_alloc_buffer(output_message_buffer, input_message_buffer->length);
+       memcpy(output_message_buffer->value, input_message_buffer->value,
+              input_message_buffer->length);
+
+       *minor_status = 0;
+       return 0;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_unwrap(OM_uint32 *minor_status, gss_ctx_id_t context_handle ATTR_UNUSED,
+          gss_buffer_t input_message_buffer,
+          gss_buffer_t output_message_buffer, int *conf_state,
+          gss_qop_t *qop_state ATTR_UNUSED)
+{
+       i_assert(conf_state == NULL);
+
+       gss_alloc_buffer(output_message_buffer, input_message_buffer->length);
+       memcpy(output_message_buffer->value, input_message_buffer->value,
+              input_message_buffer->length);
+
+       *minor_status = 0;
+       return 0;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_display_status(OM_uint32 *minor_status,
+                  OM_uint32 status_value,
+                  int status_type, gss_OID mech_type ATTR_UNUSED,
+                  OM_uint32 *message_context ATTR_UNUSED,
+                  gss_buffer_t status_string)
+{
+       char *error_msg = NULL;
+
+       *minor_status = 0;
+
+       switch (status_type) {
+       case  GSS_C_GSS_CODE:
+               switch (status_value) {
+               case GSS_S_NO_CRED:
+                       error_msg = i_strdup("No valid credentials.");
+                       break;
+               default:
+                       break;
+               }
+               break;
+       case GSS_C_MECH_CODE:
+               switch (status_value) {
+               case GSS_KRB5_DUMMY_FAIL_CODE:
+                       error_msg = i_strdup("Kerberos5 dummy says no.");
+                       break;
+               default:
+                       break;
+               }
+               break;
+       default:
+               break;
+       }
+
+       if (error_msg == NULL) {
+               error_msg = i_strdup_printf("STATUS: %"PRIu32":%"PRIu32,
+                                           status_type, status_value);
+       }
+
+       status_string->value = error_msg;
+       status_string->length = strlen(error_msg);
+
+       return 0;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_compare_name(OM_uint32 *minor_status, gss_name_t name1, gss_name_t name2,
+                int *name_equal)
+{
+       *name_equal = (strcmp(name1->name, name2->name) == 0 ? 1 : 0);
+
+       *minor_status = 0;
+       return 0;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_display_name(OM_uint32 *minor_status, gss_name_t input_name,
+                gss_buffer_t output_name_buffer, gss_OID *output_name_type)
+{
+       size_t input_name_len = strlen(input_name->name);
+
+       gss_alloc_buffer(output_name_buffer, input_name_len);
+       memcpy(output_name_buffer->value, input_name->name, input_name_len);
+       output_name_buffer->length = input_name_len;
+
+       if (output_name_type != NULL)
+               *output_name_type = GSS_KRB5_NT_PRINCIPAL_NAME;
+
+       *minor_status = 0;
+       return 0;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_import_name(OM_uint32 *minor_status, gss_buffer_t input_name_buffer,
+               gss_OID input_name_type ATTR_UNUSED, gss_name_t *output_name)
+{
+       struct gss_name_struct *name;
+
+       name = i_new(struct gss_name_struct, 1);
+       name->name = i_strndup(input_name_buffer->value,
+                              input_name_buffer->length);
+       *output_name = name;
+
+       *minor_status = 0;
+       return GSS_S_COMPLETE;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_release_name(OM_uint32 *minor_status, gss_name_t *input_name)
+{
+       if (*input_name == NULL) {
+               *minor_status = 0;
+               return GSS_S_COMPLETE;
+       }
+
+       i_free((*input_name)->name);
+       i_free((*input_name));
+
+       *minor_status = 0;
+       return GSS_S_COMPLETE;
+}
+
+static void gss_alloc_buffer(gss_buffer_t buffer, size_t length)
+{
+       i_zero(buffer);
+       buffer->length = length;
+       if (length > 0)
+               buffer->value = i_malloc(length);
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_release_buffer(OM_uint32 *minor_status, gss_buffer_t buffer)
+{
+       if (buffer != NULL) {
+               if (buffer->value != NULL)
+                       i_free(buffer->value);
+               i_zero(buffer);
+       }
+
+       *minor_status = 0;
+       return GSS_S_COMPLETE;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_duplicate_name(OM_uint32  *minor_status, const gss_name_t input_name,
+                  gss_name_t * dest_name)
+{
+       struct gss_name_struct *name;
+
+       name = i_new(struct gss_name_struct, 1);
+       name->name = i_strdup(input_name->name);
+       *dest_name = name;
+
+       *minor_status = 0;
+       return GSS_S_COMPLETE;
+}
diff --git a/src/lib-sasl/gssapi-dummy.h b/src/lib-sasl/gssapi-dummy.h
new file mode 100644 (file)
index 0000000..d20e2b9
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef GSSAPI_DUMMY_H
+#define GSSAPI_DUMMY_H
+
+void gss_dummy_kinit(const char *principal);
+void gss_dummy_add_principal(const char *principal);
+
+void gss_dummy_deinit(void);
+
+#endif
diff --git a/src/lib-sasl/krb5-dummy.c b/src/lib-sasl/krb5-dummy.c
new file mode 100644 (file)
index 0000000..dc2a9f8
--- /dev/null
@@ -0,0 +1,61 @@
+#include "lib.h"
+
+#ifndef HAVE___GSS_USEROK
+#  define USE_KRB5_USEROK
+#  include <krb5.h>
+
+#ifdef HAVE_GSSAPI_GSSAPI_KRB5_H
+#  include <gssapi/gssapi_krb5.h>
+#elif defined (HAVE_GSSAPI_KRB5_H)
+#  include <gssapi_krb5.h>
+#else
+#  undef USE_KRB5_USEROK
+#endif
+
+#ifdef USE_KRB5_USEROK
+krb5_error_code KRB5_CALLCONV
+krb5_parse_name(krb5_context context ATTR_UNUSED, const char *name ATTR_UNUSED,
+                krb5_principal *principal_out ATTR_UNUSED)
+{
+       return 0;
+}
+
+void KRB5_CALLCONV
+krb5_free_principal(krb5_context context ATTR_UNUSED,
+                   krb5_principal val ATTR_UNUSED)
+{
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_init_context(krb5_context *context ATTR_UNUSED)
+{
+       return 0;
+}
+
+void KRB5_CALLCONV krb5_free_context(krb5_context context ATTR_UNUSED)
+{
+}
+
+krb5_boolean KRB5_CALLCONV
+krb5_kuserok(krb5_context context ATTR_UNUSED,
+            krb5_principal principal ATTR_UNUSED,
+            const char *luser ATTR_UNUSED)
+{
+       return 0;
+}
+#endif
+
+#ifdef HAVE_GSSKRB5_REGISTER_ACCEPTOR_IDENTITY
+OM_uint32 gsskrb5_register_acceptor_identity(const char *identity ATTR_UNUSED)
+{
+       return 0;
+}
+#elif defined (HAVE_KRB5_GSS_REGISTER_ACCEPTOR_IDENTITY)
+OM_uint32 KRB5_CALLCONV
+krb5_gss_register_acceptor_identity(const char *path ATTR_UNUSED)
+{
+       return 0;
+}
+#endif
+
+#endif
index 054424eea960ae70c2a88435079db81a91af1734..96bfbc3ae5dbb38b7a2a54cce1d8123b426a9d32 100644 (file)
@@ -7,8 +7,10 @@
 #include "randgen.h"
 #include "test-common.h"
 #include "password-scheme.h"
+#include "gssapi-dummy.h"
 #include "sasl-server.h"
 #include "sasl-server-oauth2.h"
+#include "sasl-server-gssapi.h"
 #include "dsasl-client.h"
 #include "dsasl-client-mech-ntlm-dummy.h"
 
@@ -206,6 +208,15 @@ test_server_request_lookup_credentials(
 
        i_zero(&result);
 
+#ifdef HAVE_GSSAPI
+       if (strcmp(tctx->test->mech, SASL_MECH_NAME_GSSAPI) == 0) {
+               i_assert(*scheme == '\0');
+               result.status = SASL_PASSDB_RESULT_OK;
+               callback(&tctx->ssrctx, &result);
+               return;
+       }
+#endif
+
        if (null_strcmp(test->server.authid, tctx->authid) != 0 ||
            null_strcmp(test->server.authzid, tctx->authzid) != 0) {
                e_debug(test_event, "User unknown");
@@ -512,11 +523,27 @@ test_sasl_run(const struct test_sasl *test, const char *label,
        };
        sasl_server_mech_register_winbind_ntlm(server_inst, &winbind_set);
 
+#ifdef HAVE_GSSAPI
+       struct sasl_server_gssapi_settings gssapi_set;
+
+       i_zero(&gssapi_set);
+       gssapi_set.hostname = "localhost";
+       sasl_server_mech_register_gssapi(server_inst, &gssapi_set);
+#endif
+
        const struct sasl_server_mech *server_mech;
 
        server_mech = sasl_server_mech_find(server_inst, test->mech);
        i_assert(server_mech != NULL);
 
+#ifdef HAVE_GSSAPI
+       if (strcmp(test->mech, SASL_MECH_NAME_GSSAPI) == 0) {
+               gss_dummy_add_principal(test->server.authid);
+               gss_dummy_kinit(test->client.authid != NULL ?
+                               test->client.authid : test->server.authid);
+       }
+#endif
+
        struct test_sasl_passdb passdb;
        unsigned int repeat = (test->repeat > 0 ? test->repeat : 1);
 
@@ -722,6 +749,17 @@ static const struct test_sasl success_tests[] = {
                        .password = "",
                },
        },
+#ifdef HAVE_GSSAPI
+       /* GSSAPI */
+       {
+               .mech = "GSSAPI",
+               .authid_type = SASL_SERVER_AUTHID_TYPE_USERNAME,
+               .server = {
+                       .authid = "user",
+                       .password = "",
+               },
+       },
+#endif
        /* NTLM */
        {
                .mech = "NTLM",
@@ -1447,6 +1485,21 @@ static const struct test_sasl bad_creds_tests[] = {
                },
                .failure = TRUE,
        },
+#ifdef HAVE_GSSAPI
+       /* GSSAPI */
+       {
+               .mech = "GSSAPI",
+               .authid_type = SASL_SERVER_AUTHID_TYPE_USERNAME,
+               .server = {
+                       .authid = "user",
+                       .password = "",
+               },
+               .client = {
+                       .authid = "userb",
+               },
+               .failure = TRUE,
+       },
+#endif
        /* NTLM */
        {
                .mech = "NTLM",
@@ -1529,6 +1582,9 @@ int main(int argc, char *argv[])
        password_schemes_init();
        dsasl_clients_init();
        dsasl_client_mech_ntlm_init_dummy();
+#ifdef HAVE_GSSAPI
+       dsasl_clients_init_gssapi();
+#endif
 
        ret = test_run(test_functions);