From d2000467fe0f288a09857297f97c884cfa2bee8a Mon Sep 17 00:00:00 2001 From: Luke Howard Date: Mon, 27 Sep 2010 00:46:41 +0200 Subject: [PATCH] Add GS2 mech code --- plugins/gs2.c | 1705 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1705 insertions(+) create mode 100644 plugins/gs2.c diff --git a/plugins/gs2.c b/plugins/gs2.c new file mode 100644 index 0000000..cee634f --- /dev/null +++ b/plugins/gs2.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#include "plugin_common.h" + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include +#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; +} -- 2.1.4