summaryrefslogtreecommitdiffstats
path: root/src/auth/mech-gssapi.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/auth/mech-gssapi.c')
-rw-r--r--src/auth/mech-gssapi.c790
1 files changed, 790 insertions, 0 deletions
diff --git a/src/auth/mech-gssapi.c b/src/auth/mech-gssapi.c
new file mode 100644
index 0000000..e9f2af2
--- /dev/null
+++ b/src/auth/mech-gssapi.c
@@ -0,0 +1,790 @@
+/*
+ * GSSAPI Module
+ *
+ * Copyright (c) 2005 Jelmer Vernooij <jelmer@samba.org>
+ *
+ * Related standards:
+ * - draft-ietf-sasl-gssapi-03
+ * - RFC2222
+ *
+ * Some parts inspired by an older patch from Colin Walters
+ *
+ * This software is released under the MIT license.
+ */
+
+#include "auth-common.h"
+#include "env-util.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "hex-binary.h"
+#include "safe-memset.h"
+#include "mech.h"
+#include "passdb.h"
+
+
+#if defined(BUILTIN_GSSAPI) || defined(PLUGIN_BUILD)
+
+#ifndef HAVE___GSS_USEROK
+# define USE_KRB5_USEROK
+# include <krb5.h>
+#endif
+
+#ifdef HAVE_GSSAPI_GSSAPI_H
+# include <gssapi/gssapi.h>
+#elif defined (HAVE_GSSAPI_H)
+# include <gssapi.h>
+#endif
+
+#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 HAVE_GSSAPI_GSSAPI_EXT_H
+# include <gssapi/gssapi_ext.h>
+#endif
+
+#define krb5_boolean2bool(X) ((X) != 0)
+
+/* 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
+};
+
+struct gssapi_auth_request {
+ struct auth_request auth_request;
+ gss_ctx_id_t gss_ctx;
+ gss_cred_id_t service_cred;
+
+ enum {
+ GSS_STATE_SEC_CONTEXT,
+ GSS_STATE_WRAP,
+ GSS_STATE_UNWRAP
+ } sasl_gssapi_state;
+
+ gss_name_t authn_name;
+ gss_name_t authz_name;
+
+ pool_t pool;
+};
+
+static bool gssapi_initialized = FALSE;
+
+static gss_OID_desc mech_gssapi_krb5_oid =
+ { 9, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
+
+static int
+mech_gssapi_wrap(struct gssapi_auth_request *request, gss_buffer_desc inbuf);
+
+static void mech_gssapi_log_error(struct auth_request *request,
+ OM_uint32 status_value, int status_type,
+ const char *description)
+{
+ OM_uint32 message_context = 0;
+ OM_uint32 minor_status;
+ gss_buffer_desc status_string;
+
+ do {
+ (void)gss_display_status(&minor_status, status_value,
+ status_type, GSS_C_NO_OID,
+ &message_context, &status_string);
+
+ e_info(request->mech_event,
+ "While %s: %s", description,
+ str_sanitize(status_string.value, SIZE_MAX));
+
+ (void)gss_release_buffer(&minor_status, &status_string);
+ } while (message_context != 0);
+}
+
+static void mech_gssapi_initialize(const struct auth_settings *set)
+{
+ const char *path = set->krb5_keytab;
+
+ if (*path != '\0') {
+ /* environment may be used by Kerberos 5 library directly */
+ env_put("KRB5_KTNAME", path);
+#ifdef HAVE_GSSKRB5_REGISTER_ACCEPTOR_IDENTITY
+ gsskrb5_register_acceptor_identity(path);
+#elif defined (HAVE_KRB5_GSS_REGISTER_ACCEPTOR_IDENTITY)
+ krb5_gss_register_acceptor_identity(path);
+#endif
+ }
+}
+
+static struct auth_request *mech_gssapi_auth_new(void)
+{
+ struct gssapi_auth_request *request;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"gssapi_auth_request", 2048);
+ request = p_new(pool, struct gssapi_auth_request, 1);
+ request->pool = pool;
+
+ request->gss_ctx = GSS_C_NO_CONTEXT;
+
+ request->auth_request.pool = pool;
+ return &request->auth_request;
+}
+
+static OM_uint32
+obtain_service_credentials(struct auth_request *request, gss_cred_id_t *ret_r)
+{
+ OM_uint32 major_status, minor_status;
+ string_t *principal_name;
+ gss_buffer_desc inbuf;
+ gss_name_t gss_principal;
+ const char *service_name;
+
+ if (!gssapi_initialized) {
+ gssapi_initialized = TRUE;
+ mech_gssapi_initialize(request->set);
+ }
+
+ if (strcmp(request->set->gssapi_hostname, "$ALL") == 0) {
+ e_debug(request->mech_event,
+ "Using all keytab entries");
+ *ret_r = GSS_C_NO_CREDENTIAL;
+ return GSS_S_COMPLETE;
+ }
+
+ if (strcasecmp(request->fields.service, "POP3") == 0) {
+ /* The standard POP3 service name with GSSAPI is called
+ just "pop". */
+ service_name = "pop";
+ } else {
+ service_name = t_str_lcase(request->fields.service);
+ }
+
+ principal_name = t_str_new(128);
+ str_append(principal_name, service_name);
+ str_append_c(principal_name, '@');
+ str_append(principal_name, request->set->gssapi_hostname);
+
+ e_debug(request->mech_event,
+ "Obtaining credentials for %s", str_c(principal_name));
+
+ inbuf.length = str_len(principal_name);
+ inbuf.value = str_c_modifiable(principal_name);
+
+ major_status = gss_import_name(&minor_status, &inbuf,
+ GSS_C_NT_HOSTBASED_SERVICE,
+ &gss_principal);
+ str_free(&principal_name);
+
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(request, major_status, GSS_C_GSS_CODE,
+ "importing principal name");
+ return major_status;
+ }
+
+ major_status = gss_acquire_cred(&minor_status, gss_principal, 0,
+ GSS_C_NULL_OID_SET, GSS_C_ACCEPT,
+ ret_r, NULL, NULL);
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(request, major_status, GSS_C_GSS_CODE,
+ "acquiring service credentials");
+ mech_gssapi_log_error(request, minor_status, GSS_C_MECH_CODE,
+ "acquiring service credentials");
+ return major_status;
+ }
+
+ gss_release_name(&minor_status, &gss_principal);
+ return major_status;
+}
+
+static gss_name_t
+import_name(struct auth_request *request, void *str, size_t len)
+{
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc name_buf;
+ gss_name_t name;
+
+ name_buf.value = str;
+ name_buf.length = len;
+ major_status = gss_import_name(&minor_status, &name_buf,
+ GSS_C_NO_OID, &name);
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(request, major_status, GSS_C_GSS_CODE,
+ "gss_import_name");
+ return GSS_C_NO_NAME;
+ }
+ return name;
+}
+
+static gss_name_t
+duplicate_name(struct auth_request *request, gss_name_t old)
+{
+ OM_uint32 major_status, minor_status;
+ gss_name_t new;
+
+ major_status = gss_duplicate_name(&minor_status, old, &new);
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(request, major_status, GSS_C_GSS_CODE,
+ "gss_duplicate_name");
+ return GSS_C_NO_NAME;
+ }
+ return new;
+}
+
+static bool data_has_nuls(const void *data, size_t len)
+{
+ const unsigned char *c = data;
+ size_t i;
+
+ /* apparently all names end with NUL? */
+ if (len > 0 && c[len-1] == '\0')
+ len--;
+
+ for (i = 0; i < len; i++) {
+ if (c[i] == '\0')
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int get_display_name(struct auth_request *auth_request, gss_name_t name,
+ gss_OID *name_type_r, const char **display_name_r)
+{
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc buf;
+
+ major_status = gss_display_name(&minor_status, name,
+ &buf, name_type_r);
+ if (major_status != GSS_S_COMPLETE) {
+ mech_gssapi_log_error(auth_request, major_status,
+ GSS_C_GSS_CODE, "gss_display_name");
+ return -1;
+ }
+ if (data_has_nuls(buf.value, buf.length)) {
+ e_info(auth_request->mech_event,
+ "authn_name has NULs");
+ return -1;
+ }
+ *display_name_r = t_strndup(buf.value, buf.length);
+ (void)gss_release_buffer(&minor_status, &buf);
+ return 0;
+}
+
+static bool mech_gssapi_oid_cmp(const gss_OID_desc *oid1,
+ const gss_OID_desc *oid2)
+{
+ return oid1->length == oid2->length &&
+ mem_equals_timing_safe(oid1->elements, oid2->elements, oid1->length);
+}
+
+static int
+mech_gssapi_sec_context(struct gssapi_auth_request *request,
+ gss_buffer_desc inbuf)
+{
+ struct auth_request *auth_request = &request->auth_request;
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc output_token;
+ gss_OID name_type;
+ gss_OID mech_type;
+ const char *username, *error;
+ int ret = 0;
+
+ major_status = gss_accept_sec_context (
+ &minor_status,
+ &request->gss_ctx,
+ request->service_cred,
+ &inbuf,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &request->authn_name,
+ &mech_type,
+ &output_token,
+ NULL, /* ret_flags */
+ NULL, /* time_rec */
+ NULL /* delegated_cred_handle */
+ );
+
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(auth_request, major_status,
+ GSS_C_GSS_CODE,
+ "processing incoming data");
+ mech_gssapi_log_error(auth_request, minor_status,
+ GSS_C_MECH_CODE,
+ "processing incoming data");
+ return -1;
+ }
+
+ switch (major_status) {
+ case GSS_S_COMPLETE:
+ if (!mech_gssapi_oid_cmp(mech_type, &mech_gssapi_krb5_oid)) {
+ e_info(auth_request->mech_event,
+ "GSSAPI mechanism not Kerberos5");
+ ret = -1;
+ } else if (get_display_name(auth_request, request->authn_name,
+ &name_type, &username) < 0)
+ ret = -1;
+ else if (!auth_request_set_username(auth_request, username,
+ &error)) {
+ e_info(auth_request->mech_event,
+ "authn_name: %s", error);
+ ret = -1;
+ } else {
+ request->sasl_gssapi_state = GSS_STATE_WRAP;
+ e_debug(auth_request->mech_event,
+ "security context state completed.");
+ }
+ break;
+ case GSS_S_CONTINUE_NEEDED:
+ e_debug(auth_request->mech_event,
+ "Processed incoming packet correctly, "
+ "waiting for another.");
+ break;
+ default:
+ e_error(auth_request->mech_event,
+ "Received unexpected major status %d", major_status);
+ break;
+ }
+
+ if (ret == 0) {
+ if (output_token.length > 0) {
+ auth_request_handler_reply_continue(auth_request,
+ output_token.value,
+ output_token.length);
+ } else {
+ /* If there is no output token, go straight to wrap,
+ which is expecting an empty input token. */
+ ret = mech_gssapi_wrap(request, output_token);
+ }
+ }
+ (void)gss_release_buffer(&minor_status, &output_token);
+ return ret;
+}
+
+static int
+mech_gssapi_wrap(struct gssapi_auth_request *request, gss_buffer_desc inbuf)
+{
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc outbuf;
+ unsigned char ret[4];
+
+ /* The client's return data should be empty here */
+
+ /* Only authentication, no integrity or confidentiality
+ protection (yet?) */
+ ret[0] = (SASL_GSSAPI_QOP_UNSPECIFIED |
+ SASL_GSSAPI_QOP_AUTH_ONLY);
+ ret[1] = 0xFF;
+ ret[2] = 0xFF;
+ ret[3] = 0xFF;
+
+ inbuf.length = 4;
+ inbuf.value = ret;
+
+ major_status = gss_wrap(&minor_status, request->gss_ctx, 0,
+ GSS_C_QOP_DEFAULT, &inbuf, NULL, &outbuf);
+
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(&request->auth_request, major_status,
+ GSS_C_GSS_CODE, "sending security layer negotiation");
+ mech_gssapi_log_error(&request->auth_request, minor_status,
+ GSS_C_MECH_CODE, "sending security layer negotiation");
+ return -1;
+ }
+
+ e_debug(request->auth_request.mech_event,
+ "Negotiated security layer");
+
+ auth_request_handler_reply_continue(&request->auth_request,
+ outbuf.value, outbuf.length);
+
+ (void)gss_release_buffer(&minor_status, &outbuf);
+ request->sasl_gssapi_state = GSS_STATE_UNWRAP;
+ return 0;
+}
+
+#ifdef USE_KRB5_USEROK
+static bool
+k5_principal_is_authorized(struct auth_request *request, const char *name)
+{
+ const char *value, *const *authorized_names, *const *tmp;
+
+ value = auth_fields_find(request->fields.extra_fields, "k5principals");
+ if (value == NULL)
+ return FALSE;
+
+ authorized_names = t_strsplit_spaces(value, ",");
+ for (tmp = authorized_names; *tmp != NULL; tmp++) {
+ if (strcmp(*tmp, name) == 0) {
+ e_debug(request->mech_event,
+ "authorized by k5principals field: %s", name);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static bool
+mech_gssapi_krb5_userok(struct gssapi_auth_request *request,
+ gss_name_t name, const char *login_user,
+ bool check_name_type)
+{
+ krb5_context ctx;
+ krb5_principal princ;
+ krb5_error_code krb5_err;
+ gss_OID name_type;
+ const char *princ_display_name;
+ bool authorized = FALSE;
+
+ /* Parse out the principal's username */
+ if (get_display_name(&request->auth_request, name, &name_type,
+ &princ_display_name) < 0)
+ return FALSE;
+
+ if (!mech_gssapi_oid_cmp(name_type, GSS_KRB5_NT_PRINCIPAL_NAME) &&
+ check_name_type) {
+ e_info(request->auth_request.mech_event,
+ "OID not kerberos principal name");
+ return FALSE;
+ }
+
+ /* Init a krb5 context and parse the principal username */
+ krb5_err = krb5_init_context(&ctx);
+ if (krb5_err != 0) {
+ e_error(request->auth_request.mech_event,
+ "krb5_init_context() failed: %d", (int)krb5_err);
+ return FALSE;
+ }
+ krb5_err = krb5_parse_name(ctx, princ_display_name, &princ);
+ if (krb5_err != 0) {
+ /* writing the error string would be better, but we probably
+ rarely get here and there doesn't seem to be a standard
+ way of getting it */
+ e_info(request->auth_request.mech_event,
+ "krb5_parse_name() failed: %d",
+ (int)krb5_err);
+ } else {
+ /* See if the principal is in the list of authorized
+ * principals for the user */
+ authorized = k5_principal_is_authorized(&request->auth_request,
+ princ_display_name);
+
+ /* See if the principal is authorized to act as the
+ specified (UNIX) user */
+ if (!authorized) {
+ authorized = krb5_boolean2bool(krb5_kuserok(ctx, princ, login_user));
+ }
+
+ krb5_free_principal(ctx, princ);
+ }
+ krb5_free_context(ctx);
+ return authorized;
+}
+#endif
+
+static int
+mech_gssapi_userok(struct gssapi_auth_request *request, const char *login_user)
+{
+ struct auth_request *auth_request = &request->auth_request;
+ OM_uint32 major_status, minor_status;
+ int equal_authn_authz;
+#ifdef HAVE___GSS_USEROK
+ int login_ok;
+#endif
+
+ /* if authn and authz names equal, don't bother checking further. */
+ major_status = gss_compare_name(&minor_status,
+ request->authn_name,
+ request->authz_name,
+ &equal_authn_authz);
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(auth_request, major_status,
+ GSS_C_GSS_CODE,
+ "gss_compare_name failed");
+ return -1;
+ }
+
+ if (equal_authn_authz != 0)
+ return 0;
+
+ /* handle cross-realm authentication */
+#ifdef HAVE___GSS_USEROK
+ /* Solaris */
+ major_status = __gss_userok(&minor_status, request->authn_name,
+ login_user, &login_ok);
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(auth_request, major_status,
+ GSS_C_GSS_CODE, "__gss_userok failed");
+ return -1;
+ }
+
+ if (login_ok == 0) {
+ e_info(auth_request->mech_event,
+ "User not authorized to log in as %s", login_user);
+ return -1;
+ }
+ return 0;
+#elif defined(USE_KRB5_USEROK)
+ if (!mech_gssapi_krb5_userok(request, request->authn_name,
+ login_user, TRUE)) {
+ e_info(auth_request->mech_event,
+ "User not authorized to log in as %s", login_user);
+ return -1;
+ }
+
+ return 0;
+#else
+ e_info(auth_request->mech_event,
+ "Cross-realm authentication not supported "
+ "(authn_name=%s, authz_name=%s)",
+ request->auth_request.fields.original_username, login_user);
+ return -1;
+#endif
+}
+
+static void
+gssapi_credentials_callback(enum passdb_result result,
+ const unsigned char *credentials ATTR_UNUSED,
+ size_t size ATTR_UNUSED,
+ struct auth_request *request)
+{
+ struct gssapi_auth_request *gssapi_request =
+ (struct gssapi_auth_request *)request;
+
+ /* We don't care much whether the lookup succeeded or not because GSSAPI
+ * does not strictly require a passdb. But if a passdb is configured,
+ * now the k5principals field will have been filled in. */
+ switch (result) {
+ case PASSDB_RESULT_INTERNAL_FAILURE:
+ auth_request_internal_failure(request);
+ return;
+ case PASSDB_RESULT_USER_DISABLED:
+ case PASSDB_RESULT_PASS_EXPIRED:
+ /* user is explicitly disabled, don't allow it to log in */
+ auth_request_fail(request);
+ return;
+ case PASSDB_RESULT_NEXT:
+ case PASSDB_RESULT_SCHEME_NOT_AVAILABLE:
+ case PASSDB_RESULT_USER_UNKNOWN:
+ case PASSDB_RESULT_PASSWORD_MISMATCH:
+ case PASSDB_RESULT_OK:
+ break;
+ }
+
+ if (mech_gssapi_userok(gssapi_request, request->fields.user) == 0)
+ auth_request_success(request, NULL, 0);
+ else
+ auth_request_fail(request);
+}
+
+static int
+mech_gssapi_unwrap(struct gssapi_auth_request *request, gss_buffer_desc inbuf)
+{
+ struct auth_request *auth_request = &request->auth_request;
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc outbuf;
+ const char *login_user, *error;
+ unsigned char *name;
+ size_t name_len;
+
+ major_status = gss_unwrap(&minor_status, request->gss_ctx,
+ &inbuf, &outbuf, NULL, NULL);
+
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(auth_request, major_status,
+ GSS_C_GSS_CODE,
+ "final negotiation: gss_unwrap");
+ return -1;
+ }
+
+ /* outbuf[0] contains bitmask for selected security layer,
+ outbuf[1..3] contains maximum output_message size */
+ if (outbuf.length < 4) {
+ e_error(auth_request->mech_event,
+ "Invalid response length");
+ return -1;
+ }
+
+ if (outbuf.length > 4) {
+ name = (unsigned char *)outbuf.value + 4;
+ name_len = outbuf.length - 4;
+
+ if (data_has_nuls(name, name_len)) {
+ e_info(auth_request->mech_event,
+ "authz_name has NULs");
+ return -1;
+ }
+
+ login_user = p_strndup(auth_request->pool, name, name_len);
+ request->authz_name = import_name(auth_request, name, name_len);
+ } else {
+ request->authz_name = duplicate_name(auth_request,
+ request->authn_name);
+ if (get_display_name(auth_request, request->authz_name,
+ NULL, &login_user) < 0)
+ return -1;
+ }
+
+ if (request->authz_name == GSS_C_NO_NAME) {
+ e_info(auth_request->mech_event,
+ "no authz_name");
+ return -1;
+ }
+
+ /* Set username early, so that the credential lookup is for the
+ * authorizing user. This means the username in subsequent log
+ * messages will be the authorization name, not the authentication
+ * name, which may mean that future log messages should be adjusted
+ * to log the right thing. */
+ if (!auth_request_set_username(auth_request, login_user, &error)) {
+ e_info(auth_request->mech_event,
+ "authz_name: %s", error);
+ return -1;
+ }
+
+ /* Continue in callback once auth_request is populated with passdb
+ information. */
+ auth_request->passdb_success = TRUE; /* default to success */
+ auth_request_lookup_credentials(&request->auth_request, "",
+ gssapi_credentials_callback);
+ return 0;
+}
+
+static void
+mech_gssapi_auth_continue(struct auth_request *request,
+ const unsigned char *data, size_t data_size)
+{
+ struct gssapi_auth_request *gssapi_request =
+ (struct gssapi_auth_request *)request;
+ gss_buffer_desc inbuf;
+ int ret = -1;
+
+ inbuf.value = (void *)data;
+ inbuf.length = data_size;
+
+ switch (gssapi_request->sasl_gssapi_state) {
+ case GSS_STATE_SEC_CONTEXT:
+ ret = mech_gssapi_sec_context(gssapi_request, inbuf);
+ break;
+ case GSS_STATE_WRAP:
+ ret = mech_gssapi_wrap(gssapi_request, inbuf);
+ break;
+ case GSS_STATE_UNWRAP:
+ ret = mech_gssapi_unwrap(gssapi_request, inbuf);
+ break;
+ default:
+ i_unreached();
+ }
+ if (ret < 0)
+ auth_request_fail(request);
+}
+
+static void
+mech_gssapi_auth_initial(struct auth_request *request,
+ const unsigned char *data, size_t data_size)
+{
+ struct gssapi_auth_request *gssapi_request =
+ (struct gssapi_auth_request *)request;
+ OM_uint32 major_status;
+
+ major_status =
+ obtain_service_credentials(request,
+ &gssapi_request->service_cred);
+
+ if (GSS_ERROR(major_status) != 0) {
+ auth_request_internal_failure(request);
+ return;
+ }
+ gssapi_request->authn_name = GSS_C_NO_NAME;
+ gssapi_request->authz_name = GSS_C_NO_NAME;
+
+ gssapi_request->sasl_gssapi_state = GSS_STATE_SEC_CONTEXT;
+
+ if (data_size == 0) {
+ /* The client should go first */
+ auth_request_handler_reply_continue(request, NULL, 0);
+ } else {
+ mech_gssapi_auth_continue(request, data, data_size);
+ }
+}
+
+static void
+mech_gssapi_auth_free(struct auth_request *request)
+{
+ struct gssapi_auth_request *gssapi_request =
+ (struct gssapi_auth_request *)request;
+ OM_uint32 minor_status;
+
+ if (gssapi_request->gss_ctx != GSS_C_NO_CONTEXT) {
+ (void)gss_delete_sec_context(&minor_status,
+ &gssapi_request->gss_ctx,
+ GSS_C_NO_BUFFER);
+ }
+
+ (void)gss_release_cred(&minor_status, &gssapi_request->service_cred);
+ if (gssapi_request->authn_name != GSS_C_NO_NAME) {
+ (void)gss_release_name(&minor_status,
+ &gssapi_request->authn_name);
+ }
+ if (gssapi_request->authz_name != GSS_C_NO_NAME) {
+ (void)gss_release_name(&minor_status,
+ &gssapi_request->authz_name);
+ }
+ pool_unref(&request->pool);
+}
+
+const struct mech_module mech_gssapi = {
+ "GSSAPI",
+
+ .flags = MECH_SEC_ALLOW_NULS,
+ .passdb_need = MECH_PASSDB_NEED_NOTHING,
+
+ mech_gssapi_auth_new,
+ mech_gssapi_auth_initial,
+ mech_gssapi_auth_continue,
+ mech_gssapi_auth_free
+};
+
+/* MTI Kerberos v1.5+ and Heimdal v0.7+ supports SPNEGO for Kerberos tickets
+ internally. Nothing else needs to be done here. Note however that this does
+ not support SPNEGO when the only available credential is NTLM.. */
+const struct mech_module mech_gssapi_spnego = {
+ "GSS-SPNEGO",
+
+ .flags = MECH_SEC_ALLOW_NULS,
+ .passdb_need = MECH_PASSDB_NEED_NOTHING,
+
+ mech_gssapi_auth_new,
+ mech_gssapi_auth_initial,
+ mech_gssapi_auth_continue,
+ mech_gssapi_auth_free
+};
+
+#ifndef BUILTIN_GSSAPI
+void mech_gssapi_init(void);
+void mech_gssapi_deinit(void);
+
+void mech_gssapi_init(void)
+{
+ mech_register_module(&mech_gssapi);
+#ifdef HAVE_GSSAPI_SPNEGO
+ /* load if we already didn't load it using winbind */
+ if (mech_module_find(mech_gssapi_spnego.mech_name) == NULL)
+ mech_register_module(&mech_gssapi_spnego);
+#endif
+}
+
+void mech_gssapi_deinit(void)
+{
+#ifdef HAVE_GSSAPI_SPNEGO
+ const struct mech_module *mech;
+
+ mech = mech_module_find(mech_gssapi_spnego.mech_name);
+ if (mech != NULL && mech->auth_new == mech_gssapi_auth_new)
+ mech_unregister_module(&mech_gssapi_spnego);
+#endif
+ mech_unregister_module(&mech_gssapi);
+}
+#endif
+
+#endif