Add GS2 mech code
authorLuke Howard <lukeh@padl.com>
Sun, 26 Sep 2010 22:46:41 +0000 (00:46 +0200)
committerLuke Howard <lukeh@padl.com>
Sun, 26 Sep 2010 22:46:41 +0000 (00:46 +0200)
plugins/gs2.c [new file with mode: 0644]

diff --git a/plugins/gs2.c b/plugins/gs2.c
new file mode 100644 (file)
index 0000000..cee634f
--- /dev/null
@@ -0,0 +1,1705 @@
+/*
+ * Copyright 2010 PADL Software Pty Ltd. All rights reserved.
+ */
+/*
+ * Portions Copyright (c) 1998-2003 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The name "Carnegie Mellon University" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For permission or any other legal
+ *    details, please contact
+ *      Office of Technology Transfer
+ *      Carnegie Mellon University
+ *      5000 Forbes Avenue
+ *      Pittsburgh, PA  15213-3890
+ *      (412) 268-4387, fax: (412) 268-7395
+ *      tech-transfer@andrew.cmu.edu
+ *
+ * 4. Redistributions of any form whatsoever must retain the following
+ *    acknowledgment:
+ *    "This product includes software developed by Computing Services
+ *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
+ *
+ * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
+ * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_ext.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sasl.h>
+#include <saslutil.h>
+#include <saslplug.h>
+
+#include "plugin_common.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <errno.h>
+#include <assert.h>
+#include "gs2_token.h"
+
+#define GS2_CB_FLAG_MASK    0x0F
+#define GS2_CB_FLAG_P       SASL_CB_FLAG_USED
+#define GS2_CB_FLAG_N       SASL_CB_FLAG_NONE
+#define GS2_CB_FLAG_Y       SASL_CB_FLAG_WANT
+#define GS2_NONSTD_FLAG     0x10
+
+typedef struct context {
+    gss_ctx_id_t gss_ctx;
+    gss_name_t client_name;
+    gss_name_t server_name;
+    gss_cred_id_t server_creds;
+    gss_cred_id_t client_creds;
+    char *out_buf;
+    unsigned out_buf_len;
+    const sasl_utils_t *utils;
+    char *authid;
+    char *authzid;
+    union {
+        sasl_client_plug_t *client;
+        sasl_server_plug_t *server;
+    } plug;
+    gss_OID mechanism;
+    int gs2_flags;
+    char *cb_name;
+    struct gss_channel_bindings_struct bindings;
+    sasl_secret_t *password;
+    unsigned int free_password;
+    OM_uint32 lifetime;
+} context_t;
+
+static gss_OID_set gs2_mechs = GSS_C_NO_OID_SET;
+
+static int gs2_ask_user_info(context_t *context,
+                             sasl_client_params_t *params,
+                             char **realms, int nrealm,
+                             sasl_interact_t **prompt_need,
+                             sasl_out_params_t *oparams);
+
+static int gs2_verify_initial_message(context_t *text,
+                                      sasl_server_params_t *sparams,
+                                      const char *in,
+                                      unsigned inlen,
+                                      gss_buffer_t token);
+
+static int gs2_make_header(context_t *text,
+                           sasl_client_params_t *cparams,
+                           const char *authzid,
+                           char **out,
+                           unsigned *outlen);
+
+static int gs2_make_message(context_t *text,
+                            sasl_client_params_t *cparams,
+                            int initialContextToken,
+                            gss_buffer_t token,
+                            char **out,
+                            unsigned *outlen);
+
+static int gs2_get_mech_attrs(const sasl_utils_t *utils,
+                              const gss_OID mech,
+                              unsigned int *security_flags,
+                              unsigned int *features);
+
+static int gs2_indicate_mechs(const sasl_utils_t *utils);
+
+static int gs2_map_sasl_name(const sasl_utils_t *utils,
+                             const char *mech,
+                             gss_OID *oid);
+
+static int gs2_duplicate_buffer(const sasl_utils_t *utils,
+                                const gss_buffer_t src,
+                                gss_buffer_t dst);
+
+static int gs2_unescape_authzid(const sasl_utils_t *utils,
+                                char **in,
+                                unsigned *inlen,
+                                char **authzid);
+
+static int gs2_escape_authzid(const sasl_utils_t *utils,
+                              const char *in,
+                              unsigned inlen,
+                              char **authzid);
+
+/* sasl_gs_log: only logs status string returned from gss_display_status() */
+#define sasl_gs2_log(x,y,z) sasl_gs2_seterror_(x,y,z,1)
+#define sasl_gs2_seterror(x,y,z) sasl_gs2_seterror_(x,y,z,0)
+
+static int
+sasl_gs2_seterror_(const sasl_utils_t *utils, OM_uint32 maj, OM_uint32 min,
+                   int logonly);
+
+static context_t *
+sasl_gs2_new_context(const sasl_utils_t *utils)
+{
+    context_t *ret;
+
+    ret = utils->malloc(sizeof(context_t));
+    if (!ret)
+        return NULL;
+
+    memset(ret, 0, sizeof(context_t));
+    ret->utils = utils;
+
+    return ret;
+}
+
+static int
+sasl_gs2_free_context_contents(context_t *text)
+{
+    OM_uint32 min_stat;
+
+    if (text == NULL)
+        return SASL_OK;
+
+    if (text->gss_ctx != GSS_C_NO_CONTEXT) {
+        gss_delete_sec_context(&min_stat,&text->gss_ctx,
+                               GSS_C_NO_BUFFER);
+        text->gss_ctx = GSS_C_NO_CONTEXT;
+    }
+
+    if (text->client_name != GSS_C_NO_NAME) {
+        gss_release_name(&min_stat,&text->client_name);
+        text->client_name = GSS_C_NO_NAME;
+    }
+
+    if (text->server_name != GSS_C_NO_NAME) {
+        gss_release_name(&min_stat,&text->server_name);
+        text->server_name = GSS_C_NO_NAME;
+    }
+
+    if (text->server_creds != GSS_C_NO_CREDENTIAL) {
+        gss_release_cred(&min_stat, &text->server_creds);
+        text->server_creds = GSS_C_NO_CREDENTIAL;
+    }
+
+    if (text->client_creds != GSS_C_NO_CREDENTIAL) {
+        gss_release_cred(&min_stat, &text->client_creds);
+        text->client_creds = GSS_C_NO_CREDENTIAL;
+    }
+
+    if (text->authid != NULL) {
+        text->utils->free(text->authid);
+        text->authid = NULL;
+    }
+
+    if (text->authzid != NULL) {
+        text->utils->free(text->authzid);
+        text->authzid = NULL;
+    }
+
+    if (text->mechanism != NULL) {
+        gss_release_oid(&min_stat, &text->mechanism);
+        text->mechanism = GSS_C_NO_OID;
+    }
+
+    gss_release_buffer(&min_stat, &text->bindings.application_data);
+
+    if (text->out_buf != NULL) {
+        text->utils->free(text->out_buf);
+        text->out_buf = NULL;
+    }
+
+    text->out_buf_len = 0;
+
+    if (text->cb_name != NULL) {
+        text->utils->free(text->cb_name);
+        text->cb_name = NULL;
+    }
+
+    if (text->free_password)
+        _plug_free_secret(text->utils, &text->password);
+
+    memset(text, 0, sizeof(*text));
+
+    return SASL_OK;
+}
+
+static void
+gs2_common_mech_dispose(void *conn_context, const sasl_utils_t *utils)
+{
+    sasl_gs2_free_context_contents((context_t *)(conn_context));
+    utils->free(conn_context);
+}
+
+static void
+gs2_common_mech_free(void *global_context __attribute__((unused)),
+                     const sasl_utils_t *utils)
+{
+    OM_uint32 minor;
+
+    if (gs2_mechs != GSS_C_NO_OID_SET) {
+        gss_release_oid_set(&minor, &gs2_mechs);
+        gs2_mechs = GSS_C_NO_OID_SET;
+    }
+}
+
+/*****************************  Server Section  *****************************/
+
+static int
+gs2_server_mech_new(void *glob_context,
+                    sasl_server_params_t *params,
+                    const char *challenge __attribute__((unused)),
+                    unsigned challen __attribute__((unused)),
+                    void **conn_context)
+{
+    context_t *text;
+    int ret;
+
+    text = sasl_gs2_new_context(params->utils);
+    if (text == NULL) {
+        MEMERROR(params->utils);
+        return SASL_NOMEM;
+    }
+
+    text->gss_ctx = GSS_C_NO_CONTEXT;
+    text->client_name = GSS_C_NO_NAME;
+    text->server_name = GSS_C_NO_NAME;
+    text->server_creds = GSS_C_NO_CREDENTIAL;
+    text->client_creds = GSS_C_NO_CREDENTIAL;
+    text->plug.server = glob_context;
+
+    ret = gs2_map_sasl_name(params->utils, text->plug.server->mech_name,
+                            &text->mechanism);
+    if (ret != SASL_OK) {
+        gs2_common_mech_dispose(text, params->utils);
+        return ret;
+    }
+
+    *conn_context = text;
+
+    return SASL_OK;
+}
+
+static int
+gs2_server_mech_step(void *conn_context,
+                     sasl_server_params_t *params,
+                     const char *clientin,
+                     unsigned clientinlen,
+                     const char **serverout,
+                     unsigned *serveroutlen,
+                     sasl_out_params_t *oparams)
+{
+    context_t *text = (context_t *)conn_context;
+    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
+    gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+    OM_uint32 maj_stat = GSS_S_FAILURE, min_stat = 0;
+    gss_buffer_desc name_buf = GSS_C_EMPTY_BUFFER;
+    gss_buffer_desc short_name_buf = GSS_C_EMPTY_BUFFER;
+    gss_name_t without = GSS_C_NO_NAME;
+    gss_OID_set_desc mechs;
+    gss_OID actual_mech = GSS_C_NO_OID;
+    OM_uint32 out_flags = 0;
+    int ret = 0, equal = 0;
+    int initialContextToken = (text->gss_ctx == GSS_C_NO_CONTEXT);
+    char *p;
+
+    if (serverout == NULL) {
+        PARAMERROR(text->utils);
+        return SASL_BADPARAM;
+    }
+
+    *serverout = NULL;
+    *serveroutlen = 0;
+
+    if (initialContextToken) {
+        name_buf.length = strlen(params->service) + 1 + strlen(params->serverFQDN);
+        name_buf.value = params->utils->malloc(name_buf.length + 1);
+        if (name_buf.value == NULL) {
+            MEMERROR(text->utils);
+            ret = SASL_NOMEM;
+            goto cleanup;
+        }
+        snprintf(name_buf.value, name_buf.length + 1,
+                 "%s@%s", params->service, params->serverFQDN);
+        maj_stat = gss_import_name(&min_stat,
+                                   &name_buf,
+                                   GSS_C_NT_HOSTBASED_SERVICE,
+                                   &text->server_name);
+        params->utils->free(name_buf.value);
+        name_buf.value = NULL;
+
+        if (GSS_ERROR(maj_stat))
+            goto cleanup;
+
+        assert(text->server_creds == GSS_C_NO_CREDENTIAL);
+
+        mechs.count = 1;
+        mechs.elements = (gss_OID)text->mechanism;
+
+        if (params->gss_creds == GSS_C_NO_CREDENTIAL) {
+            maj_stat = gss_acquire_cred(&min_stat,
+                                        text->server_name,
+                                        GSS_C_INDEFINITE,
+                                        &mechs,
+                                        GSS_C_ACCEPT,
+                                        &text->server_creds,
+                                        NULL,
+                                        &text->lifetime);
+            if (GSS_ERROR(maj_stat))
+                goto cleanup;
+        }
+
+        ret = gs2_verify_initial_message(text,
+                                         params,
+                                         clientin,
+                                         clientinlen,
+                                         &input_token);
+        if (ret != SASL_OK)
+            goto cleanup;
+
+        if ((text->gs2_flags & GS2_CB_FLAG_MASK) == GS2_CB_FLAG_N) {
+            if (params->chanbindingcrit != 0)
+                ret = SASL_BADAUTH;
+        } else if ((text->gs2_flags & GS2_CB_FLAG_MASK) == GS2_CB_FLAG_Y) {
+            if (SASL_CB_PRESENT(params))
+                ret = SASL_BADAUTH;
+        }
+        if (ret != SASL_OK)
+            goto cleanup;
+    } else {
+        input_token.value = (void *)clientin;
+        input_token.length = clientinlen;
+    }
+
+    maj_stat = gss_accept_sec_context(&min_stat,
+                                      &text->gss_ctx,
+                                      (params->gss_creds != GSS_C_NO_CREDENTIAL)
+                                        ? params->gss_creds
+                                        : text->server_creds,
+                                      &input_token,
+                                      &text->bindings,
+                                      &text->client_name,
+                                      &actual_mech,
+                                      &output_token,
+                                      &out_flags,
+                                      &text->lifetime,
+                                      &text->client_creds);
+    if (GSS_ERROR(maj_stat)) {
+        sasl_gs2_log(text->utils, maj_stat, min_stat);
+        text->utils->seterror(text->utils->conn, SASL_NOLOG,
+                              "GS2 Failure: gss_accept_sec_context");
+        ret = SASL_BADAUTH;
+        goto cleanup;
+    }
+
+    *serveroutlen = output_token.length;
+    if (output_token.value != NULL) {
+        ret = _plug_buf_alloc(text->utils, &text->out_buf,
+                              &text->out_buf_len, *serveroutlen);
+        if (ret != SASL_OK)
+            goto cleanup;
+        memcpy(text->out_buf, output_token.value, *serveroutlen);
+        *serverout = text->out_buf;
+    } else {
+        /* No output token, send an empty string */
+        *serverout = "";
+        serveroutlen = 0;
+    }
+
+    if (maj_stat == GSS_S_CONTINUE_NEEDED) {
+        ret = SASL_CONTINUE;
+        goto cleanup;
+    }
+
+    assert(maj_stat == GSS_S_COMPLETE);
+
+    if (!g_OID_equal(text->mechanism, actual_mech)) {
+        ret = SASL_WRONGMECH;
+        goto cleanup;
+    }
+    if ((out_flags & GSS_C_SEQUENCE_FLAG) == 0)  {
+        ret = SASL_BADAUTH;
+        goto cleanup;
+    }
+
+    maj_stat = gss_display_name(&min_stat, text->client_name,
+                                &name_buf, NULL);
+    if (GSS_ERROR(maj_stat))
+        goto cleanup;
+
+    ret = gs2_duplicate_buffer(params->utils, &name_buf, &short_name_buf);
+    if (ret != 0)
+        goto cleanup;
+
+    p = (char *)memchr(name_buf.value, '@', name_buf.length);
+    if (p != NULL) {
+        short_name_buf.length = (p - (char *)name_buf.value);
+
+        maj_stat = gss_import_name(&min_stat,
+                                   &short_name_buf,
+                                   GSS_C_NT_USER_NAME,
+                                   &without);
+        if (GSS_ERROR(maj_stat)) {
+            ret = SASL_BADAUTH;
+            goto cleanup;
+        }
+
+        maj_stat = gss_compare_name(&min_stat, text->client_name,
+                                    without, &equal);
+        if (GSS_ERROR(maj_stat)) {
+            ret = SASL_BADAUTH;
+            goto cleanup;
+        }
+
+        if (equal)
+            ((char *)short_name_buf.value)[short_name_buf.length] = '\0';
+    }
+
+    text->authid = (char *)short_name_buf.value;
+    short_name_buf.value = NULL;
+    short_name_buf.length = 0;
+
+    if (text->authzid != NULL) {
+        ret = params->canon_user(params->utils->conn,
+                                 text->authzid, 0,
+                                 SASL_CU_AUTHZID, oparams);
+        if (ret != SASL_OK)
+            goto cleanup;
+    }
+
+    ret = params->canon_user(params->utils->conn,
+                             text->authid, 0,
+                             text->authzid == NULL
+                                ? (SASL_CU_AUTHZID | SASL_CU_AUTHID)
+                                : SASL_CU_AUTHID,
+                             oparams);
+    if (ret != SASL_OK)
+        goto cleanup;
+
+    if (text->client_creds != GSS_C_NO_CREDENTIAL)
+        oparams->client_creds = &text->client_creds;
+    else
+        oparams->client_creds = NULL;
+
+    oparams->gss_peer_name = text->client_name;
+    oparams->gss_local_name = text->server_name;
+    oparams->maxoutbuf = 0xFFFFFF;
+    oparams->encode = NULL;
+    oparams->decode = NULL;
+    oparams->mech_ssf = 0;
+    oparams->doneflag = 1;
+
+    ret = SASL_OK;
+
+cleanup:
+    if (initialContextToken)
+        gss_release_buffer(&min_stat, &input_token);
+    gss_release_buffer(&min_stat, &name_buf);
+    gss_release_buffer(&min_stat, &short_name_buf);
+    gss_release_buffer(&min_stat, &output_token);
+    gss_release_name(&min_stat, &without);
+    gss_release_oid(&min_stat, &actual_mech);
+
+    if (ret == SASL_OK && maj_stat != GSS_S_COMPLETE) {
+        sasl_gs2_seterror(text->utils, maj_stat, min_stat);
+        ret = SASL_FAIL;
+    }
+    if (ret != SASL_OK && ret != SASL_CONTINUE)
+        sasl_gs2_free_context_contents(text);
+
+    return ret;
+}
+
+static int
+gs2_common_plug_init(const sasl_utils_t *utils,
+                     size_t plugsize,
+                     int (*plug_alloc)(const sasl_utils_t *,
+                                       void *,
+                                       const gss_buffer_t,
+                                       const gss_OID),
+                     void **pluglist,
+                     int *plugcount)
+{
+    OM_uint32 major, minor;
+    size_t i, count = 0;
+    void *plugs = NULL;
+
+    *pluglist = NULL;
+    *plugcount = 0;
+
+    if (gs2_indicate_mechs(utils) != SASL_OK) {
+        return SASL_NOMECH;
+    }
+
+    plugs = utils->malloc(2 * gs2_mechs->count * plugsize);
+    if (plugs == NULL) {
+        MEMERROR(utils);
+        return SASL_NOMEM;
+    }
+    memset(plugs, 0, 2 * gs2_mechs->count * plugsize);
+
+    for (i = 0; i < gs2_mechs->count; i++) {
+        gss_buffer_desc sasl_mech_name = GSS_C_EMPTY_BUFFER;
+
+        major = gss_inquire_saslname_for_mech(&minor,
+                                              &gs2_mechs->elements[i],
+                                              &sasl_mech_name,
+                                              GSS_C_NO_BUFFER,
+                                              GSS_C_NO_BUFFER);
+        if (GSS_ERROR(major))
+            continue;
+
+#define PLUG_AT(index)      (void *)((unsigned char *)plugs + (count * plugsize))
+
+        if (plug_alloc(utils, PLUG_AT(count), &sasl_mech_name,
+                       &gs2_mechs->elements[i]) == SASL_OK)
+            count++;
+
+        gss_release_buffer(&minor, &sasl_mech_name);
+    }
+
+    if (count == 0) {
+        utils->free(plugs);
+        return SASL_NOMECH;
+    }
+
+    *pluglist = plugs;
+    *plugcount = count;
+
+    return SASL_OK;
+}
+
+static int
+gs2_server_plug_alloc(const sasl_utils_t *utils,
+                      void *plug,
+                      gss_buffer_t sasl_name,
+                      gss_OID mech)
+{
+    int ret;
+    sasl_server_plug_t *splug = (sasl_server_plug_t *)plug;
+    gss_buffer_desc buf;
+
+    memset(splug, 0, sizeof(*splug));
+
+    ret = gs2_get_mech_attrs(utils, mech,
+                             &splug->security_flags,
+                             &splug->features);
+    if (ret != SASL_OK)
+        return ret;
+
+    ret = gs2_duplicate_buffer(utils, sasl_name, &buf);
+    if (ret != SASL_OK)
+        return ret;
+
+    splug->mech_name = (char *)buf.value;
+    splug->glob_context = plug;
+    splug->mech_new = gs2_server_mech_new;
+    splug->mech_step = gs2_server_mech_step;
+    splug->mech_dispose = gs2_common_mech_dispose;
+    splug->mech_free = gs2_common_mech_free;
+
+    return SASL_OK;
+}
+
+static sasl_server_plug_t *gs2_server_plugins;
+static int gs2_server_plugcount;
+
+int
+gs2_server_plug_init(const sasl_utils_t *utils,
+                     int maxversion,
+                     int *outversion,
+                     sasl_server_plug_t **pluglist,
+                     int *plugcount)
+{
+    int ret;
+
+    *pluglist = NULL;
+    *plugcount = 0;
+
+    if (maxversion < SASL_SERVER_PLUG_VERSION)
+        return SASL_BADVERS;
+
+    *outversion = SASL_SERVER_PLUG_VERSION;
+
+    if (gs2_server_plugins == NULL) {
+        ret = gs2_common_plug_init(utils,
+                                   sizeof(sasl_server_plug_t),
+                                   gs2_server_plug_alloc,
+                                   (void **)&gs2_server_plugins,
+                                   &gs2_server_plugcount);
+        if (ret != SASL_OK)
+            return ret;
+    }
+
+    *pluglist = gs2_server_plugins;
+    *plugcount = gs2_server_plugcount;
+
+    return SASL_OK;
+}
+
+/*****************************  Client Section  *****************************/
+
+static int gs2_client_mech_step(void *conn_context,
+                                sasl_client_params_t *params,
+                                const char *serverin,
+                                unsigned serverinlen,
+                                sasl_interact_t **prompt_need,
+                                const char **clientout,
+                                unsigned *clientoutlen,
+                                sasl_out_params_t *oparams)
+{
+    context_t *text = (context_t *)conn_context;
+    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
+    gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+    gss_buffer_desc name_buf = GSS_C_EMPTY_BUFFER;
+    OM_uint32 maj_stat = GSS_S_FAILURE, min_stat = 0;
+    OM_uint32 req_flags, ret_flags;
+    gss_OID actual_mech = GSS_C_NO_OID;
+    int ret = SASL_FAIL;
+    int initialContextToken;
+
+    *clientout = NULL;
+    *clientoutlen = 0;
+
+    if (text->gss_ctx == GSS_C_NO_CONTEXT) {
+        ret = gs2_ask_user_info(text, params, NULL, 0, prompt_need, oparams);
+        if (ret != SASL_OK)
+            goto cleanup;
+
+        if (params->gss_creds == GSS_C_NO_CREDENTIAL && 
+            text->password != NULL && text->password->len != 0) {
+            gss_buffer_desc password_buf;
+            gss_buffer_desc name_buf;
+            gss_OID_set_desc mechs;
+
+            name_buf.length = strlen(oparams->authid);
+            name_buf.value = (void *)oparams->authid;
+
+            password_buf.length = text->password->len;
+            password_buf.value = text->password->data;
+
+            mechs.count = 1;
+            mechs.elements = (gss_OID)text->mechanism;
+
+            maj_stat = gss_import_name(&min_stat,
+                                       &name_buf,
+                                       GSS_C_NT_USER_NAME,
+                                       &text->client_name);
+            if (GSS_ERROR(maj_stat))
+                goto cleanup;
+
+            maj_stat = gss_acquire_cred_with_password(&min_stat,
+                                                      text->client_name,
+                                                      &password_buf,
+                                                      GSS_C_INDEFINITE,
+                                                      &mechs,
+                                                      GSS_C_INITIATE,
+                                                      &text->client_creds,
+                                                      NULL,
+                                                      &text->lifetime);
+            if (GSS_ERROR(maj_stat))
+                goto cleanup;
+        }
+
+        initialContextToken = 1;
+    } else
+        initialContextToken = 0;
+
+    if (text->server_name == GSS_C_NO_NAME) { /* only once */
+        name_buf.length = strlen(params->service) + 1 + strlen(params->serverFQDN);
+        name_buf.value = params->utils->malloc(name_buf.length + 1);
+        if (name_buf.value == NULL) {
+            ret = SASL_NOMEM;
+            goto cleanup;
+        }
+        if (params->serverFQDN == NULL ||
+            strlen(params->serverFQDN) == 0) {
+            SETERROR(text->utils, "GS2 Failure: no serverFQDN");
+            ret = SASL_FAIL;
+            goto cleanup;
+        }
+
+        snprintf(name_buf.value, name_buf.length + 1,
+                 "%s@%s", params->service, params->serverFQDN);
+
+        maj_stat = gss_import_name (&min_stat,
+                                    &name_buf,
+                                    GSS_C_NT_HOSTBASED_SERVICE,
+                                    &text->server_name);
+        params->utils->free(name_buf.value);
+        name_buf.value = NULL;
+
+        if (GSS_ERROR(maj_stat))
+            goto cleanup;
+    }
+
+    /* From GSSAPI plugin: apparently this is for some IMAP bug workaround */
+    if (serverinlen == 0 && text->gss_ctx != GSS_C_NO_CONTEXT) {
+        gss_delete_sec_context(&min_stat, &text->gss_ctx, GSS_C_NO_BUFFER);
+        text->gss_ctx = GSS_C_NO_CONTEXT;
+    }
+
+    input_token.value = (void *)serverin;
+    input_token.length = serverinlen;
+
+    if (initialContextToken) {
+        if ((text->plug.client->features & SASL_FEAT_GSS_FRAMING) == 0)
+            text->gs2_flags |= GS2_NONSTD_FLAG;
+
+        switch (params->chanbindingflag) {
+        case SASL_CB_FLAG_NONE:
+            text->gs2_flags |= GS2_CB_FLAG_N;
+            break;
+        case SASL_CB_FLAG_USED:
+            text->gs2_flags |= GS2_CB_FLAG_P;
+            break;
+        case SASL_CB_FLAG_WANT:
+            text->gs2_flags |= GS2_CB_FLAG_Y;
+            break;
+        }
+
+        ret = gs2_make_header(text, params,
+                              strcmp(oparams->user, oparams->authid) ?
+                                     (char *) oparams->user : NULL,
+                              &text->out_buf, &text->out_buf_len);
+        if (ret != 0)
+            goto cleanup;
+    }
+
+    req_flags = GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG;
+
+    maj_stat = gss_init_sec_context(&min_stat,
+                                    (params->gss_creds != GSS_C_NO_CREDENTIAL)
+                                        ? params->gss_creds
+                                        : text->client_creds,
+                                    &text->gss_ctx,
+                                    text->server_name,
+                                    (gss_OID)text->mechanism,
+                                    req_flags,
+                                    GSS_C_INDEFINITE,
+                                    &text->bindings,
+                                    serverinlen ? &input_token : GSS_C_NO_BUFFER,
+                                    NULL,
+                                    &output_token,
+                                    &ret_flags,
+                                    &text->lifetime);
+    if (GSS_ERROR(maj_stat))
+        goto cleanup;
+
+    ret = gs2_make_message(text, params, initialContextToken, &output_token,
+                           &text->out_buf, &text->out_buf_len);
+    if (ret != 0)
+        goto cleanup;
+
+    *clientout = text->out_buf;
+    *clientoutlen = text->out_buf_len;
+
+    if (maj_stat == GSS_S_CONTINUE_NEEDED) {
+        ret = SASL_CONTINUE;
+        goto cleanup;
+    }
+
+    if (text->client_name != GSS_C_NO_NAME)
+        gss_release_name(&min_stat, &text->client_name);
+
+    maj_stat = gss_inquire_context(&min_stat,
+                                   text->gss_ctx,
+                                   &text->client_name,
+                                   NULL,
+                                   &text->lifetime,
+                                   &actual_mech,
+                                   &ret_flags, /* flags */
+                                   NULL,
+                                   NULL);
+    if (GSS_ERROR(maj_stat))
+        goto cleanup;
+
+    if (!g_OID_equal(text->mechanism, actual_mech)) {
+        ret = SASL_WRONGMECH;
+        goto cleanup;
+    }
+    if ((ret_flags & req_flags) != req_flags) {
+        maj_stat = SASL_BADAUTH;
+        goto cleanup;
+    }
+
+    maj_stat = gss_display_name(&min_stat,
+                                text->client_name,
+                                &name_buf,
+                                NULL);
+    if (GSS_ERROR(maj_stat))
+        goto cleanup;
+
+    oparams->gss_peer_name = text->server_name;
+    oparams->gss_local_name = text->client_name;
+    oparams->encode = NULL;
+    oparams->decode = NULL;
+    oparams->mech_ssf = 0;
+    oparams->maxoutbuf = 0xFFFFFF;
+    oparams->doneflag = 1;
+
+cleanup:
+    gss_release_buffer(&min_stat, &output_token);
+    gss_release_buffer(&min_stat, &name_buf);
+    gss_release_oid(&min_stat, &actual_mech);
+
+    if (ret == SASL_OK && maj_stat != GSS_S_COMPLETE) {
+        sasl_gs2_seterror(text->utils, maj_stat, min_stat);
+        ret = SASL_FAIL;
+    }
+    if (ret != SASL_OK && ret != SASL_CONTINUE)
+        sasl_gs2_free_context_contents(text);
+
+    return ret;
+}
+
+static int gs2_client_mech_new(void *glob_context,
+                               sasl_client_params_t *params,
+                               void **conn_context)
+{
+    context_t *text;
+    int ret;
+
+    text = sasl_gs2_new_context(params->utils);
+    if (text == NULL) {
+        MEMERROR(params->utils);
+        return SASL_NOMEM;
+    }
+
+    text->gss_ctx = GSS_C_NO_CONTEXT;
+    text->client_name = GSS_C_NO_NAME;
+    text->server_creds = GSS_C_NO_CREDENTIAL;
+    text->client_creds  = GSS_C_NO_CREDENTIAL;
+    text->plug.client = glob_context;
+
+    ret = gs2_map_sasl_name(params->utils, text->plug.client->mech_name,
+                            &text->mechanism);
+    if (ret != SASL_OK) {
+        gs2_common_mech_dispose(text, params->utils);
+        return ret;
+    } 
+
+    *conn_context = text;
+
+    return SASL_OK;
+}
+
+static const unsigned long gs2_required_prompts[] = {
+    SASL_CB_LIST_END
+};
+
+static int
+gs2_client_plug_alloc(const sasl_utils_t *utils,
+                      void *plug,
+                      gss_buffer_t sasl_name,
+                      gss_OID mech)
+{
+    int ret;
+    sasl_client_plug_t *cplug = (sasl_client_plug_t *)plug;
+    gss_buffer_desc buf;
+
+    memset(cplug, 0, sizeof(*cplug));
+
+    ret = gs2_get_mech_attrs(utils, mech,
+                             &cplug->security_flags,
+                             &cplug->features);
+    if (ret != SASL_OK)
+        return ret;
+
+    ret = gs2_duplicate_buffer(utils, sasl_name, &buf);
+    if (ret != SASL_OK)
+        return ret;
+
+    cplug->mech_name = (char *)buf.value;
+    cplug->features |= SASL_FEAT_NEEDSERVERFQDN;
+    cplug->glob_context = plug;
+    cplug->mech_new = gs2_client_mech_new;
+    cplug->mech_step = gs2_client_mech_step;
+    cplug->mech_dispose = gs2_common_mech_dispose;
+    cplug->mech_free = gs2_common_mech_free;
+    cplug->required_prompts = gs2_required_prompts;
+
+    return SASL_OK;
+}
+
+static sasl_client_plug_t *gs2_client_plugins;
+static int gs2_client_plugcount;
+
+int
+gs2_client_plug_init(const sasl_utils_t *utils,
+                     int maxversion,
+                     int *outversion,
+                     sasl_client_plug_t **pluglist,
+                     int *plugcount)
+{
+    int ret;
+
+    *pluglist = NULL;
+    *plugcount = 0;
+
+    if (maxversion < SASL_CLIENT_PLUG_VERSION)
+        return SASL_BADVERS;
+
+    *outversion = SASL_CLIENT_PLUG_VERSION;
+
+    if (gs2_client_plugins == NULL) {
+        ret = gs2_common_plug_init(utils,
+                                   sizeof(sasl_client_plug_t),
+                                   gs2_client_plug_alloc,
+                                   (void **)&gs2_client_plugins,
+                                   &gs2_client_plugcount);
+        if (ret != SASL_OK)
+            return ret;
+    }
+
+    *pluglist = gs2_client_plugins;
+    *plugcount = gs2_client_plugcount;
+
+    return SASL_OK;
+}
+
+/*
+ * Copy header and application channel bindings to GSS channel bindings
+ * structure in context.
+ */
+static int
+gs2_save_cbindings(context_t *text,
+                   gss_buffer_t header,
+                   const char *chanbindingdata,
+                   unsigned int chanbindinglen)
+{
+    gss_buffer_t gss_bindings = &text->bindings.application_data;
+    size_t len;
+    unsigned char *p;
+
+    assert(gss_bindings->value == NULL);
+
+    /*
+     * The application-data field MUST be set to the gs2-header, excluding
+     * the initial [gs2-nonstd-flag ","] part, concatenated with, when a
+     * gs2-cb-flag of "p" is used, the application's channel binding data.
+     */
+    len = header->length;
+    if (text->gs2_flags & GS2_NONSTD_FLAG) {
+        assert(len > 2);
+        len -= 2;
+    }
+    if ((text->gs2_flags & GS2_CB_FLAG_MASK) == GS2_CB_FLAG_P)
+        len += chanbindinglen;
+
+    gss_bindings->length = len;
+    gss_bindings->value = text->utils->malloc(len);
+    if (gss_bindings->value == NULL)
+        return SASL_NOMEM;
+
+    p = (unsigned char *)gss_bindings->value;
+    if (text->gs2_flags & GS2_NONSTD_FLAG) {
+        memcpy(p, (unsigned char *)header->value + 2, header->length - 2);
+        p += header->length - 2;
+    } else {
+        memcpy(p, header->value, header->length);
+        p += header->length;
+    }
+
+    if ((text->gs2_flags & GS2_CB_FLAG_MASK) == GS2_CB_FLAG_P &&
+        chanbindinglen != 0) {
+        memcpy(p, chanbindingdata, chanbindinglen);
+    }
+
+    return SASL_OK;
+}
+
+#define CHECK_REMAIN(n)     do { if (remain < (n)) return SASL_BADAUTH; } while (0)
+
+/*
+ * Verify gs2-header, save authzid and channel bindings to context.
+ */
+static int
+gs2_verify_initial_message(context_t *text,
+                           sasl_server_params_t *sparams,
+                           const char *in,
+                           unsigned inlen,
+                           gss_buffer_t token)
+{
+    char *p = (char *)in;
+    unsigned remain = inlen;
+    int ret;
+    gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
+
+    assert(text->cb_name == NULL);
+    assert(text->authzid == NULL);
+
+    token->length = 0;
+    token->value = NULL;
+
+    /* minimum header includes CB flag and non-zero GSS token */
+    CHECK_REMAIN(4); /* [pny],,. */
+
+    /* non-standard GSS framing flag */
+    if (remain > 1 && memcmp(p, "F,", 2) == 0) {
+        text->gs2_flags |= GS2_NONSTD_FLAG;
+        remain -= 2;
+        p += 2;
+    }
+
+    /* SASL channel bindings */
+    CHECK_REMAIN(1); /* [pny] */
+    remain--;
+    switch (*p++) {
+    case 'p':
+        CHECK_REMAIN(1); /* = */
+        remain--;
+        if (*p++ != '=')
+            return SASL_BADAUTH;
+
+        ret = gs2_unescape_authzid(text->utils, &p, &remain, &text->cb_name);
+        if (ret != SASL_OK)
+            return ret;
+
+        text->gs2_flags |= GS2_CB_FLAG_P;
+        break;
+    case 'n':
+        text->gs2_flags |= GS2_CB_FLAG_N;
+        break;
+    case 'y':
+        text->gs2_flags |= GS2_CB_FLAG_Y;
+        break;
+    }
+
+    CHECK_REMAIN(1); /* , */
+    remain--;
+    if (*p++ != ',')
+        return SASL_BADAUTH;
+
+    /* authorization identity */
+    if (remain > 1 && memcmp(p, "a=", 2) == 0) {
+        CHECK_REMAIN(2);
+        remain -= 2;
+        p += 2;
+
+        ret = gs2_unescape_authzid(text->utils, &p, &remain, &text->authzid);
+        if (ret != SASL_OK)
+            return ret;
+    }
+
+    /* end of header */ 
+    CHECK_REMAIN(1); /* , */
+    remain--;
+    if (*p++ != ',')
+        return SASL_BADAUTH;
+
+    buf.length = inlen - remain;
+    buf.value = (void *)in;
+
+    /* stash channel bindings to pass into gss_accept_sec_context() */
+    ret = gs2_save_cbindings(text, &buf, sparams->chanbindingdata,
+                             sparams->chanbindinglen);
+    if (ret != SASL_OK)
+        return ret;
+
+    buf.length = remain;
+    buf.value = p;
+
+    if (text->gs2_flags & GS2_NONSTD_FLAG) {
+        token->value = text->utils->malloc(buf.length);
+        if (token->value == NULL)
+            return SASL_NOMEM;
+
+        token->length = buf.length;
+        memcpy(token->value, buf.value, buf.length);
+    } else {
+        unsigned int token_size;
+
+        /* create a properly formed GSS token */ 
+        token_size = gs2_token_size(text->mechanism, buf.length);
+        token->value = text->utils->malloc(token_size);
+        if (token->value == NULL)
+            return SASL_NOMEM;
+
+        token->length = token_size;
+
+        p = (char *)token->value;
+        gs2_make_token_header(text->mechanism, buf.length,
+                              (unsigned char **)&p);
+        memcpy(p, buf.value, buf.length);
+    }
+
+    return SASL_OK;
+}
+
+/*
+ * Create gs2-header, save channel bindings to context.
+ */
+static int
+gs2_make_header(context_t *text,
+                sasl_client_params_t *cparams,
+                const char *authzid,
+                char **out,
+                unsigned *outlen)
+{
+    size_t required = 0;
+    size_t wire_authzid_len = 0, cb_name_len = 0;
+    char *wire_authzid = NULL;
+    char *p;
+    int ret;
+    gss_buffer_desc buf;
+
+    *out = NULL;
+    *outlen = 0;
+
+    /* non-standard GSS framing flag */
+    if (text->gs2_flags & GS2_NONSTD_FLAG)
+        required += 2; /* F, */
+
+    /* SASL channel bindings */
+    switch (text->gs2_flags & GS2_CB_FLAG_MASK) {
+    case GS2_CB_FLAG_P:
+        if (cparams->chanbindingtype == NULL)
+            return SASL_BADPARAM;
+        cb_name_len = strlen(cparams->chanbindingtype);
+        required += 1 /*=*/ + cb_name_len;
+        /* fallthrough */
+    case GS2_CB_FLAG_N:
+    case GS2_CB_FLAG_Y:
+        required += 2; /* [pny], */
+        break;
+    default:
+        return SASL_BADPARAM;
+    }
+
+    /* authorization identity */
+    if (authzid != NULL) {
+        ret = gs2_escape_authzid(text->utils, authzid,
+                                 strlen(authzid), &wire_authzid);
+        if (ret != SASL_OK)
+            return ret;
+
+        wire_authzid_len = strlen(wire_authzid);
+        required += 2 /* a= */ + wire_authzid_len;
+    }
+
+    required += 1; /* trailing comma */
+
+    ret = _plug_buf_alloc(text->utils, out, outlen, required);
+    if (ret != SASL_OK) {
+        text->utils->free(wire_authzid);
+        return ret;
+    }
+
+    *out = text->out_buf;
+    *outlen = required;
+
+    p = (char *)text->out_buf;
+    if (text->gs2_flags & GS2_NONSTD_FLAG) {
+        *p++ = 'F';
+        *p++ = ',';
+    }
+    switch (text->gs2_flags & GS2_CB_FLAG_MASK) {
+    case GS2_CB_FLAG_P:
+        memcpy(p, "p=", 2);
+        memcpy(p + 2, cparams->chanbindingtype, cb_name_len);
+        p += 2 + cb_name_len;
+        break;
+    case GS2_CB_FLAG_N:
+        *p++ = 'n';
+        break;
+    case GS2_CB_FLAG_Y:
+        *p++ = 'y';
+        break;
+    }
+    *p++ = ',';
+    if (wire_authzid != NULL) {
+        memcpy(p, "a=", 2);
+        memcpy(p + 2, wire_authzid, wire_authzid_len);
+        text->utils->free(wire_authzid);
+        p += 2 + wire_authzid_len;
+    }
+    *p++ = ',';
+
+    assert(p == (char *)text->out_buf + required);
+
+    buf.length = required;
+    buf.value = *out;
+
+    ret = gs2_save_cbindings(text, &buf, cparams->chanbindingdata,
+                             cparams->chanbindinglen);
+
+    return ret;
+}
+
+/*
+ * Convert a GSS token to a GS2 one
+ */
+static int
+gs2_make_message(context_t *text,
+                 sasl_client_params_t *cparams __attribute__((unused)),
+                 int initialContextToken,
+                 gss_buffer_t token,
+                 char **out,
+                 unsigned *outlen)
+{
+    OM_uint32 major, minor;
+    unsigned char *mech_token_data;
+    size_t mech_token_size;
+    char *p;
+    unsigned header_len = 0;
+    int ret;
+
+    mech_token_size = token->length;
+    mech_token_data = (unsigned char *)token->value;
+
+    if (initialContextToken) {
+        header_len = *outlen;
+
+        major = gs2_verify_token_header(&minor, text->mechanism,
+                                        &mech_token_size, &mech_token_data,
+                                        token->length);
+        if ((major == GSS_S_DEFECTIVE_TOKEN &&
+             (text->plug.client->features & SASL_FEAT_GSS_FRAMING)) ||
+            GSS_ERROR(major))
+            return SASL_FAIL;
+    }
+
+    ret = _plug_buf_alloc(text->utils, out, outlen,
+                          header_len + mech_token_size);
+    if (ret != 0)
+        return ret;
+
+    p = *out + header_len;
+    memcpy(p, mech_token_data, mech_token_size);
+
+    *outlen = header_len + mech_token_size;
+
+    return SASL_OK;
+}
+
+/*
+ * Map GSS mechanism attributes to SASL ones
+ */
+static int
+gs2_get_mech_attrs(const sasl_utils_t *utils,
+                   const gss_OID mech,
+                   unsigned int *security_flags,
+                   unsigned int *features)
+{
+    OM_uint32 major, minor;
+    int present, ret;
+    gss_OID_set attrs = GSS_C_NO_OID_SET;
+
+    major = gss_inquire_attrs_for_mech(&minor, mech, &attrs, NULL);
+    if (GSS_ERROR(major)) {
+        utils->seterror(utils->conn, SASL_NOLOG,
+                        "GS2 Failure: gss_inquire_attrs_for_mech");
+        return SASL_FAIL;
+    }
+
+    *security_flags = SASL_SEC_NOPLAINTEXT | SASL_SEC_NOACTIVE;
+    *features = SASL_FEAT_WANT_CLIENT_FIRST | SASL_FEAT_CHANNEL_BINDING;
+
+#define MA_PRESENT(a)   (gss_test_oid_set_member(&minor, (gss_OID)(a), \
+                                                 attrs, &present) == GSS_S_COMPLETE && \
+                         present)
+
+    ret = SASL_OK;
+
+    if (MA_PRESENT(GSS_C_MA_PFS))
+        *security_flags |= SASL_SEC_FORWARD_SECRECY;
+    if (!MA_PRESENT(GSS_C_MA_AUTH_INIT_ANON))
+        *security_flags |= SASL_SEC_NOANONYMOUS;
+    if (MA_PRESENT(GSS_C_MA_DELEG_CRED))
+        *security_flags |= SASL_SEC_PASS_CREDENTIALS;
+    if (MA_PRESENT(GSS_C_MA_AUTH_TARG))
+        *security_flags |= SASL_SEC_MUTUAL_AUTH;
+    if (MA_PRESENT(GSS_C_MA_ITOK_FRAMED))
+        *features |= SASL_FEAT_GSS_FRAMING;
+
+    gss_release_oid_set(&minor, &attrs);
+    return ret;
+}
+
+/*
+ * Enumerate GSS mechanisms that can be used for GS2
+ */
+static int gs2_indicate_mechs(const sasl_utils_t *utils)
+{
+    OM_uint32 major, minor;
+    gss_OID_desc desired_oids[3];
+    gss_OID_set_desc desired_attrs;
+    gss_OID_desc except_oids[3];
+    gss_OID_set_desc except_attrs;
+
+    if (gs2_mechs != GSS_C_NO_OID_SET)
+        return SASL_OK;
+
+    desired_oids[0] = *GSS_C_MA_AUTH_INIT;
+    desired_oids[1] = *GSS_C_MA_AUTH_TARG;
+    desired_oids[2] = *GSS_C_MA_CBINDINGS;
+    desired_attrs.count = sizeof(desired_oids)/sizeof(desired_oids[0]);
+    desired_attrs.elements = desired_oids;
+
+    except_oids[0] = *GSS_C_MA_MECH_NEGO;
+    except_oids[1] = *GSS_C_MA_NOT_MECH;
+    except_oids[2] = *GSS_C_MA_DEPRECATED;
+
+    except_attrs.count = sizeof(except_oids)/sizeof(except_oids[0]);
+    except_attrs.elements = except_oids;
+
+    major = gss_indicate_mechs_by_attrs(&minor,
+                                        &desired_attrs,
+                                        &except_attrs,
+                                        GSS_C_NO_OID_SET,
+                                        &gs2_mechs);
+    if (GSS_ERROR(major)) {
+        utils->seterror(utils->conn, SASL_NOLOG,
+                        "GS2 Failure: gss_indicate_mechs_by_attrs");
+        return SASL_FAIL;
+    }
+
+    return (gs2_mechs->count > 0) ? SASL_OK : SASL_NOMECH;
+}
+
+/*
+ * Map SASL mechanism name to OID
+ */
+static int
+gs2_map_sasl_name(const sasl_utils_t *utils,
+                  const char *mech,
+                  gss_OID *oid)
+{
+    OM_uint32 major, minor;
+    gss_buffer_desc buf;
+
+    buf.length = strlen(mech);
+    buf.value = (void *)mech;
+
+    major = gss_inquire_mech_for_saslname(&minor, &buf, oid);
+    if (GSS_ERROR(major)) {
+        utils->seterror(utils->conn, SASL_NOLOG,
+                        "GS2 Failure: gss_inquire_mech_for_saslname");
+        return SASL_FAIL;
+    }
+
+    return SASL_OK;
+}
+
+static int
+gs2_duplicate_buffer(const sasl_utils_t *utils,
+                     const gss_buffer_t src,
+                     gss_buffer_t dst)
+{
+    dst->value = utils->malloc(src->length + 1);
+    if (dst->value == NULL)
+        return SASL_NOMEM;
+
+    memcpy(dst->value, src->value, src->length);
+    ((char *)dst->value)[src->length] = '\0';
+    dst->length = src->length;
+
+    return SASL_OK;
+}
+
+static int
+gs2_unescape_authzid(const sasl_utils_t *utils,
+                     char **endp,
+                     unsigned *remain,
+                     char **authzid)
+{
+    char *in = *endp;
+    size_t i, len, inlen = *remain;
+    char *p;
+
+    *endp = NULL;
+
+    for (i = 0, len = 0; i < inlen; i++) {
+        if (in[i] == ',') {
+            *endp = &in[i];
+            *remain -= i;
+            break;
+        } else if (in[i] == '=') {
+            if (inlen <= i + 2)
+                return SASL_BADPROT;
+            i += 2;
+        }
+        len++;
+    }
+
+    if (len == 0 || *endp == NULL)
+        return SASL_BADPROT;
+
+    p = *authzid = utils->malloc(len + 1);
+    if (*authzid == NULL)
+        return SASL_NOMEM;
+
+    for (i = 0; i < inlen; i++) {
+        if (in[i] == ',')
+            break;
+        else if (in[i] == '=') {
+            if (memcmp(&in[i + 1], "2C", 2) == 0)
+                *p++ = ',';
+            else if (memcmp(&in[i + 1], "3D", 2) == 0)
+                *p++ = '=';
+            else {
+                utils->free(*authzid);
+                *authzid = NULL;
+                return SASL_BADPROT;
+            }
+            i += 2;
+        } else
+            *p++ = in[i];
+    }
+
+    *p = '\0';
+
+    return SASL_OK;
+}
+
+static int
+gs2_escape_authzid(const sasl_utils_t *utils,
+                   const char *in,
+                   unsigned inlen,
+                   char **authzid)
+{
+    size_t i;
+    char *p;
+
+    p = *authzid = utils->malloc((inlen * 3) + 1);
+    if (*authzid == NULL)
+        return SASL_NOMEM;
+
+    for (i = 0; i < inlen; i++) {
+        if (in[i] == ',') {
+            memcpy(p, "=2C", 3);
+            p += 3;
+        } else if (in[i] == '=') {
+            memcpy(p, "=3D", 3);
+            p += 3;
+        } else {
+            *p++ = in[i];
+        }
+    }
+
+    *p = '\0';
+
+    return SASL_OK;
+}
+
+static int
+gs2_ask_user_info(context_t *text,
+                  sasl_client_params_t *params,
+                  char **realms __attribute__((unused)),
+                  int nrealm __attribute__((unused)),
+                  sasl_interact_t **prompt_need,
+                  sasl_out_params_t *oparams)
+{
+    int result = SASL_OK;
+    const char *authid = NULL, *userid = NULL;
+    int user_result = SASL_OK;
+    int auth_result = SASL_OK;
+    int pass_result = SASL_OK;
+
+    /* try to get the authid */
+    if (oparams->authid == NULL) {
+        auth_result = _plug_get_authid(params->utils, &authid, prompt_need);
+
+        if (auth_result != SASL_OK && auth_result != SASL_INTERACT) {
+            return auth_result;
+        }
+    }
+
+    /* try to get the userid */
+    if (oparams->user == NULL) {
+        user_result = _plug_get_userid(params->utils, &userid, prompt_need);
+
+        if (user_result != SASL_OK && user_result != SASL_INTERACT) {
+            return user_result;
+        }
+    }
+
+    /* try to get the password */
+    if (text->password == NULL) {
+        pass_result = _plug_get_password(params->utils, &text->password,
+                                         &text->free_password, prompt_need);
+        if (pass_result != SASL_OK && pass_result != SASL_INTERACT) {
+            return pass_result;
+        }
+    }
+
+    /* free prompts we got */
+    if (prompt_need && *prompt_need) {
+        params->utils->free(*prompt_need);
+        *prompt_need = NULL;
+    }
+
+    /* if there are prompts not filled in */
+    if (user_result == SASL_INTERACT || auth_result == SASL_INTERACT ||
+        pass_result == SASL_INTERACT) {
+
+        /* make the prompt list */
+        result =
+            _plug_make_prompts(params->utils, prompt_need,
+                               user_result == SASL_INTERACT ?
+                               "Please enter your authorization name" : NULL,
+                               NULL,
+                               auth_result == SASL_INTERACT ?
+                               "Please enter your authentication name" : NULL,
+                               NULL,
+                               pass_result == SASL_INTERACT ?
+                               "Please enter your password" : NULL, NULL,
+                               NULL, NULL, NULL,
+                               NULL,
+                               NULL, NULL);
+        if (result == SASL_OK) return SASL_INTERACT;
+
+        return result;
+    }
+
+    if (oparams->authid == NULL) {
+        if (userid == NULL || userid[0] == '\0') {
+            result = params->canon_user(params->utils->conn, authid, 0,
+                                        SASL_CU_AUTHID | SASL_CU_AUTHZID,
+                                        oparams);
+        } else {
+            result = params->canon_user(params->utils->conn,
+                                        authid, 0, SASL_CU_AUTHID, oparams);
+            if (result != SASL_OK) return result;
+
+            result = params->canon_user(params->utils->conn,
+                                        userid, 0, SASL_CU_AUTHZID, oparams);
+        }
+        if (result != SASL_OK)
+            return result;
+    }
+
+    return result;
+}
+
+static int
+sasl_gs2_seterror_(const sasl_utils_t *utils, OM_uint32 maj, OM_uint32 min,
+                   int logonly)
+{
+    OM_uint32 maj_stat, min_stat;
+    gss_buffer_desc msg;
+    OM_uint32 msg_ctx;
+    int ret;
+    char *out = NULL;
+    unsigned int len, curlen = 0;
+    const char prefix[] = "GSSAPI Error: ";
+
+    len = sizeof(prefix);
+    ret = _plug_buf_alloc(utils, &out, &curlen, 256);
+    if (ret != SASL_OK)
+        return SASL_OK;
+
+    strcpy(out, prefix);
+
+    msg_ctx = 0;
+    while (1) {
+        maj_stat = gss_display_status(&min_stat, maj,
+                                      GSS_C_GSS_CODE, GSS_C_NULL_OID,
+                                      &msg_ctx, &msg);
+
+        if (GSS_ERROR(maj_stat)) {
+            if (logonly) {
+                utils->log(utils->conn, SASL_LOG_FAIL,
+                        "GS2 Failure: (could not get major error message)");
+            } else {
+                utils->seterror(utils->conn, 0,
+                                "GS2 Failure "
+                                "(could not get major error message)");
+            }
+            utils->free(out);
+            return SASL_OK;
+        }
+
+        len += len + msg.length;
+        ret = _plug_buf_alloc(utils, &out, &curlen, len);
+        if (ret != SASL_OK) {
+            utils->free(out);
+            return SASL_OK;
+        }
+
+        strcat(out, msg.value);
+
+        gss_release_buffer(&min_stat, &msg);
+
+        if (!msg_ctx)
+            break;
+    }
+
+    /* Now get the minor status */
+
+    len += 2;
+    ret = _plug_buf_alloc(utils, &out, &curlen, len);
+    if (ret != SASL_OK) {
+        utils->free(out);
+        return SASL_NOMEM;
+    }
+
+    strcat(out, " (");
+
+    msg_ctx = 0;
+    while (1) {
+        maj_stat = gss_display_status(&min_stat, min,
+                                      GSS_C_MECH_CODE, GSS_C_NULL_OID,
+                                      &msg_ctx, &msg);
+
+        if (GSS_ERROR(maj_stat)) {
+            if (logonly) {
+                utils->log(utils->conn, SASL_LOG_FAIL,
+                        "GS2 Failure: (could not get minor error message)");
+            } else {
+                utils->seterror(utils->conn, 0,
+                                "GS2 Failure "
+                                "(could not get minor error message)");
+            }
+            utils->free(out);
+            return SASL_OK;
+        }
+
+        len += len + msg.length;
+
+        ret = _plug_buf_alloc(utils, &out, &curlen, len);
+        if (ret != SASL_OK) {
+            utils->free(out);
+            return SASL_NOMEM;
+        }
+
+        strcat(out, msg.value);
+
+        gss_release_buffer(&min_stat, &msg);
+
+        if (!msg_ctx)
+            break;
+    }
+
+    len += 1;
+    ret = _plug_buf_alloc(utils, &out, &curlen, len);
+    if (ret != SASL_OK) {
+        utils->free(out);
+        return SASL_NOMEM;
+    }
+
+    strcat(out, ")");
+
+    if (logonly) {
+        utils->log(utils->conn, SASL_LOG_FAIL, out);
+    } else {
+        utils->seterror(utils->conn, 0, out);
+    }
+    utils->free(out);
+
+    return SASL_OK;
+}