1/*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al. 9 * 10 * This software is licensed as described in the file COPYING, which 11 * you should have received as part of this distribution. The terms 12 * are also available at http://curl.haxx.se/docs/copyright.html. 13 * 14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell 15 * copies of the Software, and permit persons to whom the Software is 16 * furnished to do so, under the terms of the COPYING file. 17 * 18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 19 * KIND, either express or implied. 20 * 21 ***************************************************************************/ 22 23#include "curl_setup.h" 24 25#ifdef HAVE_GSSAPI 26#ifdef HAVE_OLD_GSSMIT 27#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name 28#define NCOMPAT 1 29#endif 30 31#ifndef CURL_DISABLE_HTTP 32 33#include "urldata.h" 34#include "sendf.h" 35#include "curl_gssapi.h" 36#include "rawstr.h" 37#include "curl_base64.h" 38#include "http_negotiate.h" 39#include "curl_memory.h" 40#include "url.h" 41 42#ifdef HAVE_SPNEGO 43# include <spnegohelp.h> 44# ifdef USE_SSLEAY 45# ifdef USE_OPENSSL 46# include <openssl/objects.h> 47# else 48# include <objects.h> 49# endif 50# else 51# error "Can't compile SPNEGO support without OpenSSL." 52# endif 53#endif 54 55#define _MPRINTF_REPLACE /* use our functions only */ 56#include <curl/mprintf.h> 57 58/* The last #include file should be: */ 59#include "memdebug.h" 60 61static int 62get_gss_name(struct connectdata *conn, bool proxy, gss_name_t *server) 63{ 64 struct negotiatedata *neg_ctx = proxy?&conn->data->state.proxyneg: 65 &conn->data->state.negotiate; 66 OM_uint32 major_status, minor_status; 67 gss_buffer_desc token = GSS_C_EMPTY_BUFFER; 68 char name[2048]; 69 const char* service; 70 71 /* GSSAPI implementation by Globus (known as GSI) requires the name to be 72 of form "<service>/<fqdn>" instead of <service>@<fqdn> (ie. slash instead 73 of at-sign). Also GSI servers are often identified as 'host' not 'khttp'. 74 Change following lines if you want to use GSI */ 75 76 /* IIS uses the <service>@<fqdn> form but uses 'http' as the service name */ 77 78 if(neg_ctx->gss) 79 service = "KHTTP"; 80 else 81 service = "HTTP"; 82 83 token.length = strlen(service) + 1 + strlen(proxy ? conn->proxy.name : 84 conn->host.name) + 1; 85 if(token.length + 1 > sizeof(name)) 86 return EMSGSIZE; 87 88 snprintf(name, sizeof(name), "%s@%s", service, proxy ? conn->proxy.name : 89 conn->host.name); 90 91 token.value = (void *) name; 92 major_status = gss_import_name(&minor_status, 93 &token, 94 GSS_C_NT_HOSTBASED_SERVICE, 95 server); 96 97 return GSS_ERROR(major_status) ? -1 : 0; 98} 99 100static void 101log_gss_error(struct connectdata *conn, OM_uint32 error_status, 102 const char *prefix) 103{ 104 OM_uint32 maj_stat, min_stat; 105 OM_uint32 msg_ctx = 0; 106 gss_buffer_desc status_string; 107 char buf[1024]; 108 size_t len; 109 110 snprintf(buf, sizeof(buf), "%s", prefix); 111 len = strlen(buf); 112 do { 113 maj_stat = gss_display_status(&min_stat, 114 error_status, 115 GSS_C_MECH_CODE, 116 GSS_C_NO_OID, 117 &msg_ctx, 118 &status_string); 119 if(sizeof(buf) > len + status_string.length + 1) { 120 snprintf(buf + len, sizeof(buf) - len, 121 ": %s", (char*) status_string.value); 122 len += status_string.length; 123 } 124 gss_release_buffer(&min_stat, &status_string); 125 } while(!GSS_ERROR(maj_stat) && msg_ctx != 0); 126 127 infof(conn->data, "%s\n", buf); 128} 129 130/* returning zero (0) means success, everything else is treated as "failure" 131 with no care exactly what the failure was */ 132int Curl_input_negotiate(struct connectdata *conn, bool proxy, 133 const char *header) 134{ 135 struct SessionHandle *data = conn->data; 136 struct negotiatedata *neg_ctx = proxy?&data->state.proxyneg: 137 &data->state.negotiate; 138 OM_uint32 major_status, minor_status, discard_st; 139 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; 140 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; 141 int ret; 142 size_t len; 143 size_t rawlen = 0; 144 bool gss; 145 const char* protocol; 146 CURLcode error; 147 148 if(checkprefix("GSS-Negotiate", header)) { 149 protocol = "GSS-Negotiate"; 150 gss = TRUE; 151 } 152 else if(checkprefix("Negotiate", header)) { 153 protocol = "Negotiate"; 154 gss = FALSE; 155 } 156 else 157 return -1; 158 159 if(neg_ctx->context) { 160 if(neg_ctx->gss != gss) { 161 return -1; 162 } 163 } 164 else { 165 neg_ctx->protocol = protocol; 166 neg_ctx->gss = gss; 167 } 168 169 if(neg_ctx->context && neg_ctx->status == GSS_S_COMPLETE) { 170 /* We finished successfully our part of authentication, but server 171 * rejected it (since we're again here). Exit with an error since we 172 * can't invent anything better */ 173 Curl_cleanup_negotiate(data); 174 return -1; 175 } 176 177 if(neg_ctx->server_name == NULL && 178 (ret = get_gss_name(conn, proxy, &neg_ctx->server_name))) 179 return ret; 180 181 header += strlen(neg_ctx->protocol); 182 while(*header && ISSPACE(*header)) 183 header++; 184 185 len = strlen(header); 186 if(len > 0) { 187 error = Curl_base64_decode(header, 188 (unsigned char **)&input_token.value, &rawlen); 189 if(error || rawlen == 0) 190 return -1; 191 input_token.length = rawlen; 192 193 DEBUGASSERT(input_token.value != NULL); 194 195#ifdef HAVE_SPNEGO /* Handle SPNEGO */ 196 if(checkprefix("Negotiate", header)) { 197 unsigned char *spnegoToken = NULL; 198 size_t spnegoTokenLength = 0; 199 gss_buffer_desc mechToken = GSS_C_EMPTY_BUFFER; 200 201 spnegoToken = malloc(input_token.length); 202 if(spnegoToken == NULL) { 203 Curl_safefree(input_token.value); 204 return CURLE_OUT_OF_MEMORY; 205 } 206 memcpy(spnegoToken, input_token.value, input_token.length); 207 spnegoTokenLength = input_token.length; 208 209 if(!parseSpnegoTargetToken(spnegoToken, 210 spnegoTokenLength, 211 NULL, 212 NULL, 213 (unsigned char**)&mechToken.value, 214 &mechToken.length, 215 NULL, 216 NULL)) { 217 Curl_safefree(spnegoToken); 218 infof(data, "Parse SPNEGO Target Token failed\n"); 219 } 220 else if(!mechToken.value || !mechToken.length) { 221 Curl_safefree(spnegoToken); 222 if(mechToken.value) 223 gss_release_buffer(&discard_st, &mechToken); 224 infof(data, "Parse SPNEGO Target Token succeeded (NULL token)\n"); 225 } 226 else { 227 Curl_safefree(spnegoToken); 228 Curl_safefree(input_token.value); 229 input_token.value = malloc(mechToken.length); 230 if(input_token.value == NULL) { 231 gss_release_buffer(&discard_st, &mechToken); 232 return CURLE_OUT_OF_MEMORY; 233 } 234 memcpy(input_token.value, mechToken.value, mechToken.length); 235 input_token.length = mechToken.length; 236 gss_release_buffer(&discard_st, &mechToken); 237 infof(data, "Parse SPNEGO Target Token succeeded\n"); 238 } 239 } 240#endif 241 } 242 243 major_status = Curl_gss_init_sec_context(data, 244 &minor_status, 245 &neg_ctx->context, 246 neg_ctx->server_name, 247 GSS_C_NO_CHANNEL_BINDINGS, 248 &input_token, 249 &output_token, 250 NULL); 251 Curl_safefree(input_token.value); 252 253 neg_ctx->status = major_status; 254 if(GSS_ERROR(major_status)) { 255 if(output_token.value) 256 gss_release_buffer(&discard_st, &output_token); 257 log_gss_error(conn, minor_status, "gss_init_sec_context() failed: "); 258 return -1; 259 } 260 261 if(!output_token.value || !output_token.length) { 262 if(output_token.value) 263 gss_release_buffer(&discard_st, &output_token); 264 return -1; 265 } 266 267 neg_ctx->output_token = output_token; 268 return 0; 269} 270 271 272CURLcode Curl_output_negotiate(struct connectdata *conn, bool proxy) 273{ 274 struct negotiatedata *neg_ctx = proxy?&conn->data->state.proxyneg: 275 &conn->data->state.negotiate; 276 char *encoded = NULL; 277 size_t len = 0; 278 char *userp; 279 CURLcode error; 280 OM_uint32 discard_st; 281 282#ifdef HAVE_SPNEGO /* Handle SPNEGO */ 283 if(checkprefix("Negotiate", neg_ctx->protocol)) { 284 ASN1_OBJECT *object = NULL; 285 unsigned char *responseToken = NULL; 286 size_t responseTokenLength = 0; 287 gss_buffer_desc spnegoToken = GSS_C_EMPTY_BUFFER; 288 289 responseToken = malloc(neg_ctx->output_token.length); 290 if(responseToken == NULL) 291 return CURLE_OUT_OF_MEMORY; 292 memcpy(responseToken, neg_ctx->output_token.value, 293 neg_ctx->output_token.length); 294 responseTokenLength = neg_ctx->output_token.length; 295 296 object = OBJ_txt2obj("1.2.840.113554.1.2.2", 1); 297 if(!object) { 298 Curl_safefree(responseToken); 299 return CURLE_OUT_OF_MEMORY; 300 } 301 302 if(!makeSpnegoInitialToken(object, 303 responseToken, 304 responseTokenLength, 305 (unsigned char**)&spnegoToken.value, 306 &spnegoToken.length)) { 307 Curl_safefree(responseToken); 308 ASN1_OBJECT_free(object); 309 infof(conn->data, "Make SPNEGO Initial Token failed\n"); 310 } 311 else if(!spnegoToken.value || !spnegoToken.length) { 312 Curl_safefree(responseToken); 313 ASN1_OBJECT_free(object); 314 if(spnegoToken.value) 315 gss_release_buffer(&discard_st, &spnegoToken); 316 infof(conn->data, "Make SPNEGO Initial Token succeeded (NULL token)\n"); 317 } 318 else { 319 Curl_safefree(responseToken); 320 ASN1_OBJECT_free(object); 321 gss_release_buffer(&discard_st, &neg_ctx->output_token); 322 neg_ctx->output_token.value = spnegoToken.value; 323 neg_ctx->output_token.length = spnegoToken.length; 324 infof(conn->data, "Make SPNEGO Initial Token succeeded\n"); 325 } 326 } 327#endif 328 error = Curl_base64_encode(conn->data, 329 neg_ctx->output_token.value, 330 neg_ctx->output_token.length, 331 &encoded, &len); 332 if(error) { 333 gss_release_buffer(&discard_st, &neg_ctx->output_token); 334 neg_ctx->output_token.value = NULL; 335 neg_ctx->output_token.length = 0; 336 return error; 337 } 338 339 if(!encoded || !len) { 340 gss_release_buffer(&discard_st, &neg_ctx->output_token); 341 neg_ctx->output_token.value = NULL; 342 neg_ctx->output_token.length = 0; 343 return CURLE_REMOTE_ACCESS_DENIED; 344 } 345 346 userp = aprintf("%sAuthorization: %s %s\r\n", proxy ? "Proxy-" : "", 347 neg_ctx->protocol, encoded); 348 if(proxy) { 349 Curl_safefree(conn->allocptr.proxyuserpwd); 350 conn->allocptr.proxyuserpwd = userp; 351 } 352 else { 353 Curl_safefree(conn->allocptr.userpwd); 354 conn->allocptr.userpwd = userp; 355 } 356 357 Curl_safefree(encoded); 358 Curl_cleanup_negotiate(conn->data); 359 360 return (userp == NULL) ? CURLE_OUT_OF_MEMORY : CURLE_OK; 361} 362 363static void cleanup(struct negotiatedata *neg_ctx) 364{ 365 OM_uint32 minor_status; 366 if(neg_ctx->context != GSS_C_NO_CONTEXT) 367 gss_delete_sec_context(&minor_status, &neg_ctx->context, GSS_C_NO_BUFFER); 368 369 if(neg_ctx->output_token.value) 370 gss_release_buffer(&minor_status, &neg_ctx->output_token); 371 372 if(neg_ctx->server_name != GSS_C_NO_NAME) 373 gss_release_name(&minor_status, &neg_ctx->server_name); 374 375 memset(neg_ctx, 0, sizeof(*neg_ctx)); 376} 377 378void Curl_cleanup_negotiate(struct SessionHandle *data) 379{ 380 cleanup(&data->state.negotiate); 381 cleanup(&data->state.proxyneg); 382} 383 384 385#endif 386#endif 387