/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://curl.haxx.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * * $Id: http_negotiate.c,v 1.11 2004/08/05 18:52:54 bagder Exp $ ***************************************************************************/ #include "setup.h" #ifdef HAVE_GSSAPI #ifdef HAVE_GSSMIT #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name #endif #ifndef CURL_DISABLE_HTTP /* -- WIN32 approved -- */ #include #include #include #include #include #include #include "urldata.h" #include "sendf.h" #include "strequal.h" #include "base64.h" #include "http_negotiate.h" #include "memory.h" #define _MPRINTF_REPLACE /* use our functions only */ #include /* The last #include file should be: */ #include "memdebug.h" static int get_gss_name( struct connectdata *conn, gss_name_t *server ) { struct negotiatedata *neg_ctx = &conn->data->state.negotiate; OM_uint32 major_status, minor_status; gss_buffer_desc token = GSS_C_EMPTY_BUFFER; char name[2048]; const char* service; /* GSSAPI implementation by Globus (known as GSI) requires the name to be of form "/" instead of @ (ie. slash instead of at-sign). Also GSI servers are often identified as 'host' not 'khttp'. Change following lines if you want to use GSI */ /* IIS uses the @ form but uses 'http' as the service name */ if ( neg_ctx->gss ) { service = "KHTTP"; } else { service = "HTTP"; } token.length = strlen( service ) + 1 + strlen( conn->host.name ) + 1; if ( token.length + 1 > sizeof( name ) ) { return EMSGSIZE; } snprintf( name, sizeof( name ), "%s@%s", service, conn->host.name ); token.value = (void *) name; major_status = gss_import_name( &minor_status, &token, GSS_C_NT_HOSTBASED_SERVICE, server ); return GSS_ERROR( major_status ) ? -1 : 0; } static void log_gss_error( struct connectdata *conn, OM_uint32 error_status, char *prefix ) { OM_uint32 maj_stat, min_stat; OM_uint32 msg_ctx = 0; gss_buffer_desc status_string; char buf[1024]; size_t len; snprintf( buf, sizeof( buf ), "%s", prefix ); len = strlen( buf ); do { maj_stat = gss_display_status( &min_stat, error_status, GSS_C_MECH_CODE, GSS_C_NO_OID, &msg_ctx, &status_string ); if ( sizeof( buf ) > len + status_string.length + 1 ) { snprintf( buf + len, sizeof( buf ) - len, ": %s", (char*) status_string.value ); len += status_string.length; } gss_release_buffer( &min_stat, &status_string ); } while ( !GSS_ERROR( maj_stat ) && msg_ctx != 0 ); infof( conn->data, buf ); } int Curl_input_negotiate( struct connectdata *conn, char *header ) { struct negotiatedata *neg_ctx = &conn->data->state.negotiate; OM_uint32 major_status, minor_status, minor_status2; gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; int ret; size_t len; bool gss; const char* protocol; while ( *header && isspace( (int)*header ) ) header++; if ( checkprefix( "GSS-Negotiate", header ) ) { protocol = "GSS-Negotiate"; gss = TRUE; } else if ( checkprefix( "Negotiate", header ) ) { protocol = "Negotiate"; gss = FALSE; } else { return -1; } if ( neg_ctx->context ) { if ( neg_ctx->gss != gss ) { return -1; } } else { neg_ctx->protocol = protocol; neg_ctx->gss = gss; } if ( neg_ctx->context && neg_ctx->status == GSS_S_COMPLETE ) { /* We finished succesfully our part of authentication, but server * rejected it (since we're again here). Exit with an error since we * can't invent anything better */ Curl_cleanup_negotiate( conn->data ); return -1; } if ( neg_ctx->server_name == NULL && ( ret = get_gss_name( conn, &neg_ctx->server_name ) ) ) { return ret; } header += strlen( neg_ctx->protocol ); while ( *header && isspace( (int)*header ) ) header++; len = strlen( header ); if ( len > 0 ) { int rawlen; input_token.length = ( len + 3 ) / 4 * 3; input_token.value = malloc( input_token.length ); if ( input_token.value == NULL ) { return ENOMEM; } rawlen = Curl_base64_decode( header, input_token.value ); if ( rawlen < 0 ) { return -1; } input_token.length = rawlen; #ifdef HAVE_SPNEGO /* Handle SPNEGO */ if ( checkprefix( "Negotiate", header ) ) { ASN1_OBJECT * object = NULL; int rc = 1; unsigned char * spnegoToken = NULL; size_t spnegoTokenLength = 0; unsigned char * mechToken = NULL; size_t mechTokenLength = 0; spnegoToken = malloc( input_token.length ); if ( input_token.value == NULL ) { return ENOMEM; } spnegoTokenLength = input_token.length; object = OBJ_txt2obj( "1.2.840.113554.1.2.2", 1 ); if ( !parseSpnegoTargetToken( spnegoToken, spnegoTokenLength, NULL, NULL, &mechToken, &mechTokenLength, NULL, NULL ) ) { free( spnegoToken ); spnegoToken = NULL; infof( conn->data, "Parse SPNEGO Target Token failed\n" ); } else { free( input_token.value ); input_token.value = NULL; input_token.value = malloc( mechTokenLength ); memcpy( input_token.value, mechToken,mechTokenLength ); input_token.length = mechTokenLength; free( mechToken ); mechToken = NULL; infof( conn->data, "Parse SPNEGO Target Token succeded\n" ); } } #endif } major_status = gss_init_sec_context( &minor_status, GSS_C_NO_CREDENTIAL, &neg_ctx->context, neg_ctx->server_name, GSS_C_NO_OID, GSS_C_DELEG_FLAG, 0, GSS_C_NO_CHANNEL_BINDINGS, &input_token, NULL, &output_token, NULL, NULL ); if ( input_token.length > 0 ) { gss_release_buffer( &minor_status2, &input_token ); } neg_ctx->status = major_status; if ( GSS_ERROR( major_status ) ) { /* Curl_cleanup_negotiate(conn->data) ??? */ log_gss_error( conn, minor_status, (char *)"gss_init_sec_context() failed: " ); return -1; } if ( output_token.length == 0 ) { return -1; } neg_ctx->output_token = output_token; /* conn->bits.close = FALSE; */ return 0; } CURLcode Curl_output_negotiate( struct connectdata *conn ) { struct negotiatedata *neg_ctx = &conn->data->state.negotiate; OM_uint32 minor_status; char *encoded = NULL; int len; #ifdef HAVE_SPNEGO /* Handle SPNEGO */ if ( checkprefix( "Negotiate",neg_ctx->protocol ) ) { ASN1_OBJECT * object = NULL; int rc = 1; unsigned char * spnegoToken = NULL; size_t spnegoTokenLength = 0; unsigned char * responseToken = NULL; size_t responseTokenLength = 0; responseToken = malloc( neg_ctx->output_token.length ); if ( responseToken == NULL ) { return CURLE_OUT_OF_MEMORY; } memcpy( responseToken, neg_ctx->output_token.value, neg_ctx->output_token.length ); responseTokenLength = neg_ctx->output_token.length; object = OBJ_txt2obj( "1.2.840.113554.1.2.2", 1 ); if ( !makeSpnegoInitialToken( object, responseToken, responseTokenLength, &spnegoToken, &spnegoTokenLength ) ) { free( responseToken ); responseToken = NULL; infof( conn->data, "Make SPNEGO Initial Token failed\n" ); } else { free( neg_ctx->output_token.value ); responseToken = NULL; neg_ctx->output_token.value = malloc( spnegoTokenLength ); memcpy( neg_ctx->output_token.value, spnegoToken,spnegoTokenLength ); neg_ctx->output_token.length = spnegoTokenLength; free( spnegoToken ); spnegoToken = NULL; infof( conn->data, "Make SPNEGO Initial Token succeded\n" ); } } #endif len = Curl_base64_encode( neg_ctx->output_token.value, neg_ctx->output_token.length, &encoded ); if ( len < 0 ) { return CURLE_OUT_OF_MEMORY; } conn->allocptr.userpwd = aprintf( "Authorization: %s %s\r\n", neg_ctx->protocol, encoded ); free( encoded ); gss_release_buffer( &minor_status, &neg_ctx->output_token ); return ( conn->allocptr.userpwd == NULL ) ? CURLE_OUT_OF_MEMORY : CURLE_OK; } void Curl_cleanup_negotiate( struct SessionHandle *data ) { OM_uint32 minor_status; struct negotiatedata *neg_ctx = &data->state.negotiate; if ( neg_ctx->context != GSS_C_NO_CONTEXT ) { gss_delete_sec_context( &minor_status, &neg_ctx->context, GSS_C_NO_BUFFER ); } if ( neg_ctx->output_token.length != 0 ) { gss_release_buffer( &minor_status, &neg_ctx->output_token ); } if ( neg_ctx->server_name != GSS_C_NO_NAME ) { gss_release_name( &minor_status, &neg_ctx->server_name ); } memset( neg_ctx, 0, sizeof( *neg_ctx ) ); } #endif #endif