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)
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 \
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
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 \
--- /dev/null
+/* 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);
+}
void dsasl_clients_init(void);
void dsasl_clients_deinit(void);
+void dsasl_clients_init_gssapi(void);
+
#endif
#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"
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, ¶ms, scheme,
&result.credentials.data,
&result.credentials.size)) {
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);
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;
}
pool_unref(&fctx.pool);
+#ifdef HAVE_GSSAPI
+ gss_dummy_deinit();
+#endif
}
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();
--- /dev/null
+/* 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;
+}
--- /dev/null
+#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
--- /dev/null
+#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
#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"
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");
};
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);
.password = "",
},
},
+#ifdef HAVE_GSSAPI
+ /* GSSAPI */
+ {
+ .mech = "GSSAPI",
+ .authid_type = SASL_SERVER_AUTHID_TYPE_USERNAME,
+ .server = {
+ .authid = "user",
+ .password = "",
+ },
+ },
+#endif
/* NTLM */
{
.mech = "NTLM",
},
.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",
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);