/* GSSAPI SASL plugin * Leif Johansson * Rob Siemborski (SASL v2 Conversion) * $Id: gssapi.c,v 1.115 2011/11/21 15:12:35 mel Exp $ */ /* * 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 #ifdef HAVE_GSSAPI_H #include #else #include #endif #ifdef WIN32 # include # ifndef R_OK # define R_OK 04 # endif /* we also need io.h for access() prototype */ # include #else # include # include # include # include # include #endif /* WIN32 */ #include #include #include #include #include #include "plugin_common.h" #ifdef HAVE_UNISTD_H #include #endif #include #include /***************************** Common Section *****************************/ static const char plugin_id[] = "$Id: gssapi.c,v 1.115 2011/11/21 15:12:35 mel Exp $"; static const char * GSSAPI_BLANK_STRING = ""; static gss_OID_desc gss_spnego_oid = { 6, (void *) "\x2b\x06\x01\x05\x05\x02" }; #if !defined(HAVE_GSS_C_NT_HOSTBASED_SERVICE) && !defined(GSS_C_NT_HOSTBASED_SERVICE) extern gss_OID gss_nt_service_name; #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name #endif #ifdef WANT_KERBEROS5_3DES /* Check if CyberSafe flag is defined */ #ifdef CSF_GSS_C_DES3_FLAG #define K5_MAX_SSF 112 #endif /* Heimdal and MIT use the following */ #ifdef GSS_KRB5_CONF_C_QOP_DES3_KD #define K5_MAX_SSF 112 #endif #endif #ifndef K5_MAX_SSF /* All Kerberos implementations support DES */ #define K5_MAX_SSF 56 #endif /* GSSAPI SASL Mechanism by Leif Johansson * inspired by the kerberos mechanism and the gssapi_server and * gssapi_client from the heimdal distribution by Assar Westerlund * and Johan Danielsson . * See the configure.in file for details on dependencies. * * Important contributions from Sam Hartman . * * This code was tested with the following distributions of Kerberos: * Heimdal (http://www.pdc.kth.se/heimdal), MIT (http://web.mit.edu/kerberos/www/) * CyberSafe (http://www.cybersafe.com/) and SEAM. */ #ifdef GSS_USE_MUTEXES #define GSS_LOCK_MUTEX(utils) \ if(((sasl_utils_t *)(utils))->mutex_lock(gss_mutex) != 0) { \ return SASL_FAIL; \ } #define GSS_UNLOCK_MUTEX(utils) \ if(((sasl_utils_t *)(utils))->mutex_unlock(gss_mutex) != 0) { \ return SASL_FAIL; \ } static void *gss_mutex = NULL; #else #define GSS_LOCK_MUTEX(utils) #define GSS_UNLOCK_MUTEX(utils) #endif typedef struct context { int state; gss_OID mech_type; /* GSS-SPNEGO or GSSAPI */ int http_mode; /* use RFC 4559 compatible protocol? */ 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; sasl_ssf_t limitssf, requiressf; /* application defined bounds, for the server */ unsigned char qop; /* as allowed by GSSAPI */ const sasl_utils_t *utils; /* layers buffering */ decode_context_t decode_context; char *encode_buf; /* For encoding/decoding mem management */ char *decode_buf; char *decode_once_buf; unsigned encode_buf_len; unsigned decode_buf_len; unsigned decode_once_buf_len; buffer_info_t *enc_in_buf; char *out_buf; /* per-step mem management */ unsigned out_buf_len; char *authid; /* hold the authid between steps - server */ const char *user; /* hold the userid between steps - client */ } context_t; enum { SASL_GSSAPI_STATE_AUTHNEG = 1, SASL_GSSAPI_STATE_SSFCAP = 2, SASL_GSSAPI_STATE_SSFREQ = 3, SASL_GSSAPI_STATE_AUTHENTICATED = 4 }; #define LAYER_CONFIDENTIALITY 4 #define LAYER_INTEGRITY 2 #define LAYER_NONE 1 /* sasl_gss_log: only logs status string returned from gss_display_status() */ #define sasl_gss_log(x,y,z) sasl_gss_seterror_(x,y,z,1) #define sasl_gss_seterror(x,y,z) sasl_gss_seterror_(x,y,z,0) static int sasl_gss_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; size_t len, curlen = 0; const char prefix[] = "GSSAPI Error: "; if (!utils) return SASL_OK; len = sizeof(prefix); ret = _plug_buf_alloc(utils, &out, &curlen, 256); if (ret != SASL_OK) return SASL_NOMEM; strcpy(out, prefix); msg_ctx = 0; while (1) { GSS_LOCK_MUTEX(utils); maj_stat = gss_display_status(&min_stat, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &msg); GSS_UNLOCK_MUTEX(utils); if(GSS_ERROR(maj_stat)) { if (logonly) { utils->log(utils->conn, SASL_LOG_FAIL, "GSSAPI Failure: (could not get major error message)"); } else { utils->seterror(utils->conn, 0, "GSSAPI 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_NOMEM; } strcat(out, msg.value); GSS_LOCK_MUTEX(utils); gss_release_buffer(&min_stat, &msg); GSS_UNLOCK_MUTEX(utils); 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) { GSS_LOCK_MUTEX(utils); maj_stat = gss_display_status(&min_stat, min, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &msg); GSS_UNLOCK_MUTEX(utils); if(GSS_ERROR(maj_stat)) { if (logonly) { utils->log(utils->conn, SASL_LOG_FAIL, "GSSAPI Failure: (could not get minor error message)"); } else { utils->seterror(utils->conn, 0, "GSSAPI 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_LOCK_MUTEX(utils); gss_release_buffer(&min_stat, &msg); GSS_UNLOCK_MUTEX(utils); 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; } static int sasl_gss_encode(void *context, const struct iovec *invec, unsigned numiov, const char **output, unsigned *outputlen, int privacy) { context_t *text = (context_t *)context; OM_uint32 maj_stat, min_stat; gss_buffer_t input_token, output_token; gss_buffer_desc real_input_token, real_output_token; int ret; struct buffer_info *inblob, bufinfo; if (!output) return SASL_BADPARAM; if (numiov > 1) { ret = _plug_iovec_to_buf(text->utils, invec, numiov, &text->enc_in_buf); if (ret != SASL_OK) return ret; inblob = text->enc_in_buf; } else { bufinfo.data = invec[0].iov_base; bufinfo.curlen = invec[0].iov_len; inblob = &bufinfo; } if (text->state != SASL_GSSAPI_STATE_AUTHENTICATED) return SASL_NOTDONE; input_token = &real_input_token; real_input_token.value = inblob->data; real_input_token.length = inblob->curlen; output_token = &real_output_token; output_token->value = NULL; output_token->length = 0; GSS_LOCK_MUTEX(text->utils); maj_stat = gss_wrap (&min_stat, text->gss_ctx, privacy, GSS_C_QOP_DEFAULT, input_token, NULL, output_token); GSS_UNLOCK_MUTEX(text->utils); if (GSS_ERROR(maj_stat)) { sasl_gss_seterror(text->utils, maj_stat, min_stat); if (output_token->value) { GSS_LOCK_MUTEX(text->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(text->utils); } return SASL_FAIL; } if (output_token->value && output) { unsigned char * p; ret = _plug_buf_alloc(text->utils, &(text->encode_buf), &(text->encode_buf_len), output_token->length + 4); if (ret != SASL_OK) { GSS_LOCK_MUTEX(text->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(text->utils); return ret; } p = (unsigned char *) text->encode_buf; p[0] = (output_token->length>>24) & 0xFF; p[1] = (output_token->length>>16) & 0xFF; p[2] = (output_token->length>>8) & 0xFF; p[3] = output_token->length & 0xFF; memcpy(text->encode_buf + 4, output_token->value, output_token->length); } if (outputlen) { *outputlen = output_token->length + 4; } *output = text->encode_buf; if (output_token->value) { GSS_LOCK_MUTEX(text->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(text->utils); } return SASL_OK; } static int gssapi_privacy_encode(void *context, const struct iovec *invec, unsigned numiov, const char **output, unsigned *outputlen) { return sasl_gss_encode(context,invec,numiov,output,outputlen,1); } static int gssapi_integrity_encode(void *context, const struct iovec *invec, unsigned numiov, const char **output, unsigned *outputlen) { return sasl_gss_encode(context,invec,numiov,output,outputlen,0); } static int gssapi_decode_packet(void *context, const char *input, unsigned inputlen, char **output, unsigned *outputlen) { context_t *text = (context_t *) context; OM_uint32 maj_stat, min_stat; gss_buffer_t input_token, output_token; gss_buffer_desc real_input_token, real_output_token; int result; if (text->state != SASL_GSSAPI_STATE_AUTHENTICATED) { SETERROR(text->utils, "GSSAPI Failure"); return SASL_NOTDONE; } input_token = &real_input_token; real_input_token.value = (char *) input; real_input_token.length = inputlen; output_token = &real_output_token; output_token->value = NULL; output_token->length = 0; GSS_LOCK_MUTEX(text->utils); maj_stat = gss_unwrap (&min_stat, text->gss_ctx, input_token, output_token, NULL, NULL); GSS_UNLOCK_MUTEX(text->utils); if (GSS_ERROR(maj_stat)) { sasl_gss_seterror(text->utils,maj_stat,min_stat); if (output_token->value) { GSS_LOCK_MUTEX(text->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(text->utils); } return SASL_FAIL; } if (outputlen) { *outputlen = output_token->length; } if (output_token->value) { if (output) { result = _plug_buf_alloc(text->utils, &text->decode_once_buf, &text->decode_once_buf_len, *outputlen); if (result != SASL_OK) { GSS_LOCK_MUTEX(text->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(text->utils); return result; } *output = text->decode_once_buf; memcpy(*output, output_token->value, *outputlen); } GSS_LOCK_MUTEX(text->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(text->utils); } return SASL_OK; } static int gssapi_decode(void *context, const char *input, unsigned inputlen, const char **output, unsigned *outputlen) { context_t *text = (context_t *) context; int ret; ret = _plug_decode(&text->decode_context, input, inputlen, &text->decode_buf, &text->decode_buf_len, outputlen, gssapi_decode_packet, text); *output = text->decode_buf; return ret; } static context_t *sasl_gss_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_gss_free_context_contents(context_t *text) { OM_uint32 maj_stat, min_stat; if (!text) return SASL_OK; GSS_LOCK_MUTEX(text->utils); if (text->gss_ctx != GSS_C_NO_CONTEXT) { maj_stat = 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) { maj_stat = gss_release_name(&min_stat,&text->client_name); text->client_name = GSS_C_NO_NAME; } if (text->server_name != GSS_C_NO_NAME) { maj_stat = gss_release_name(&min_stat,&text->server_name); text->server_name = GSS_C_NO_NAME; } if ( text->server_creds != GSS_C_NO_CREDENTIAL) { maj_stat = gss_release_cred(&min_stat, &text->server_creds); text->server_creds = GSS_C_NO_CREDENTIAL; } if ( text->client_creds != GSS_C_NO_CREDENTIAL) { maj_stat = gss_release_cred(&min_stat, &text->client_creds); text->client_creds = GSS_C_NO_CREDENTIAL; } GSS_UNLOCK_MUTEX(text->utils); if (text->out_buf) { text->utils->free(text->out_buf); text->out_buf = NULL; } if (text->encode_buf) { text->utils->free(text->encode_buf); text->encode_buf = NULL; } if (text->decode_buf) { text->utils->free(text->decode_buf); text->decode_buf = NULL; } if (text->decode_once_buf) { text->utils->free(text->decode_once_buf); text->decode_once_buf = NULL; } if (text->enc_in_buf) { if(text->enc_in_buf->data) text->utils->free(text->enc_in_buf->data); text->utils->free(text->enc_in_buf); text->enc_in_buf = NULL; } _plug_decode_free(&text->decode_context); if (text->authid) { /* works for both client and server */ text->utils->free(text->authid); text->authid = NULL; } return SASL_OK; } static void gssapi_common_mech_dispose(void *conn_context, const sasl_utils_t *utils) { sasl_gss_free_context_contents((context_t *)(conn_context)); utils->free(conn_context); } static void gssapi_common_mech_free(void *global_context __attribute__((unused)), const sasl_utils_t *utils) { #ifdef GSS_USE_MUTEXES if (gss_mutex) { utils->mutex_free(gss_mutex); gss_mutex=NULL; } #endif } /***************************** Server Section *****************************/ static int gssapi_server_mech_new(void *glob_context __attribute__((unused)), sasl_server_params_t *params, const char *challenge __attribute__((unused)), unsigned challen __attribute__((unused)), void **conn_context) { context_t *text; text = sasl_gss_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->state = SASL_GSSAPI_STATE_AUTHNEG; text->http_mode = (params->flags & SASL_NEED_HTTP); *conn_context = text; return SASL_OK; } static int gssapi_server_mech_authneg(context_t *text, sasl_server_params_t *params, const char *clientin, unsigned clientinlen, const char **serverout, unsigned *serveroutlen, sasl_out_params_t *oparams __attribute__((unused))) { gss_buffer_t input_token, output_token; gss_buffer_desc real_input_token, real_output_token; OM_uint32 maj_stat = 0, min_stat = 0; gss_buffer_desc name_token; int ret, equal = 0; unsigned out_flags = 0; gss_cred_id_t server_creds = (gss_cred_id_t) params->gss_creds; gss_buffer_desc name_without_realm; gss_name_t client_name_MN = NULL, without = NULL; gss_OID mech_type; input_token = &real_input_token; output_token = &real_output_token; output_token->value = NULL; output_token->length = 0; input_token->value = NULL; input_token->length = 0; if (text->server_name == GSS_C_NO_NAME) { /* only once */ if (params->serverFQDN == NULL || strlen(params->serverFQDN) == 0) { SETERROR(text->utils, "GSSAPI Failure: no serverFQDN"); sasl_gss_free_context_contents(text); return SASL_FAIL; } name_token.length = strlen(params->service) + 1 + strlen(params->serverFQDN); name_token.value = (char *)params->utils->malloc((name_token.length + 1) * sizeof(char)); if (name_token.value == NULL) { MEMERROR(text->utils); sasl_gss_free_context_contents(text); return SASL_NOMEM; } sprintf(name_token.value,"%s@%s", params->service, params->serverFQDN); GSS_LOCK_MUTEX(params->utils); maj_stat = gss_import_name (&min_stat, &name_token, GSS_C_NT_HOSTBASED_SERVICE, &text->server_name); GSS_UNLOCK_MUTEX(params->utils); params->utils->free(name_token.value); name_token.value = NULL; if (GSS_ERROR(maj_stat)) { sasl_gss_seterror(text->utils, maj_stat, min_stat); sasl_gss_free_context_contents(text); return SASL_FAIL; } if ( text->server_creds != GSS_C_NO_CREDENTIAL) { GSS_LOCK_MUTEX(params->utils); maj_stat = gss_release_cred(&min_stat, &text->server_creds); GSS_UNLOCK_MUTEX(params->utils); text->server_creds = GSS_C_NO_CREDENTIAL; } /* If caller didn't provide creds already */ if ( server_creds == GSS_C_NO_CREDENTIAL) { GSS_LOCK_MUTEX(params->utils); maj_stat = gss_acquire_cred(&min_stat, text->server_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT, &text->server_creds, NULL, NULL); GSS_UNLOCK_MUTEX(params->utils); if (GSS_ERROR(maj_stat)) { sasl_gss_seterror(text->utils, maj_stat, min_stat); sasl_gss_free_context_contents(text); return SASL_FAIL; } server_creds = text->server_creds; } } if (clientinlen) { real_input_token.value = (void *)clientin; real_input_token.length = clientinlen; } GSS_LOCK_MUTEX(params->utils); maj_stat = gss_accept_sec_context(&min_stat, &(text->gss_ctx), server_creds, input_token, GSS_C_NO_CHANNEL_BINDINGS, &text->client_name, &mech_type, output_token, &out_flags, NULL, /* context validity period */ &(text->client_creds)); GSS_UNLOCK_MUTEX(params->utils); if (GSS_ERROR(maj_stat)) { sasl_gss_log(text->utils, maj_stat, min_stat); text->utils->seterror(text->utils->conn, SASL_NOLOG, "GSSAPI Failure: gss_accept_sec_context"); if (output_token->value) { GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); } sasl_gss_free_context_contents(text); return SASL_BADAUTH; } if (serveroutlen) { *serveroutlen = output_token->length; } if (output_token->value) { if (serverout) { ret = _plug_buf_alloc(text->utils, &(text->out_buf), &(text->out_buf_len), *serveroutlen); if(ret != SASL_OK) { GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); return ret; } memcpy(text->out_buf, output_token->value, *serveroutlen); *serverout = text->out_buf; } GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); } else { /* No output token, send an empty string */ *serverout = GSSAPI_BLANK_STRING; *serveroutlen = 0; } if (maj_stat == GSS_S_CONTINUE_NEEDED) { /* Context isn't complete */ return SASL_CONTINUE; } assert(maj_stat == GSS_S_COMPLETE); /* When GSS_Accept_sec_context returns GSS_S_COMPLETE, the server examines the context to ensure that it provides a level of protection permitted by the server's security policy. In particular, if the integ_avail flag is not set in the context, then no security layer can be offered or accepted. If the conf_avail flag is not set in the context, then no security layer with confidentiality can be offered or accepted. */ if ((out_flags & GSS_C_INTEG_FLAG) == 0) { /* if the integ_avail flag is not set in the context, then no security layer can be offered or accepted. */ text->qop = LAYER_NONE; } else if ((out_flags & GSS_C_CONF_FLAG) == 0) { /* If the conf_avail flag is not set in the context, then no security layer with confidentiality can be offered or accepted. */ text->qop = LAYER_NONE | LAYER_INTEGRITY; } else { text->qop = LAYER_NONE | LAYER_INTEGRITY | LAYER_CONFIDENTIALITY; } if ((params->props.security_flags & SASL_SEC_PASS_CREDENTIALS) && (!(out_flags & GSS_C_DELEG_FLAG) || text->client_creds == GSS_C_NO_CREDENTIAL) ) { text->utils->seterror(text->utils->conn, SASL_LOG_WARN, "GSSAPI warning: no credentials were passed"); /* continue with authentication */ } GSS_LOCK_MUTEX(params->utils); maj_stat = gss_canonicalize_name(&min_stat, text->client_name, mech_type, &client_name_MN); GSS_UNLOCK_MUTEX(params->utils); if (GSS_ERROR(maj_stat)) { SETERROR(text->utils, "GSSAPI Failure: gss_canonicalize_name"); sasl_gss_free_context_contents(text); return SASL_BADAUTH; } name_token.value = NULL; name_without_realm.value = NULL; GSS_LOCK_MUTEX(params->utils); maj_stat = gss_display_name (&min_stat, client_name_MN, &name_token, NULL); GSS_UNLOCK_MUTEX(params->utils); if (GSS_ERROR(maj_stat)) { SETERROR(text->utils, "GSSAPI Failure: gss_display_name"); sasl_gss_free_context_contents(text); ret = SASL_BADAUTH; goto cleanup; } /* If the id contains a realm get the identifier for the user without the realm and see if it's the same id (i.e. tmartin == tmartin@ANDREW.CMU.EDU. If this is the case we just want to return the id (i.e. just "tmartin" */ if (strchr((char *) name_token.value, (int) '@') != NULL) { /* NOTE: libc malloc, as it is freed below by a gssapi internal * function! */ name_without_realm.value = params->utils->malloc(strlen(name_token.value)+1); if (name_without_realm.value == NULL) { MEMERROR(text->utils); ret = SASL_NOMEM; goto cleanup; } strcpy(name_without_realm.value, name_token.value); /* cut off string at '@' */ (strchr(name_without_realm.value,'@'))[0] = '\0'; name_without_realm.length = strlen( (char *) name_without_realm.value ); GSS_LOCK_MUTEX(params->utils); maj_stat = gss_import_name (&min_stat, &name_without_realm, /* Solaris 8/9 gss_import_name doesn't accept GSS_C_NULL_OID here, so use GSS_C_NT_USER_NAME instead if available. */ #ifdef HAVE_GSS_C_NT_USER_NAME GSS_C_NT_USER_NAME, #else GSS_C_NULL_OID, #endif &without); GSS_UNLOCK_MUTEX(params->utils); if (GSS_ERROR(maj_stat)) { SETERROR(text->utils, "GSSAPI Failure: gss_import_name"); sasl_gss_free_context_contents(text); ret = SASL_BADAUTH; goto cleanup; } GSS_LOCK_MUTEX(params->utils); maj_stat = gss_compare_name(&min_stat, client_name_MN, without, &equal); GSS_UNLOCK_MUTEX(params->utils); if (GSS_ERROR(maj_stat)) { SETERROR(text->utils, "GSSAPI Failure: gss_compare_name"); sasl_gss_free_context_contents(text); ret = SASL_BADAUTH; goto cleanup; } } else { equal = 0; } if (equal) { text->authid = strdup(name_without_realm.value); } else { text->authid = strdup(name_token.value); } if (text->authid == NULL) { MEMERROR(params->utils); ret = SASL_NOMEM; goto cleanup; } if (text->http_mode) { /* HTTP doesn't do any ssf negotiation */ text->state = SASL_GSSAPI_STATE_AUTHENTICATED; ret = SASL_OK; } else { /* Switch to ssf negotiation */ text->state = SASL_GSSAPI_STATE_SSFCAP; ret = SASL_CONTINUE; } cleanup: if (client_name_MN) { GSS_LOCK_MUTEX(params->utils); gss_release_name(&min_stat, &client_name_MN); GSS_UNLOCK_MUTEX(params->utils); } if (name_token.value) { GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, &name_token); GSS_UNLOCK_MUTEX(params->utils); } if (name_without_realm.value) { params->utils->free(name_without_realm.value); } if (without) { GSS_LOCK_MUTEX(params->utils); gss_release_name(&min_stat, &without); GSS_UNLOCK_MUTEX(params->utils); } return ret; } static int gssapi_server_mech_ssfcap(context_t *text, sasl_server_params_t *params, const char *clientin __attribute__((unused)), unsigned clientinlen, const char **serverout, unsigned *serveroutlen, sasl_out_params_t *oparams __attribute__((unused))) { gss_buffer_t input_token, output_token; gss_buffer_desc real_input_token, real_output_token; OM_uint32 maj_stat = 0, min_stat = 0; unsigned char sasldata[4]; int ret; input_token = &real_input_token; output_token = &real_output_token; output_token->value = NULL; output_token->length = 0; if (clientinlen != 0) { SETERROR(text->utils, "GSSAPI server is not expecting data at this stage"); sasl_gss_free_context_contents(text); return SASL_BADAUTH; } /* we have to decide what sort of encryption/integrity/etc., we support */ if (params->props.max_ssf < params->external_ssf) { text->limitssf = 0; } else { text->limitssf = params->props.max_ssf - params->external_ssf; } if (params->props.min_ssf < params->external_ssf) { text->requiressf = 0; } else { text->requiressf = params->props.min_ssf - params->external_ssf; } /* build up our security properties token */ if (text->requiressf != 0 && (text->qop & (LAYER_INTEGRITY|LAYER_CONFIDENTIALITY))) { if (params->props.maxbufsize > 0xFFFFFF) { /* make sure maxbufsize isn't too large */ /* maxbufsize = 0xFFFFFF */ sasldata[1] = sasldata[2] = sasldata[3] = 0xFF; } else { sasldata[1] = (params->props.maxbufsize >> 16) & 0xFF; sasldata[2] = (params->props.maxbufsize >> 8) & 0xFF; sasldata[3] = (params->props.maxbufsize >> 0) & 0xFF; } } else { /* From RFC 4752: "The client verifies that the server maximum buffer is 0 if the server does not advertise support for any security layer." */ sasldata[1] = sasldata[2] = sasldata[3] = 0; } sasldata[0] = 0; if(text->requiressf != 0 && !params->props.maxbufsize) { params->utils->seterror(params->utils->conn, 0, "GSSAPI needs a security layer but one is forbidden"); return SASL_TOOWEAK; } if (text->requiressf == 0) { sasldata[0] |= LAYER_NONE; /* authentication */ } if ((text->qop & LAYER_INTEGRITY) && text->requiressf <= 1 && text->limitssf >= 1 && params->props.maxbufsize) { sasldata[0] |= LAYER_INTEGRITY; } if ((text->qop & LAYER_CONFIDENTIALITY) && text->requiressf <= K5_MAX_SSF && text->limitssf >= K5_MAX_SSF && params->props.maxbufsize) { sasldata[0] |= LAYER_CONFIDENTIALITY; } /* Remember what we want and can offer */ text->qop = sasldata[0]; real_input_token.value = (void *)sasldata; real_input_token.length = 4; GSS_LOCK_MUTEX(params->utils); maj_stat = gss_wrap(&min_stat, text->gss_ctx, 0, /* Just integrity checking here */ GSS_C_QOP_DEFAULT, input_token, NULL, output_token); GSS_UNLOCK_MUTEX(params->utils); if (GSS_ERROR(maj_stat)) { sasl_gss_seterror(text->utils, maj_stat, min_stat); if (output_token->value) { GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); } sasl_gss_free_context_contents(text); return SASL_FAIL; } if (serveroutlen) *serveroutlen = output_token->length; if (output_token->value) { if (serverout) { ret = _plug_buf_alloc(text->utils, &(text->out_buf), &(text->out_buf_len), *serveroutlen); if(ret != SASL_OK) { GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); return ret; } memcpy(text->out_buf, output_token->value, *serveroutlen); *serverout = text->out_buf; } GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); } /* Wait for ssf request and authid */ text->state = SASL_GSSAPI_STATE_SSFREQ; return SASL_CONTINUE; } static int gssapi_server_mech_ssfreq(context_t *text, sasl_server_params_t *params, const char *clientin, unsigned clientinlen, const char **serverout __attribute__((unused)), unsigned *serveroutlen __attribute__((unused)), sasl_out_params_t *oparams) { gss_buffer_t input_token, output_token; gss_buffer_desc real_input_token, real_output_token; OM_uint32 maj_stat = 0, min_stat = 0; OM_uint32 max_input; int layerchoice; input_token = &real_input_token; output_token = &real_output_token; output_token->value = NULL; output_token->length = 0; real_input_token.value = (void *)clientin; real_input_token.length = clientinlen; GSS_LOCK_MUTEX(params->utils); maj_stat = gss_unwrap(&min_stat, text->gss_ctx, input_token, output_token, NULL, NULL); GSS_UNLOCK_MUTEX(params->utils); if (GSS_ERROR(maj_stat)) { sasl_gss_seterror(text->utils, maj_stat, min_stat); sasl_gss_free_context_contents(text); return SASL_FAIL; } if (output_token->length < 4) { SETERROR(text->utils, "token too short"); GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); sasl_gss_free_context_contents(text); return SASL_FAIL; } layerchoice = (int)(((char *)(output_token->value))[0]); if (layerchoice == LAYER_NONE && (text->qop & LAYER_NONE)) { /* no encryption */ oparams->encode = NULL; oparams->decode = NULL; oparams->mech_ssf = 0; } else if (layerchoice == LAYER_INTEGRITY && (text->qop & LAYER_INTEGRITY)) { /* integrity */ oparams->encode = &gssapi_integrity_encode; oparams->decode = &gssapi_decode; oparams->mech_ssf = 1; } else if ((layerchoice == LAYER_CONFIDENTIALITY || /* For compatibility with broken clients setting both bits */ layerchoice == (LAYER_CONFIDENTIALITY|LAYER_INTEGRITY)) && (text->qop & LAYER_CONFIDENTIALITY)) { /* privacy */ oparams->encode = &gssapi_privacy_encode; oparams->decode = &gssapi_decode; /* FIX ME: Need to extract the proper value here */ oparams->mech_ssf = K5_MAX_SSF; } else { /* not a supported encryption layer */ SETERROR(text->utils, "protocol violation: client requested invalid layer"); /* Mark that we attempted negotiation */ oparams->mech_ssf = 2; if (output_token->value) { GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); } sasl_gss_free_context_contents(text); return SASL_FAIL; } if (output_token->length > 4) { int ret; ret = params->canon_user(params->utils->conn, ((char *) output_token->value) + 4, (output_token->length - 4) * sizeof(char), SASL_CU_AUTHZID, oparams); if (ret != SASL_OK) { sasl_gss_free_context_contents(text); return ret; } } /* No matter what, set the rest of the oparams */ oparams->maxoutbuf = (((unsigned char *) output_token->value)[1] << 16) | (((unsigned char *) output_token->value)[2] << 8) | (((unsigned char *) output_token->value)[3] << 0); if (oparams->mech_ssf) { maj_stat = gss_wrap_size_limit( &min_stat, text->gss_ctx, 1, GSS_C_QOP_DEFAULT, (OM_uint32) oparams->maxoutbuf, &max_input); if(max_input > oparams->maxoutbuf) { /* Heimdal appears to get this wrong */ oparams->maxoutbuf -= (max_input - oparams->maxoutbuf); } else { /* This code is actually correct */ oparams->maxoutbuf = max_input; } } GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); text->state = SASL_GSSAPI_STATE_AUTHENTICATED; /* used by layers */ _plug_decode_init(&text->decode_context, text->utils, (params->props.maxbufsize > 0xFFFFFF) ? 0xFFFFFF : params->props.maxbufsize); return SASL_OK; } static int gssapi_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; int ret; if (!serverout) { PARAMERROR(text->utils); return SASL_BADPARAM; } *serverout = NULL; *serveroutlen = 0; if (text == NULL) return SASL_BADPROT; params->utils->log(NULL, SASL_LOG_DEBUG, "GSSAPI server step %d\n", text->state); switch (text->state) { case SASL_GSSAPI_STATE_AUTHNEG: ret = gssapi_server_mech_authneg(text, params, clientin, clientinlen, serverout, serveroutlen, oparams); if (ret != SASL_CONTINUE || *serveroutlen) break; /* Pretend that we just got an empty response from the client */ clientinlen = 0; /* fall through */ case SASL_GSSAPI_STATE_SSFCAP: ret = gssapi_server_mech_ssfcap(text, params, clientin, clientinlen, serverout, serveroutlen, oparams); break; case SASL_GSSAPI_STATE_SSFREQ: ret = gssapi_server_mech_ssfreq(text, params, clientin, clientinlen, serverout, serveroutlen, oparams); break; default: params->utils->log(NULL, SASL_LOG_ERR, "Invalid GSSAPI server step %d\n", text->state); return SASL_FAIL; } if (ret == SASL_OK) { ret = params->canon_user(params->utils->conn, text->authid, 0, /* strlen(text->authid) */ (oparams->user ? 0 : SASL_CU_AUTHZID) | SASL_CU_AUTHID | SASL_CU_EXTERNALLY_VERIFIED, oparams); if (ret != SASL_OK) { sasl_gss_free_context_contents(text); return ret; } if (text->client_creds != GSS_C_NO_CREDENTIAL) { oparams->client_creds = &text->client_creds; } else { oparams->client_creds = NULL; } oparams->doneflag = 1; } return ret; } static sasl_server_plug_t gssapi_server_plugins[] = { { "GSSAPI", /* mech_name */ K5_MAX_SSF, /* max_ssf */ SASL_SEC_NOPLAINTEXT | SASL_SEC_NOACTIVE | SASL_SEC_NOANONYMOUS | SASL_SEC_MUTUAL_AUTH /* security_flags */ | SASL_SEC_PASS_CREDENTIALS, SASL_FEAT_WANT_CLIENT_FIRST | SASL_FEAT_ALLOWS_PROXY | SASL_FEAT_DONTUSE_USERPASSWD, /* features */ NULL, /* glob_context */ &gssapi_server_mech_new, /* mech_new */ &gssapi_server_mech_step, /* mech_step */ &gssapi_common_mech_dispose, /* mech_dispose */ &gssapi_common_mech_free, /* mech_free */ NULL, /* setpass */ NULL, /* user_query */ NULL, /* idle */ NULL, /* mech_avail */ NULL /* spare */ } #ifdef HAVE_GSS_SPNEGO ,{ "GSS-SPNEGO", /* mech_name */ K5_MAX_SSF, /* max_ssf */ SASL_SEC_NOPLAINTEXT | SASL_SEC_NOACTIVE | SASL_SEC_NOANONYMOUS | SASL_SEC_MUTUAL_AUTH /* security_flags */ | SASL_SEC_PASS_CREDENTIALS, SASL_FEAT_WANT_CLIENT_FIRST | SASL_FEAT_ALLOWS_PROXY | SASL_FEAT_DONTUSE_USERPASSWD | SASL_FEAT_SUPPORTS_HTTP, /* features */ NULL, /* glob_context */ &gssapi_server_mech_new, /* mech_new */ &gssapi_server_mech_step, /* mech_step */ &gssapi_common_mech_dispose, /* mech_dispose */ &gssapi_common_mech_free, /* mech_free */ NULL, /* setpass */ NULL, /* user_query */ NULL, /* idle */ NULL, /* mech_avail */ NULL /* spare */ } #endif }; int gssapiv2_server_plug_init( #ifndef HAVE_GSSKRB5_REGISTER_ACCEPTOR_IDENTITY const sasl_utils_t *utils __attribute__((unused)), #else const sasl_utils_t *utils, #endif int maxversion, int *out_version, sasl_server_plug_t **pluglist, int *plugcount) { #ifdef HAVE_GSSKRB5_REGISTER_ACCEPTOR_IDENTITY const char *keytab = NULL; char keytab_path[1024]; unsigned int rl; #endif if (maxversion < SASL_SERVER_PLUG_VERSION) { return SASL_BADVERS; } #ifdef HAVE_GSSKRB5_REGISTER_ACCEPTOR_IDENTITY /* unfortunately, we don't check for readability of keytab if it's the standard one, since we don't know where it is */ /* FIXME: This code is broken */ utils->getopt(utils->getopt_context, "GSSAPI", "keytab", &keytab, &rl); if (keytab != NULL) { if (access(keytab, R_OK) != 0) { utils->log(NULL, SASL_LOG_ERR, "Could not find keytab file: %s: %m", keytab, errno); return SASL_FAIL; } if(strlen(keytab) > 1024) { utils->log(NULL, SASL_LOG_ERR, "path to keytab is > 1024 characters"); return SASL_BUFOVER; } strncpy(keytab_path, keytab, 1024); gsskrb5_register_acceptor_identity(keytab_path); } #endif *out_version = SASL_SERVER_PLUG_VERSION; *pluglist = gssapi_server_plugins; #ifdef HAVE_GSS_SPNEGO *plugcount = 2; #else *plugcount = 1; #endif #ifdef GSS_USE_MUTEXES if (!gss_mutex) { gss_mutex = utils->mutex_alloc(); if (!gss_mutex) { return SASL_FAIL; } } #endif return SASL_OK; } /***************************** Client Section *****************************/ static int gssapi_client_mech_new(void *glob_context, sasl_client_params_t *params, void **conn_context) { context_t *text; /* holds state are in */ text = sasl_gss_new_context(params->utils); if (text == NULL) { MEMERROR(params->utils); return SASL_NOMEM; } text->state = SASL_GSSAPI_STATE_AUTHNEG; text->mech_type = (gss_OID) glob_context; 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->http_mode = (params->flags & SASL_NEED_HTTP); *conn_context = text; return SASL_OK; } static int gssapi_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_t input_token, output_token; gss_buffer_desc real_input_token, real_output_token; OM_uint32 maj_stat = 0, min_stat = 0; OM_uint32 max_input; gss_buffer_desc name_token; int ret; OM_uint32 req_flags = 0, out_req_flags = 0; input_token = &real_input_token; output_token = &real_output_token; output_token->value = NULL; input_token->value = NULL; input_token->length = 0; gss_cred_id_t client_creds = (gss_cred_id_t)params->gss_creds; *clientout = NULL; *clientoutlen = 0; params->utils->log(NULL, SASL_LOG_DEBUG, "GSSAPI client step %d", text->state); switch (text->state) { case SASL_GSSAPI_STATE_AUTHNEG: /* try to get the userid */ if (text->user == NULL) { int user_result = SASL_OK; user_result = _plug_get_userid(params->utils, &text->user, prompt_need); if ((user_result != SASL_OK) && (user_result != SASL_INTERACT)) { sasl_gss_free_context_contents(text); return user_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) { /* make the prompt list */ int result = _plug_make_prompts(params->utils, prompt_need, user_result == SASL_INTERACT ? "Please enter your authorization name" : NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); if (result != SASL_OK) return result; return SASL_INTERACT; } } if (text->server_name == GSS_C_NO_NAME) { /* only once */ if (params->serverFQDN == NULL || strlen(params->serverFQDN) == 0) { SETERROR(text->utils, "GSSAPI Failure: no serverFQDN"); sasl_gss_free_context_contents(text); return SASL_FAIL; } name_token.length = strlen(params->service) + 1 + strlen(params->serverFQDN); name_token.value = (char *)params->utils->malloc((name_token.length + 1) * sizeof(char)); if (name_token.value == NULL) { sasl_gss_free_context_contents(text); return SASL_NOMEM; } sprintf(name_token.value,"%s@%s", params->service, params->serverFQDN); GSS_LOCK_MUTEX(params->utils); maj_stat = gss_import_name (&min_stat, &name_token, GSS_C_NT_HOSTBASED_SERVICE, &text->server_name); GSS_UNLOCK_MUTEX(params->utils); params->utils->free(name_token.value); name_token.value = NULL; if (GSS_ERROR(maj_stat)) { sasl_gss_seterror(text->utils, maj_stat, min_stat); sasl_gss_free_context_contents(text); return SASL_FAIL; } } if (serverinlen == 0) input_token = GSS_C_NO_BUFFER; if (serverinlen) { real_input_token.value = (void *)serverin; real_input_token.length = serverinlen; } else if (text->gss_ctx != GSS_C_NO_CONTEXT ) { /* This can't happen under GSSAPI: we have a non-null context * and no input from the server. However, thanks to Imap, * which discards our first output, this happens all the time. * Throw away the context and try again. */ GSS_LOCK_MUTEX(params->utils); maj_stat = gss_delete_sec_context (&min_stat,&text->gss_ctx,GSS_C_NO_BUFFER); GSS_UNLOCK_MUTEX(params->utils); text->gss_ctx = GSS_C_NO_CONTEXT; } /* Setup req_flags properly */ req_flags = GSS_C_INTEG_FLAG; if (params->props.max_ssf > params->external_ssf) { /* We are requesting a security layer */ req_flags |= GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG; /* Any SSF bigger than 1 is confidentiality. */ /* Let's check if the client of the API requires confidentiality, and it wasn't already provided by an external layer */ if (params->props.max_ssf - params->external_ssf > 1) { /* We want to try for privacy */ req_flags |= GSS_C_CONF_FLAG; } } if (params->props.security_flags & SASL_SEC_PASS_CREDENTIALS) { req_flags = req_flags | GSS_C_DELEG_FLAG; } GSS_LOCK_MUTEX(params->utils); maj_stat = gss_init_sec_context(&min_stat, client_creds, /* GSS_C_NO_CREDENTIAL */ &text->gss_ctx, text->server_name, text->mech_type, req_flags, 0, GSS_C_NO_CHANNEL_BINDINGS, input_token, NULL, output_token, &out_req_flags, NULL); GSS_UNLOCK_MUTEX(params->utils); if (GSS_ERROR(maj_stat)) { sasl_gss_seterror(text->utils, maj_stat, min_stat); if (output_token->value) { GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); } sasl_gss_free_context_contents(text); return SASL_FAIL; } if ((out_req_flags & GSS_C_INTEG_FLAG) == 0) { /* if the integ_avail flag is not set in the context, then no security layer can be offered or accepted. */ text->qop = LAYER_NONE; } else if ((out_req_flags & GSS_C_CONF_FLAG) == 0) { /* If the conf_avail flag is not set in the context, then no security layer with confidentiality can be offered or accepted. */ text->qop = LAYER_NONE | LAYER_INTEGRITY; } else { text->qop = LAYER_NONE | LAYER_INTEGRITY | LAYER_CONFIDENTIALITY; } if ((out_req_flags & GSS_C_DELEG_FLAG) != (req_flags & GSS_C_DELEG_FLAG)) { text->utils->seterror(text->utils->conn, SASL_LOG_WARN, "GSSAPI warning: no credentials were passed"); /* not a fatal error */ } *clientoutlen = output_token->length; if (output_token->value) { if (clientout) { ret = _plug_buf_alloc(text->utils, &(text->out_buf), &(text->out_buf_len), *clientoutlen); if(ret != SASL_OK) { GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); return ret; } memcpy(text->out_buf, output_token->value, *clientoutlen); *clientout = text->out_buf; } GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); } if (maj_stat == GSS_S_COMPLETE) { GSS_LOCK_MUTEX(params->utils); maj_stat = gss_inquire_context(&min_stat, text->gss_ctx, &text->client_name, NULL, /* targ_name */ NULL, /* lifetime */ NULL, /* mech */ /* FIX ME: Should check the resulting flags here */ NULL, /* flags */ NULL, /* local init */ NULL); /* open */ GSS_UNLOCK_MUTEX(params->utils); if (GSS_ERROR(maj_stat)) { sasl_gss_seterror(text->utils, maj_stat, min_stat); sasl_gss_free_context_contents(text); return SASL_FAIL; } name_token.length = 0; GSS_LOCK_MUTEX(params->utils); maj_stat = gss_display_name(&min_stat, text->client_name, &name_token, NULL); GSS_UNLOCK_MUTEX(params->utils); if (GSS_ERROR(maj_stat)) { if (name_token.value) { GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, &name_token); GSS_UNLOCK_MUTEX(params->utils); } SETERROR(text->utils, "GSSAPI Failure"); sasl_gss_free_context_contents(text); return SASL_FAIL; } if (text->user && text->user[0]) { ret = params->canon_user(params->utils->conn, text->user, 0, SASL_CU_AUTHZID, oparams); if (ret == SASL_OK) ret = params->canon_user(params->utils->conn, name_token.value, 0, SASL_CU_AUTHID, oparams); } else { ret = params->canon_user(params->utils->conn, name_token.value, 0, SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams); } GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, &name_token); GSS_UNLOCK_MUTEX(params->utils); if (ret != SASL_OK) return ret; if (text->http_mode) { /* HTTP doesn't do any ssf negotiation */ text->state = SASL_GSSAPI_STATE_AUTHENTICATED; oparams->doneflag = 1; return SASL_OK; } /* Switch to ssf negotiation */ text->state = SASL_GSSAPI_STATE_SSFCAP; } return SASL_CONTINUE; case SASL_GSSAPI_STATE_SSFCAP: { sasl_security_properties_t *secprops = &(params->props); unsigned int alen, external = params->external_ssf; sasl_ssf_t need, allowed; char serverhas, mychoice; real_input_token.value = (void *) serverin; real_input_token.length = serverinlen; GSS_LOCK_MUTEX(params->utils); maj_stat = gss_unwrap(&min_stat, text->gss_ctx, input_token, output_token, NULL, NULL); GSS_UNLOCK_MUTEX(params->utils); if (GSS_ERROR(maj_stat)) { sasl_gss_seterror(text->utils, maj_stat, min_stat); sasl_gss_free_context_contents(text); if (output_token->value) { GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); } return SASL_FAIL; } if (output_token->length != 4) { SETERROR(text->utils, (output_token->length < 4) ? "token too short" : "token too long"); GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); sasl_gss_free_context_contents(text); return SASL_FAIL; } /* taken from kerberos.c */ if (secprops->min_ssf > (K5_MAX_SSF + external)) { return SASL_TOOWEAK; } else if (secprops->min_ssf > secprops->max_ssf) { return SASL_BADPARAM; } /* need bits of layer -- sasl_ssf_t is unsigned so be careful */ if (secprops->max_ssf >= external) { allowed = secprops->max_ssf - external; } else { allowed = 0; } if (secprops->min_ssf >= external) { need = secprops->min_ssf - external; } else { /* good to go */ need = 0; } /* bit mask of server support */ serverhas = ((char *)output_token->value)[0]; /* use the strongest layer available */ if ((text->qop & LAYER_CONFIDENTIALITY) && allowed >= K5_MAX_SSF && need <= K5_MAX_SSF && (serverhas & LAYER_CONFIDENTIALITY)) { const char *ad_compat; /* encryption */ oparams->encode = &gssapi_privacy_encode; oparams->decode = &gssapi_decode; /* FIX ME: Need to extract the proper value here */ oparams->mech_ssf = K5_MAX_SSF; mychoice = LAYER_CONFIDENTIALITY; if (serverhas & LAYER_INTEGRITY) { /* should we send an AD compatible choice of security layers? */ params->utils->getopt(params->utils->getopt_context, "GSSAPI", "ad_compat", &ad_compat, NULL); if (ad_compat && (ad_compat[0] == '1' || ad_compat[0] == 'y' || (ad_compat[0] == 'o' && ad_compat[1] == 'n') || ad_compat[0] == 't')) { mychoice = LAYER_INTEGRITY|LAYER_CONFIDENTIALITY; } } } else if ((text->qop & LAYER_INTEGRITY) && allowed >= 1 && need <= 1 && (serverhas & LAYER_INTEGRITY)) { /* integrity */ oparams->encode = &gssapi_integrity_encode; oparams->decode = &gssapi_decode; oparams->mech_ssf = 1; mychoice = LAYER_INTEGRITY; } else if ((text->qop & LAYER_NONE) && need <= 0 && (serverhas & LAYER_NONE)) { /* no layer */ oparams->encode = NULL; oparams->decode = NULL; oparams->mech_ssf = 0; mychoice = LAYER_NONE; } else { /* there's no appropriate layering for us! */ sasl_gss_free_context_contents(text); return SASL_TOOWEAK; } oparams->maxoutbuf = (((unsigned char *) output_token->value)[1] << 16) | (((unsigned char *) output_token->value)[2] << 8) | (((unsigned char *) output_token->value)[3] << 0); if (oparams->mech_ssf) { maj_stat = gss_wrap_size_limit( &min_stat, text->gss_ctx, 1, GSS_C_QOP_DEFAULT, (OM_uint32) oparams->maxoutbuf, &max_input); if (max_input > oparams->maxoutbuf) { /* Heimdal appears to get this wrong */ oparams->maxoutbuf -= (max_input - oparams->maxoutbuf); } else { /* This code is actually correct */ oparams->maxoutbuf = max_input; } } GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); /* oparams->user is always set, due to canon_user requirements. * Make sure the client actually requested it though, by checking * if our context was set. */ if (text->user && text->user[0]) { alen = strlen(oparams->user); } else { alen = 0; } input_token->length = 4 + alen; input_token->value = (char *)params->utils->malloc((input_token->length + 1)*sizeof(char)); if (input_token->value == NULL) { sasl_gss_free_context_contents(text); return SASL_NOMEM; } if (alen) memcpy((char *)input_token->value+4,oparams->user,alen); /* build up our security properties token */ if (mychoice > 1) { if (params->props.maxbufsize > 0xFFFFFF) { /* make sure maxbufsize isn't too large */ /* maxbufsize = 0xFFFFFF */ ((unsigned char *)input_token->value)[1] = 0xFF; ((unsigned char *)input_token->value)[2] = 0xFF; ((unsigned char *)input_token->value)[3] = 0xFF; } else { ((unsigned char *)input_token->value)[1] = (params->props.maxbufsize >> 16) & 0xFF; ((unsigned char *)input_token->value)[2] = (params->props.maxbufsize >> 8) & 0xFF; ((unsigned char *)input_token->value)[3] = (params->props.maxbufsize >> 0) & 0xFF; } } else { ((unsigned char *)input_token->value)[1] = 0; ((unsigned char *)input_token->value)[2] = 0; ((unsigned char *)input_token->value)[3] = 0; } ((unsigned char *)input_token->value)[0] = mychoice; GSS_LOCK_MUTEX(params->utils); maj_stat = gss_wrap (&min_stat, text->gss_ctx, 0, /* Just integrity checking here */ GSS_C_QOP_DEFAULT, input_token, NULL, output_token); GSS_UNLOCK_MUTEX(params->utils); params->utils->free(input_token->value); input_token->value = NULL; if (GSS_ERROR(maj_stat)) { sasl_gss_seterror(text->utils, maj_stat, min_stat); if (output_token->value) { GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); } sasl_gss_free_context_contents(text); return SASL_FAIL; } if (clientoutlen) { *clientoutlen = output_token->length; } if (output_token->value) { if (clientout) { ret = _plug_buf_alloc(text->utils, &(text->out_buf), &(text->out_buf_len), *clientoutlen); if (ret != SASL_OK) { GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); return ret; } memcpy(text->out_buf, output_token->value, *clientoutlen); *clientout = text->out_buf; } GSS_LOCK_MUTEX(params->utils); gss_release_buffer(&min_stat, output_token); GSS_UNLOCK_MUTEX(params->utils); } text->state = SASL_GSSAPI_STATE_AUTHENTICATED; oparams->doneflag = 1; /* used by layers */ _plug_decode_init(&text->decode_context, text->utils, (params->props.maxbufsize > 0xFFFFFF) ? 0xFFFFFF : params->props.maxbufsize); return SASL_OK; } default: params->utils->log(NULL, SASL_LOG_ERR, "Invalid GSSAPI client step %d\n", text->state); return SASL_FAIL; } return SASL_FAIL; /* should never get here */ } static const unsigned long gssapi_required_prompts[] = { SASL_CB_LIST_END }; static sasl_client_plug_t gssapi_client_plugins[] = { { "GSSAPI", /* mech_name */ K5_MAX_SSF, /* max_ssf */ SASL_SEC_NOPLAINTEXT | SASL_SEC_NOACTIVE | SASL_SEC_NOANONYMOUS | SASL_SEC_MUTUAL_AUTH | SASL_SEC_PASS_CREDENTIALS, /* security_flags */ SASL_FEAT_NEEDSERVERFQDN | SASL_FEAT_WANT_CLIENT_FIRST | SASL_FEAT_ALLOWS_PROXY, /* features */ gssapi_required_prompts, /* required_prompts */ GSS_C_NO_OID, /* glob_context */ &gssapi_client_mech_new, /* mech_new */ &gssapi_client_mech_step, /* mech_step */ &gssapi_common_mech_dispose, /* mech_dispose */ &gssapi_common_mech_free, /* mech_free */ NULL, /* idle */ NULL, /* spare */ NULL /* spare */ } #ifdef HAVE_GSS_SPNEGO ,{ "GSS-SPNEGO", /* mech_name */ K5_MAX_SSF, /* max_ssf */ SASL_SEC_NOPLAINTEXT | SASL_SEC_NOACTIVE | SASL_SEC_NOANONYMOUS | SASL_SEC_MUTUAL_AUTH | SASL_SEC_PASS_CREDENTIALS, /* security_flags */ SASL_FEAT_NEEDSERVERFQDN | SASL_FEAT_WANT_CLIENT_FIRST | SASL_FEAT_ALLOWS_PROXY | SASL_FEAT_SUPPORTS_HTTP, /* features */ gssapi_required_prompts, /* required_prompts */ &gss_spnego_oid, /* glob_context */ &gssapi_client_mech_new, /* mech_new */ &gssapi_client_mech_step, /* mech_step */ &gssapi_common_mech_dispose, /* mech_dispose */ &gssapi_common_mech_free, /* mech_free */ NULL, /* idle */ NULL, /* spare */ NULL /* spare */ } #endif }; int gssapiv2_client_plug_init(const sasl_utils_t *utils __attribute__((unused)), int maxversion, int *out_version, sasl_client_plug_t **pluglist, int *plugcount) { if (maxversion < SASL_CLIENT_PLUG_VERSION) { SETERROR(utils, "Version mismatch in GSSAPI"); return SASL_BADVERS; } *out_version = SASL_CLIENT_PLUG_VERSION; *pluglist = gssapi_client_plugins; #ifdef HAVE_GSS_SPNEGO *plugcount = 2; #else *plugcount = 1; #endif #ifdef GSS_USE_MUTEXES if(!gss_mutex) { gss_mutex = utils->mutex_alloc(); if(!gss_mutex) { return SASL_FAIL; } } #endif return SASL_OK; }