1 /*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 2012 - 2015, Daniel Stenberg, <daniel (at) 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 * RFC2195 CRAM-MD5 authentication 22 * RFC2617 Basic and Digest Access Authentication 23 * RFC2831 DIGEST-MD5 authentication 24 * RFC4422 Simple Authentication and Security Layer (SASL) 25 * RFC4616 PLAIN authentication 26 * RFC6749 OAuth 2.0 Authorization Framework 27 * Draft LOGIN SASL Mechanism <draft-murchison-sasl-login-00.txt> 28 * 29 ***************************************************************************/ 30 31 #include "curl_setup.h" 32 33 #include <curl/curl.h> 34 #include "urldata.h" 35 36 #include "curl_base64.h" 37 #include "curl_md5.h" 38 #include "vtls/vtls.h" 39 #include "curl_hmac.h" 40 #include "curl_sasl.h" 41 #include "warnless.h" 42 #include "strtok.h" 43 #include "strequal.h" 44 #include "rawstr.h" 45 #include "sendf.h" 46 #include "non-ascii.h" /* included for Curl_convert_... prototypes */ 47 #include "curl_printf.h" 48 49 /* The last #include files should be: */ 50 #include "curl_memory.h" 51 #include "memdebug.h" 52 53 /* Supported mechanisms */ 54 const struct { 55 const char *name; /* Name */ 56 size_t len; /* Name length */ 57 unsigned int bit; /* Flag bit */ 58 } mechtable[] = { 59 { "LOGIN", 5, SASL_MECH_LOGIN }, 60 { "PLAIN", 5, SASL_MECH_PLAIN }, 61 { "CRAM-MD5", 8, SASL_MECH_CRAM_MD5 }, 62 { "DIGEST-MD5", 10, SASL_MECH_DIGEST_MD5 }, 63 { "GSSAPI", 6, SASL_MECH_GSSAPI }, 64 { "EXTERNAL", 8, SASL_MECH_EXTERNAL }, 65 { "NTLM", 4, SASL_MECH_NTLM }, 66 { "XOAUTH2", 7, SASL_MECH_XOAUTH2 }, 67 { ZERO_NULL, 0, 0 } 68 }; 69 70 #if !defined(CURL_DISABLE_CRYPTO_AUTH) && !defined(USE_WINDOWS_SSPI) 71 #define DIGEST_QOP_VALUE_AUTH (1 << 0) 72 #define DIGEST_QOP_VALUE_AUTH_INT (1 << 1) 73 #define DIGEST_QOP_VALUE_AUTH_CONF (1 << 2) 74 75 #define DIGEST_QOP_VALUE_STRING_AUTH "auth" 76 #define DIGEST_QOP_VALUE_STRING_AUTH_INT "auth-int" 77 #define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf" 78 79 /* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines. 80 It converts digest text to ASCII so the MD5 will be correct for 81 what ultimately goes over the network. 82 */ 83 #define CURL_OUTPUT_DIGEST_CONV(a, b) \ 84 result = Curl_convert_to_network(a, (char *)b, strlen((const char*)b)); \ 85 if(result) { \ 86 free(b); \ 87 return result; \ 88 } 89 90 #endif 91 92 #if !defined(CURL_DISABLE_CRYPTO_AUTH) 93 /* 94 * Returns 0 on success and then the buffers are filled in fine. 95 * 96 * Non-zero means failure to parse. 97 */ 98 int Curl_sasl_digest_get_pair(const char *str, char *value, char *content, 99 const char **endptr) 100 { 101 int c; 102 bool starts_with_quote = FALSE; 103 bool escape = FALSE; 104 105 for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--); ) 106 *value++ = *str++; 107 *value = 0; 108 109 if('=' != *str++) 110 /* eek, no match */ 111 return 1; 112 113 if('\"' == *str) { 114 /* this starts with a quote so it must end with one as well! */ 115 str++; 116 starts_with_quote = TRUE; 117 } 118 119 for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) { 120 switch(*str) { 121 case '\\': 122 if(!escape) { 123 /* possibly the start of an escaped quote */ 124 escape = TRUE; 125 *content++ = '\\'; /* even though this is an escape character, we still 126 store it as-is in the target buffer */ 127 continue; 128 } 129 break; 130 case ',': 131 if(!starts_with_quote) { 132 /* this signals the end of the content if we didn't get a starting 133 quote and then we do "sloppy" parsing */ 134 c = 0; /* the end */ 135 continue; 136 } 137 break; 138 case '\r': 139 case '\n': 140 /* end of string */ 141 c = 0; 142 continue; 143 case '\"': 144 if(!escape && starts_with_quote) { 145 /* end of string */ 146 c = 0; 147 continue; 148 } 149 break; 150 } 151 escape = FALSE; 152 *content++ = *str; 153 } 154 *content = 0; 155 156 *endptr = str; 157 158 return 0; /* all is fine! */ 159 } 160 #endif 161 162 #if !defined(CURL_DISABLE_CRYPTO_AUTH) && !defined(USE_WINDOWS_SSPI) 163 /* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/ 164 static void sasl_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */ 165 unsigned char *dest) /* 33 bytes */ 166 { 167 int i; 168 for(i = 0; i < 16; i++) 169 snprintf((char *)&dest[i*2], 3, "%02x", source[i]); 170 } 171 172 /* Perform quoted-string escaping as described in RFC2616 and its errata */ 173 static char *sasl_digest_string_quoted(const char *source) 174 { 175 char *dest, *d; 176 const char *s = source; 177 size_t n = 1; /* null terminator */ 178 179 /* Calculate size needed */ 180 while(*s) { 181 ++n; 182 if(*s == '"' || *s == '\\') { 183 ++n; 184 } 185 ++s; 186 } 187 188 dest = malloc(n); 189 if(dest) { 190 s = source; 191 d = dest; 192 while(*s) { 193 if(*s == '"' || *s == '\\') { 194 *d++ = '\\'; 195 } 196 *d++ = *s++; 197 } 198 *d = 0; 199 } 200 201 return dest; 202 } 203 204 /* Retrieves the value for a corresponding key from the challenge string 205 * returns TRUE if the key could be found, FALSE if it does not exists 206 */ 207 static bool sasl_digest_get_key_value(const char *chlg, 208 const char *key, 209 char *value, 210 size_t max_val_len, 211 char end_char) 212 { 213 char *find_pos; 214 size_t i; 215 216 find_pos = strstr(chlg, key); 217 if(!find_pos) 218 return FALSE; 219 220 find_pos += strlen(key); 221 222 for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i) 223 value[i] = *find_pos++; 224 value[i] = '\0'; 225 226 return TRUE; 227 } 228 229 static CURLcode sasl_digest_get_qop_values(const char *options, int *value) 230 { 231 char *tmp; 232 char *token; 233 char *tok_buf; 234 235 /* Initialise the output */ 236 *value = 0; 237 238 /* Tokenise the list of qop values. Use a temporary clone of the buffer since 239 strtok_r() ruins it. */ 240 tmp = strdup(options); 241 if(!tmp) 242 return CURLE_OUT_OF_MEMORY; 243 244 token = strtok_r(tmp, ",", &tok_buf); 245 while(token != NULL) { 246 if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH)) 247 *value |= DIGEST_QOP_VALUE_AUTH; 248 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) 249 *value |= DIGEST_QOP_VALUE_AUTH_INT; 250 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF)) 251 *value |= DIGEST_QOP_VALUE_AUTH_CONF; 252 253 token = strtok_r(NULL, ",", &tok_buf); 254 } 255 256 free(tmp); 257 258 return CURLE_OK; 259 } 260 #endif /* !CURL_DISABLE_CRYPTO_AUTH && !USE_WINDOWS_SSPI */ 261 262 #if !defined(USE_WINDOWS_SSPI) 263 /* 264 * Curl_sasl_build_spn() 265 * 266 * This is used to build a SPN string in the format service/host. 267 * 268 * Parameters: 269 * 270 * service [in] - The service type such as www, smtp, pop or imap. 271 * host [in] - The host name or realm. 272 * 273 * Returns a pointer to the newly allocated SPN. 274 */ 275 char *Curl_sasl_build_spn(const char *service, const char *host) 276 { 277 /* Generate and return our SPN */ 278 return aprintf("%s/%s", service, host); 279 } 280 #endif 281 282 /* 283 * sasl_create_plain_message() 284 * 285 * This is used to generate an already encoded PLAIN message ready 286 * for sending to the recipient. 287 * 288 * Parameters: 289 * 290 * data [in] - The session handle. 291 * userp [in] - The user name. 292 * passdwp [in] - The user's password. 293 * outptr [in/out] - The address where a pointer to newly allocated memory 294 * holding the result will be stored upon completion. 295 * outlen [out] - The length of the output message. 296 * 297 * Returns CURLE_OK on success. 298 */ 299 static CURLcode sasl_create_plain_message(struct SessionHandle *data, 300 const char *userp, 301 const char *passwdp, 302 char **outptr, size_t *outlen) 303 { 304 CURLcode result; 305 char *plainauth; 306 size_t ulen; 307 size_t plen; 308 309 ulen = strlen(userp); 310 plen = strlen(passwdp); 311 312 plainauth = malloc(2 * ulen + plen + 2); 313 if(!plainauth) { 314 *outlen = 0; 315 *outptr = NULL; 316 return CURLE_OUT_OF_MEMORY; 317 } 318 319 /* Calculate the reply */ 320 memcpy(plainauth, userp, ulen); 321 plainauth[ulen] = '\0'; 322 memcpy(plainauth + ulen + 1, userp, ulen); 323 plainauth[2 * ulen + 1] = '\0'; 324 memcpy(plainauth + 2 * ulen + 2, passwdp, plen); 325 326 /* Base64 encode the reply */ 327 result = Curl_base64_encode(data, plainauth, 2 * ulen + plen + 2, outptr, 328 outlen); 329 free(plainauth); 330 return result; 331 } 332 333 /* 334 * sasl_create_login_message() 335 * 336 * This is used to generate an already encoded LOGIN message containing the 337 * user name or password ready for sending to the recipient. 338 * 339 * Parameters: 340 * 341 * data [in] - The session handle. 342 * valuep [in] - The user name or user's password. 343 * outptr [in/out] - The address where a pointer to newly allocated memory 344 * holding the result will be stored upon completion. 345 * outlen [out] - The length of the output message. 346 * 347 * Returns CURLE_OK on success. 348 */ 349 static CURLcode sasl_create_login_message(struct SessionHandle *data, 350 const char *valuep, char **outptr, 351 size_t *outlen) 352 { 353 size_t vlen = strlen(valuep); 354 355 if(!vlen) { 356 /* Calculate an empty reply */ 357 *outptr = strdup("="); 358 if(*outptr) { 359 *outlen = (size_t) 1; 360 return CURLE_OK; 361 } 362 363 *outlen = 0; 364 return CURLE_OUT_OF_MEMORY; 365 } 366 367 /* Base64 encode the value */ 368 return Curl_base64_encode(data, valuep, vlen, outptr, outlen); 369 } 370 371 /* 372 * sasl_create_external_message() 373 * 374 * This is used to generate an already encoded EXTERNAL message containing 375 * the user name ready for sending to the recipient. 376 * 377 * Parameters: 378 * 379 * data [in] - The session handle. 380 * user [in] - The user name. 381 * outptr [in/out] - The address where a pointer to newly allocated memory 382 * holding the result will be stored upon completion. 383 * outlen [out] - The length of the output message. 384 * 385 * Returns CURLE_OK on success. 386 */ 387 static CURLcode sasl_create_external_message(struct SessionHandle *data, 388 const char *user, char **outptr, 389 size_t *outlen) 390 { 391 /* This is the same formatting as the login message. */ 392 return sasl_create_login_message(data, user, outptr, outlen); 393 } 394 395 #ifndef CURL_DISABLE_CRYPTO_AUTH 396 /* 397 * sasl_decode_cram_md5_message() 398 * 399 * This is used to decode an already encoded CRAM-MD5 challenge message. 400 * 401 * Parameters: 402 * 403 * chlg64 [in] - The base64 encoded challenge message. 404 * outptr [in/out] - The address where a pointer to newly allocated memory 405 * holding the result will be stored upon completion. 406 * outlen [out] - The length of the output message. 407 * 408 * Returns CURLE_OK on success. 409 */ 410 static CURLcode sasl_decode_cram_md5_message(const char *chlg64, char **outptr, 411 size_t *outlen) 412 { 413 CURLcode result = CURLE_OK; 414 size_t chlg64len = strlen(chlg64); 415 416 *outptr = NULL; 417 *outlen = 0; 418 419 /* Decode the challenge if necessary */ 420 if(chlg64len && *chlg64 != '=') 421 result = Curl_base64_decode(chlg64, (unsigned char **) outptr, outlen); 422 423 return result; 424 } 425 426 /* 427 * sasl_create_cram_md5_message() 428 * 429 * This is used to generate an already encoded CRAM-MD5 response message ready 430 * for sending to the recipient. 431 * 432 * Parameters: 433 * 434 * data [in] - The session handle. 435 * chlg [in] - The challenge. 436 * userp [in] - The user name. 437 * passdwp [in] - The user's password. 438 * outptr [in/out] - The address where a pointer to newly allocated memory 439 * holding the result will be stored upon completion. 440 * outlen [out] - The length of the output message. 441 * 442 * Returns CURLE_OK on success. 443 */ 444 static CURLcode sasl_create_cram_md5_message(struct SessionHandle *data, 445 const char *chlg, 446 const char *userp, 447 const char *passwdp, 448 char **outptr, size_t *outlen) 449 { 450 CURLcode result = CURLE_OK; 451 size_t chlglen = 0; 452 HMAC_context *ctxt; 453 unsigned char digest[MD5_DIGEST_LEN]; 454 char *response; 455 456 if(chlg) 457 chlglen = strlen(chlg); 458 459 /* Compute the digest using the password as the key */ 460 ctxt = Curl_HMAC_init(Curl_HMAC_MD5, 461 (const unsigned char *) passwdp, 462 curlx_uztoui(strlen(passwdp))); 463 if(!ctxt) 464 return CURLE_OUT_OF_MEMORY; 465 466 /* Update the digest with the given challenge */ 467 if(chlglen > 0) 468 Curl_HMAC_update(ctxt, (const unsigned char *) chlg, 469 curlx_uztoui(chlglen)); 470 471 /* Finalise the digest */ 472 Curl_HMAC_final(ctxt, digest); 473 474 /* Generate the response */ 475 response = aprintf( 476 "%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 477 userp, digest[0], digest[1], digest[2], digest[3], digest[4], 478 digest[5], digest[6], digest[7], digest[8], digest[9], digest[10], 479 digest[11], digest[12], digest[13], digest[14], digest[15]); 480 if(!response) 481 return CURLE_OUT_OF_MEMORY; 482 483 /* Base64 encode the response */ 484 result = Curl_base64_encode(data, response, 0, outptr, outlen); 485 486 free(response); 487 488 return result; 489 } 490 491 #ifndef USE_WINDOWS_SSPI 492 /* 493 * sasl_decode_digest_md5_message() 494 * 495 * This is used internally to decode an already encoded DIGEST-MD5 challenge 496 * message into the seperate attributes. 497 * 498 * Parameters: 499 * 500 * chlg64 [in] - The base64 encoded challenge message. 501 * nonce [in/out] - The buffer where the nonce will be stored. 502 * nlen [in] - The length of the nonce buffer. 503 * realm [in/out] - The buffer where the realm will be stored. 504 * rlen [in] - The length of the realm buffer. 505 * alg [in/out] - The buffer where the algorithm will be stored. 506 * alen [in] - The length of the algorithm buffer. 507 * qop [in/out] - The buffer where the qop-options will be stored. 508 * qlen [in] - The length of the qop buffer. 509 * 510 * Returns CURLE_OK on success. 511 */ 512 static CURLcode sasl_decode_digest_md5_message(const char *chlg64, 513 char *nonce, size_t nlen, 514 char *realm, size_t rlen, 515 char *alg, size_t alen, 516 char *qop, size_t qlen) 517 { 518 CURLcode result = CURLE_OK; 519 unsigned char *chlg = NULL; 520 size_t chlglen = 0; 521 size_t chlg64len = strlen(chlg64); 522 523 /* Decode the base-64 encoded challenge message */ 524 if(chlg64len && *chlg64 != '=') { 525 result = Curl_base64_decode(chlg64, &chlg, &chlglen); 526 if(result) 527 return result; 528 } 529 530 /* Ensure we have a valid challenge message */ 531 if(!chlg) 532 return CURLE_BAD_CONTENT_ENCODING; 533 534 /* Retrieve nonce string from the challenge */ 535 if(!sasl_digest_get_key_value((char *)chlg, "nonce=\"", nonce, nlen, '\"')) { 536 free(chlg); 537 return CURLE_BAD_CONTENT_ENCODING; 538 } 539 540 /* Retrieve realm string from the challenge */ 541 if(!sasl_digest_get_key_value((char *)chlg, "realm=\"", realm, rlen, '\"')) { 542 /* Challenge does not have a realm, set empty string [RFC2831] page 6 */ 543 strcpy(realm, ""); 544 } 545 546 /* Retrieve algorithm string from the challenge */ 547 if(!sasl_digest_get_key_value((char *)chlg, "algorithm=", alg, alen, ',')) { 548 free(chlg); 549 return CURLE_BAD_CONTENT_ENCODING; 550 } 551 552 /* Retrieve qop-options string from the challenge */ 553 if(!sasl_digest_get_key_value((char *)chlg, "qop=\"", qop, qlen, '\"')) { 554 free(chlg); 555 return CURLE_BAD_CONTENT_ENCODING; 556 } 557 558 free(chlg); 559 560 return CURLE_OK; 561 } 562 563 /* 564 * Curl_sasl_create_digest_md5_message() 565 * 566 * This is used to generate an already encoded DIGEST-MD5 response message 567 * ready for sending to the recipient. 568 * 569 * Parameters: 570 * 571 * data [in] - The session handle. 572 * chlg64 [in] - The base64 encoded challenge message. 573 * userp [in] - The user name. 574 * passdwp [in] - The user's password. 575 * service [in] - The service type such as www, smtp, pop or imap. 576 * outptr [in/out] - The address where a pointer to newly allocated memory 577 * holding the result will be stored upon completion. 578 * outlen [out] - The length of the output message. 579 * 580 * Returns CURLE_OK on success. 581 */ 582 CURLcode Curl_sasl_create_digest_md5_message(struct SessionHandle *data, 583 const char *chlg64, 584 const char *userp, 585 const char *passwdp, 586 const char *service, 587 char **outptr, size_t *outlen) 588 { 589 CURLcode result = CURLE_OK; 590 size_t i; 591 MD5_context *ctxt; 592 char *response = NULL; 593 unsigned char digest[MD5_DIGEST_LEN]; 594 char HA1_hex[2 * MD5_DIGEST_LEN + 1]; 595 char HA2_hex[2 * MD5_DIGEST_LEN + 1]; 596 char resp_hash_hex[2 * MD5_DIGEST_LEN + 1]; 597 char nonce[64]; 598 char realm[128]; 599 char algorithm[64]; 600 char qop_options[64]; 601 int qop_values; 602 char cnonce[33]; 603 unsigned int entropy[4]; 604 char nonceCount[] = "00000001"; 605 char method[] = "AUTHENTICATE"; 606 char qop[] = DIGEST_QOP_VALUE_STRING_AUTH; 607 char *spn = NULL; 608 609 /* Decode the challange message */ 610 result = sasl_decode_digest_md5_message(chlg64, nonce, sizeof(nonce), 611 realm, sizeof(realm), 612 algorithm, sizeof(algorithm), 613 qop_options, sizeof(qop_options)); 614 if(result) 615 return result; 616 617 /* We only support md5 sessions */ 618 if(strcmp(algorithm, "md5-sess") != 0) 619 return CURLE_BAD_CONTENT_ENCODING; 620 621 /* Get the qop-values from the qop-options */ 622 result = sasl_digest_get_qop_values(qop_options, &qop_values); 623 if(result) 624 return result; 625 626 /* We only support auth quality-of-protection */ 627 if(!(qop_values & DIGEST_QOP_VALUE_AUTH)) 628 return CURLE_BAD_CONTENT_ENCODING; 629 630 /* Generate 16 bytes of random data */ 631 entropy[0] = Curl_rand(data); 632 entropy[1] = Curl_rand(data); 633 entropy[2] = Curl_rand(data); 634 entropy[3] = Curl_rand(data); 635 636 /* Convert the random data into a 32 byte hex string */ 637 snprintf(cnonce, sizeof(cnonce), "%08x%08x%08x%08x", 638 entropy[0], entropy[1], entropy[2], entropy[3]); 639 640 /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */ 641 ctxt = Curl_MD5_init(Curl_DIGEST_MD5); 642 if(!ctxt) 643 return CURLE_OUT_OF_MEMORY; 644 645 Curl_MD5_update(ctxt, (const unsigned char *) userp, 646 curlx_uztoui(strlen(userp))); 647 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 648 Curl_MD5_update(ctxt, (const unsigned char *) realm, 649 curlx_uztoui(strlen(realm))); 650 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 651 Curl_MD5_update(ctxt, (const unsigned char *) passwdp, 652 curlx_uztoui(strlen(passwdp))); 653 Curl_MD5_final(ctxt, digest); 654 655 ctxt = Curl_MD5_init(Curl_DIGEST_MD5); 656 if(!ctxt) 657 return CURLE_OUT_OF_MEMORY; 658 659 Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN); 660 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 661 Curl_MD5_update(ctxt, (const unsigned char *) nonce, 662 curlx_uztoui(strlen(nonce))); 663 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 664 Curl_MD5_update(ctxt, (const unsigned char *) cnonce, 665 curlx_uztoui(strlen(cnonce))); 666 Curl_MD5_final(ctxt, digest); 667 668 /* Convert calculated 16 octet hex into 32 bytes string */ 669 for(i = 0; i < MD5_DIGEST_LEN; i++) 670 snprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]); 671 672 /* Generate our SPN */ 673 spn = Curl_sasl_build_spn(service, realm); 674 if(!spn) 675 return CURLE_OUT_OF_MEMORY; 676 677 /* Calculate H(A2) */ 678 ctxt = Curl_MD5_init(Curl_DIGEST_MD5); 679 if(!ctxt) { 680 free(spn); 681 682 return CURLE_OUT_OF_MEMORY; 683 } 684 685 Curl_MD5_update(ctxt, (const unsigned char *) method, 686 curlx_uztoui(strlen(method))); 687 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 688 Curl_MD5_update(ctxt, (const unsigned char *) spn, 689 curlx_uztoui(strlen(spn))); 690 Curl_MD5_final(ctxt, digest); 691 692 for(i = 0; i < MD5_DIGEST_LEN; i++) 693 snprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]); 694 695 /* Now calculate the response hash */ 696 ctxt = Curl_MD5_init(Curl_DIGEST_MD5); 697 if(!ctxt) { 698 free(spn); 699 700 return CURLE_OUT_OF_MEMORY; 701 } 702 703 Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN); 704 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 705 Curl_MD5_update(ctxt, (const unsigned char *) nonce, 706 curlx_uztoui(strlen(nonce))); 707 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 708 709 Curl_MD5_update(ctxt, (const unsigned char *) nonceCount, 710 curlx_uztoui(strlen(nonceCount))); 711 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 712 Curl_MD5_update(ctxt, (const unsigned char *) cnonce, 713 curlx_uztoui(strlen(cnonce))); 714 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 715 Curl_MD5_update(ctxt, (const unsigned char *) qop, 716 curlx_uztoui(strlen(qop))); 717 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 718 719 Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN); 720 Curl_MD5_final(ctxt, digest); 721 722 for(i = 0; i < MD5_DIGEST_LEN; i++) 723 snprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]); 724 725 /* Generate the response */ 726 response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\"," 727 "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s," 728 "qop=%s", 729 userp, realm, nonce, 730 cnonce, nonceCount, spn, resp_hash_hex, qop); 731 free(spn); 732 if(!response) 733 return CURLE_OUT_OF_MEMORY; 734 735 /* Base64 encode the response */ 736 result = Curl_base64_encode(data, response, 0, outptr, outlen); 737 738 free(response); 739 740 return result; 741 } 742 743 /* 744 * Curl_sasl_decode_digest_http_message() 745 * 746 * This is used to decode a HTTP DIGEST challenge message into the seperate 747 * attributes. 748 * 749 * Parameters: 750 * 751 * chlg [in] - The challenge message. 752 * digest [in/out] - The digest data struct being used and modified. 753 * 754 * Returns CURLE_OK on success. 755 */ 756 CURLcode Curl_sasl_decode_digest_http_message(const char *chlg, 757 struct digestdata *digest) 758 { 759 bool before = FALSE; /* got a nonce before */ 760 bool foundAuth = FALSE; 761 bool foundAuthInt = FALSE; 762 char *token = NULL; 763 char *tmp = NULL; 764 765 /* If we already have received a nonce, keep that in mind */ 766 if(digest->nonce) 767 before = TRUE; 768 769 /* Clean up any former leftovers and initialise to defaults */ 770 Curl_sasl_digest_cleanup(digest); 771 772 for(;;) { 773 char value[DIGEST_MAX_VALUE_LENGTH]; 774 char content[DIGEST_MAX_CONTENT_LENGTH]; 775 776 /* Pass all additional spaces here */ 777 while(*chlg && ISSPACE(*chlg)) 778 chlg++; 779 780 /* Extract a value=content pair */ 781 if(!Curl_sasl_digest_get_pair(chlg, value, content, &chlg)) { 782 if(Curl_raw_equal(value, "nonce")) { 783 digest->nonce = strdup(content); 784 if(!digest->nonce) 785 return CURLE_OUT_OF_MEMORY; 786 } 787 else if(Curl_raw_equal(value, "stale")) { 788 if(Curl_raw_equal(content, "true")) { 789 digest->stale = TRUE; 790 digest->nc = 1; /* we make a new nonce now */ 791 } 792 } 793 else if(Curl_raw_equal(value, "realm")) { 794 digest->realm = strdup(content); 795 if(!digest->realm) 796 return CURLE_OUT_OF_MEMORY; 797 } 798 else if(Curl_raw_equal(value, "opaque")) { 799 digest->opaque = strdup(content); 800 if(!digest->opaque) 801 return CURLE_OUT_OF_MEMORY; 802 } 803 else if(Curl_raw_equal(value, "qop")) { 804 char *tok_buf; 805 /* Tokenize the list and choose auth if possible, use a temporary 806 clone of the buffer since strtok_r() ruins it */ 807 tmp = strdup(content); 808 if(!tmp) 809 return CURLE_OUT_OF_MEMORY; 810 811 token = strtok_r(tmp, ",", &tok_buf); 812 while(token != NULL) { 813 if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH)) { 814 foundAuth = TRUE; 815 } 816 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) { 817 foundAuthInt = TRUE; 818 } 819 token = strtok_r(NULL, ",", &tok_buf); 820 } 821 822 free(tmp); 823 824 /* Select only auth or auth-int. Otherwise, ignore */ 825 if(foundAuth) { 826 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH); 827 if(!digest->qop) 828 return CURLE_OUT_OF_MEMORY; 829 } 830 else if(foundAuthInt) { 831 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT); 832 if(!digest->qop) 833 return CURLE_OUT_OF_MEMORY; 834 } 835 } 836 else if(Curl_raw_equal(value, "algorithm")) { 837 digest->algorithm = strdup(content); 838 if(!digest->algorithm) 839 return CURLE_OUT_OF_MEMORY; 840 841 if(Curl_raw_equal(content, "MD5-sess")) 842 digest->algo = CURLDIGESTALGO_MD5SESS; 843 else if(Curl_raw_equal(content, "MD5")) 844 digest->algo = CURLDIGESTALGO_MD5; 845 else 846 return CURLE_BAD_CONTENT_ENCODING; 847 } 848 else { 849 /* unknown specifier, ignore it! */ 850 } 851 } 852 else 853 break; /* we're done here */ 854 855 /* Pass all additional spaces here */ 856 while(*chlg && ISSPACE(*chlg)) 857 chlg++; 858 859 /* Allow the list to be comma-separated */ 860 if(',' == *chlg) 861 chlg++; 862 } 863 864 /* We had a nonce since before, and we got another one now without 865 'stale=true'. This means we provided bad credentials in the previous 866 request */ 867 if(before && !digest->stale) 868 return CURLE_BAD_CONTENT_ENCODING; 869 870 /* We got this header without a nonce, that's a bad Digest line! */ 871 if(!digest->nonce) 872 return CURLE_BAD_CONTENT_ENCODING; 873 874 return CURLE_OK; 875 } 876 877 /* 878 * Curl_sasl_create_digest_http_message() 879 * 880 * This is used to generate a HTTP DIGEST response message ready for sending 881 * to the recipient. 882 * 883 * Parameters: 884 * 885 * data [in] - The session handle. 886 * userp [in] - The user name. 887 * passdwp [in] - The user's password. 888 * request [in] - The HTTP request. 889 * uripath [in] - The path of the HTTP uri. 890 * digest [in/out] - The digest data struct being used and modified. 891 * outptr [in/out] - The address where a pointer to newly allocated memory 892 * holding the result will be stored upon completion. 893 * outlen [out] - The length of the output message. 894 * 895 * Returns CURLE_OK on success. 896 */ 897 CURLcode Curl_sasl_create_digest_http_message(struct SessionHandle *data, 898 const char *userp, 899 const char *passwdp, 900 const unsigned char *request, 901 const unsigned char *uripath, 902 struct digestdata *digest, 903 char **outptr, size_t *outlen) 904 { 905 CURLcode result; 906 unsigned char md5buf[16]; /* 16 bytes/128 bits */ 907 unsigned char request_digest[33]; 908 unsigned char *md5this; 909 unsigned char ha1[33];/* 32 digits and 1 zero byte */ 910 unsigned char ha2[33];/* 32 digits and 1 zero byte */ 911 char cnoncebuf[33]; 912 char *cnonce = NULL; 913 size_t cnonce_sz = 0; 914 char *userp_quoted; 915 char *response = NULL; 916 char *tmp = NULL; 917 918 if(!digest->nc) 919 digest->nc = 1; 920 921 if(!digest->cnonce) { 922 snprintf(cnoncebuf, sizeof(cnoncebuf), "%08x%08x%08x%08x", 923 Curl_rand(data), Curl_rand(data), 924 Curl_rand(data), Curl_rand(data)); 925 926 result = Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf), 927 &cnonce, &cnonce_sz); 928 if(result) 929 return result; 930 931 digest->cnonce = cnonce; 932 } 933 934 /* 935 if the algorithm is "MD5" or unspecified (which then defaults to MD5): 936 937 A1 = unq(username-value) ":" unq(realm-value) ":" passwd 938 939 if the algorithm is "MD5-sess" then: 940 941 A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd ) 942 ":" unq(nonce-value) ":" unq(cnonce-value) 943 */ 944 945 md5this = (unsigned char *) 946 aprintf("%s:%s:%s", userp, digest->realm, passwdp); 947 if(!md5this) 948 return CURLE_OUT_OF_MEMORY; 949 950 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ 951 Curl_md5it(md5buf, md5this); 952 free(md5this); 953 sasl_digest_md5_to_ascii(md5buf, ha1); 954 955 if(digest->algo == CURLDIGESTALGO_MD5SESS) { 956 /* nonce and cnonce are OUTSIDE the hash */ 957 tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce); 958 if(!tmp) 959 return CURLE_OUT_OF_MEMORY; 960 961 CURL_OUTPUT_DIGEST_CONV(data, tmp); /* convert on non-ASCII machines */ 962 Curl_md5it(md5buf, (unsigned char *)tmp); 963 free(tmp); 964 sasl_digest_md5_to_ascii(md5buf, ha1); 965 } 966 967 /* 968 If the "qop" directive's value is "auth" or is unspecified, then A2 is: 969 970 A2 = Method ":" digest-uri-value 971 972 If the "qop" value is "auth-int", then A2 is: 973 974 A2 = Method ":" digest-uri-value ":" H(entity-body) 975 976 (The "Method" value is the HTTP request method as specified in section 977 5.1.1 of RFC 2616) 978 */ 979 980 md5this = (unsigned char *)aprintf("%s:%s", request, uripath); 981 982 if(digest->qop && Curl_raw_equal(digest->qop, "auth-int")) { 983 /* We don't support auth-int for PUT or POST at the moment. 984 TODO: replace md5 of empty string with entity-body for PUT/POST */ 985 unsigned char *md5this2 = (unsigned char *) 986 aprintf("%s:%s", md5this, "d41d8cd98f00b204e9800998ecf8427e"); 987 free(md5this); 988 md5this = md5this2; 989 } 990 991 if(!md5this) 992 return CURLE_OUT_OF_MEMORY; 993 994 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ 995 Curl_md5it(md5buf, md5this); 996 free(md5this); 997 sasl_digest_md5_to_ascii(md5buf, ha2); 998 999 if(digest->qop) { 1000 md5this = (unsigned char *)aprintf("%s:%s:%08x:%s:%s:%s", 1001 ha1, 1002 digest->nonce, 1003 digest->nc, 1004 digest->cnonce, 1005 digest->qop, 1006 ha2); 1007 } 1008 else { 1009 md5this = (unsigned char *)aprintf("%s:%s:%s", 1010 ha1, 1011 digest->nonce, 1012 ha2); 1013 } 1014 1015 if(!md5this) 1016 return CURLE_OUT_OF_MEMORY; 1017 1018 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ 1019 Curl_md5it(md5buf, md5this); 1020 free(md5this); 1021 sasl_digest_md5_to_ascii(md5buf, request_digest); 1022 1023 /* for test case 64 (snooped from a Mozilla 1.3a request) 1024 1025 Authorization: Digest username="testuser", realm="testrealm", \ 1026 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca" 1027 1028 Digest parameters are all quoted strings. Username which is provided by 1029 the user will need double quotes and backslashes within it escaped. For 1030 the other fields, this shouldn't be an issue. realm, nonce, and opaque 1031 are copied as is from the server, escapes and all. cnonce is generated 1032 with web-safe characters. uri is already percent encoded. nc is 8 hex 1033 characters. algorithm and qop with standard values only contain web-safe 1034 chracters. 1035 */ 1036 userp_quoted = sasl_digest_string_quoted(userp); 1037 if(!userp_quoted) 1038 return CURLE_OUT_OF_MEMORY; 1039 1040 if(digest->qop) { 1041 response = aprintf("username=\"%s\", " 1042 "realm=\"%s\", " 1043 "nonce=\"%s\", " 1044 "uri=\"%s\", " 1045 "cnonce=\"%s\", " 1046 "nc=%08x, " 1047 "qop=%s, " 1048 "response=\"%s\"", 1049 userp_quoted, 1050 digest->realm, 1051 digest->nonce, 1052 uripath, 1053 digest->cnonce, 1054 digest->nc, 1055 digest->qop, 1056 request_digest); 1057 1058 if(Curl_raw_equal(digest->qop, "auth")) 1059 digest->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0 1060 padded which tells to the server how many times you are 1061 using the same nonce in the qop=auth mode */ 1062 } 1063 else { 1064 response = aprintf("username=\"%s\", " 1065 "realm=\"%s\", " 1066 "nonce=\"%s\", " 1067 "uri=\"%s\", " 1068 "response=\"%s\"", 1069 userp_quoted, 1070 digest->realm, 1071 digest->nonce, 1072 uripath, 1073 request_digest); 1074 } 1075 free(userp_quoted); 1076 if(!response) 1077 return CURLE_OUT_OF_MEMORY; 1078 1079 /* Add the optional fields */ 1080 if(digest->opaque) { 1081 /* Append the opaque */ 1082 tmp = aprintf("%s, opaque=\"%s\"", response, digest->opaque); 1083 free(response); 1084 if(!tmp) 1085 return CURLE_OUT_OF_MEMORY; 1086 1087 response = tmp; 1088 } 1089 1090 if(digest->algorithm) { 1091 /* Append the algorithm */ 1092 tmp = aprintf("%s, algorithm=\"%s\"", response, digest->algorithm); 1093 free(response); 1094 if(!tmp) 1095 return CURLE_OUT_OF_MEMORY; 1096 1097 response = tmp; 1098 } 1099 1100 /* Return the output */ 1101 *outptr = response; 1102 *outlen = strlen(response); 1103 1104 return CURLE_OK; 1105 } 1106 1107 /* 1108 * Curl_sasl_digest_cleanup() 1109 * 1110 * This is used to clean up the digest specific data. 1111 * 1112 * Parameters: 1113 * 1114 * digest [in/out] - The digest data struct being cleaned up. 1115 * 1116 */ 1117 void Curl_sasl_digest_cleanup(struct digestdata *digest) 1118 { 1119 Curl_safefree(digest->nonce); 1120 Curl_safefree(digest->cnonce); 1121 Curl_safefree(digest->realm); 1122 Curl_safefree(digest->opaque); 1123 Curl_safefree(digest->qop); 1124 Curl_safefree(digest->algorithm); 1125 1126 digest->nc = 0; 1127 digest->algo = CURLDIGESTALGO_MD5; /* default algorithm */ 1128 digest->stale = FALSE; /* default means normal, not stale */ 1129 } 1130 #endif /* !USE_WINDOWS_SSPI */ 1131 1132 #endif /* CURL_DISABLE_CRYPTO_AUTH */ 1133 1134 #if defined(USE_NTLM) && !defined(USE_WINDOWS_SSPI) 1135 /* 1136 * Curl_sasl_ntlm_cleanup() 1137 * 1138 * This is used to clean up the ntlm specific data. 1139 * 1140 * Parameters: 1141 * 1142 * ntlm [in/out] - The ntlm data struct being cleaned up. 1143 * 1144 */ 1145 void Curl_sasl_ntlm_cleanup(struct ntlmdata *ntlm) 1146 { 1147 /* Free the target info */ 1148 Curl_safefree(ntlm->target_info); 1149 1150 /* Reset any variables */ 1151 ntlm->target_info_len = 0; 1152 } 1153 #endif /* USE_NTLM && !USE_WINDOWS_SSPI*/ 1154 1155 /* 1156 * sasl_create_xoauth2_message() 1157 * 1158 * This is used to generate an already encoded OAuth 2.0 message ready for 1159 * sending to the recipient. 1160 * 1161 * Parameters: 1162 * 1163 * data [in] - The session handle. 1164 * user [in] - The user name. 1165 * bearer [in] - The bearer token. 1166 * outptr [in/out] - The address where a pointer to newly allocated memory 1167 * holding the result will be stored upon completion. 1168 * outlen [out] - The length of the output message. 1169 * 1170 * Returns CURLE_OK on success. 1171 */ 1172 static CURLcode sasl_create_xoauth2_message(struct SessionHandle *data, 1173 const char *user, 1174 const char *bearer, 1175 char **outptr, size_t *outlen) 1176 { 1177 CURLcode result = CURLE_OK; 1178 char *xoauth = NULL; 1179 1180 /* Generate the message */ 1181 xoauth = aprintf("user=%s\1auth=Bearer %s\1\1", user, bearer); 1182 if(!xoauth) 1183 return CURLE_OUT_OF_MEMORY; 1184 1185 /* Base64 encode the reply */ 1186 result = Curl_base64_encode(data, xoauth, strlen(xoauth), outptr, outlen); 1187 1188 free(xoauth); 1189 1190 return result; 1191 } 1192 1193 /* 1194 * Curl_sasl_cleanup() 1195 * 1196 * This is used to cleanup any libraries or curl modules used by the sasl 1197 * functions. 1198 * 1199 * Parameters: 1200 * 1201 * conn [in] - The connection data. 1202 * authused [in] - The authentication mechanism used. 1203 */ 1204 void Curl_sasl_cleanup(struct connectdata *conn, unsigned int authused) 1205 { 1206 #if defined(USE_KERBEROS5) 1207 /* Cleanup the gssapi structure */ 1208 if(authused == SASL_MECH_GSSAPI) { 1209 Curl_sasl_gssapi_cleanup(&conn->krb5); 1210 } 1211 #endif 1212 1213 #if defined(USE_NTLM) 1214 /* Cleanup the ntlm structure */ 1215 if(authused == SASL_MECH_NTLM) { 1216 Curl_sasl_ntlm_cleanup(&conn->ntlm); 1217 } 1218 #endif 1219 1220 #if !defined(USE_KERBEROS5) && !defined(USE_NTLM) 1221 /* Reserved for future use */ 1222 (void)conn; 1223 (void)authused; 1224 #endif 1225 } 1226 1227 /* 1228 * Curl_sasl_decode_mech() 1229 * 1230 * Convert a SASL mechanism name into a token. 1231 * 1232 * Parameters: 1233 * 1234 * ptr [in] - The mechanism string. 1235 * maxlen [in] - Maximum mechanism string length. 1236 * len [out] - If not NULL, effective name length. 1237 * 1238 * Returns the SASL mechanism token or 0 if no match. 1239 */ 1240 unsigned int Curl_sasl_decode_mech(const char *ptr, size_t maxlen, size_t *len) 1241 { 1242 unsigned int i; 1243 char c; 1244 1245 for(i = 0; mechtable[i].name; i++) { 1246 if(maxlen >= mechtable[i].len && 1247 !memcmp(ptr, mechtable[i].name, mechtable[i].len)) { 1248 if(len) 1249 *len = mechtable[i].len; 1250 1251 if(maxlen == mechtable[i].len) 1252 return mechtable[i].bit; 1253 1254 c = ptr[mechtable[i].len]; 1255 if(!ISUPPER(c) && !ISDIGIT(c) && c != '-' && c != '_') 1256 return mechtable[i].bit; 1257 } 1258 } 1259 1260 return 0; 1261 } 1262 1263 /* 1264 * Curl_sasl_parse_url_auth_option() 1265 * 1266 * Parse the URL login options. 1267 */ 1268 CURLcode Curl_sasl_parse_url_auth_option(struct SASL *sasl, 1269 const char *value, size_t len) 1270 { 1271 CURLcode result = CURLE_OK; 1272 unsigned int mechbit; 1273 size_t mechlen; 1274 1275 if(!len) 1276 return CURLE_URL_MALFORMAT; 1277 1278 if(sasl->resetprefs) { 1279 sasl->resetprefs = FALSE; 1280 sasl->prefmech = SASL_AUTH_NONE; 1281 } 1282 1283 if(strnequal(value, "*", len)) 1284 sasl->prefmech = SASL_AUTH_DEFAULT; 1285 else if((mechbit = Curl_sasl_decode_mech(value, len, &mechlen)) && 1286 mechlen == len) 1287 sasl->prefmech |= mechbit; 1288 else 1289 result = CURLE_URL_MALFORMAT; 1290 1291 return result; 1292 } 1293 1294 /* 1295 * Curl_sasl_init() 1296 * 1297 * Initializes the SASL structure. 1298 */ 1299 void Curl_sasl_init(struct SASL *sasl, const struct SASLproto *params) 1300 { 1301 sasl->params = params; /* Set protocol dependent parameters */ 1302 sasl->state = SASL_STOP; /* Not yet running */ 1303 sasl->authmechs = SASL_AUTH_NONE; /* No known authentication mechanism yet */ 1304 sasl->prefmech = SASL_AUTH_DEFAULT; /* Prefer all mechanisms */ 1305 sasl->authused = SASL_AUTH_NONE; /* No the authentication mechanism used */ 1306 sasl->resetprefs = TRUE; /* Reset prefmech upon AUTH parsing. */ 1307 sasl->mutual_auth = FALSE; /* No mutual authentication (GSSAPI only) */ 1308 sasl->force_ir = FALSE; /* Respect external option */ 1309 } 1310 1311 /* 1312 * state() 1313 * 1314 * This is the ONLY way to change SASL state! 1315 */ 1316 static void state(struct SASL *sasl, struct connectdata *conn, 1317 saslstate newstate) 1318 { 1319 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) 1320 /* for debug purposes */ 1321 static const char * const names[]={ 1322 "STOP", 1323 "PLAIN", 1324 "LOGIN", 1325 "LOGIN_PASSWD", 1326 "EXTERNAL", 1327 "CRAMMD5", 1328 "DIGESTMD5", 1329 "DIGESTMD5_RESP", 1330 "NTLM", 1331 "NTLM_TYPE2MSG", 1332 "GSSAPI", 1333 "GSSAPI_TOKEN", 1334 "GSSAPI_NO_DATA", 1335 "XOAUTH2", 1336 "CANCEL", 1337 "FINAL", 1338 /* LAST */ 1339 }; 1340 1341 if(sasl->state != newstate) 1342 infof(conn->data, "SASL %p state change from %s to %s\n", 1343 (void *)sasl, names[sasl->state], names[newstate]); 1344 #else 1345 (void) conn; 1346 #endif 1347 1348 sasl->state = newstate; 1349 } 1350 1351 /* 1352 * Curl_sasl_can_authenticate() 1353 * 1354 * Check if we have enough auth data and capabilities to authenticate. 1355 */ 1356 bool Curl_sasl_can_authenticate(struct SASL *sasl, struct connectdata *conn) 1357 { 1358 /* Have credentials been provided? */ 1359 if(conn->bits.user_passwd) 1360 return TRUE; 1361 1362 /* EXTERNAL can authenticate without a user name and/or password */ 1363 if(sasl->authmechs & sasl->prefmech & SASL_MECH_EXTERNAL) 1364 return TRUE; 1365 1366 return FALSE; 1367 } 1368 1369 /* 1370 * Curl_sasl_start() 1371 * 1372 * Calculate the required login details for SASL authentication. 1373 */ 1374 CURLcode Curl_sasl_start(struct SASL *sasl, struct connectdata *conn, 1375 bool force_ir, saslprogress *progress) 1376 { 1377 CURLcode result = CURLE_OK; 1378 struct SessionHandle *data = conn->data; 1379 unsigned int enabledmechs; 1380 const char *mech = NULL; 1381 char *resp = NULL; 1382 size_t len = 0; 1383 saslstate state1 = SASL_STOP; 1384 saslstate state2 = SASL_FINAL; 1385 1386 sasl->force_ir = force_ir; /* Latch for future use */ 1387 sasl->authused = 0; /* No mechanism used yet */ 1388 enabledmechs = sasl->authmechs & sasl->prefmech; 1389 *progress = SASL_IDLE; 1390 1391 /* Calculate the supported authentication mechanism, by decreasing order of 1392 security, as well as the initial response where appropriate */ 1393 if((enabledmechs & SASL_MECH_EXTERNAL) && !conn->passwd[0]) { 1394 mech = SASL_MECH_STRING_EXTERNAL; 1395 state1 = SASL_EXTERNAL; 1396 sasl->authused = SASL_MECH_EXTERNAL; 1397 1398 if(force_ir || data->set.sasl_ir) 1399 result = sasl_create_external_message(data, conn->user, &resp, &len); 1400 } 1401 else if(conn->bits.user_passwd) { 1402 #if defined(USE_KERBEROS5) 1403 if(enabledmechs & SASL_MECH_GSSAPI) { 1404 sasl->mutual_auth = FALSE; /* TODO: Calculate mutual authentication */ 1405 mech = SASL_MECH_STRING_GSSAPI; 1406 state1 = SASL_GSSAPI; 1407 state2 = SASL_GSSAPI_TOKEN; 1408 sasl->authused = SASL_MECH_GSSAPI; 1409 1410 if(force_ir || data->set.sasl_ir) 1411 result = Curl_sasl_create_gssapi_user_message(data, conn->user, 1412 conn->passwd, 1413 sasl->params->service, 1414 sasl->mutual_auth, 1415 NULL, &conn->krb5, 1416 &resp, &len); 1417 } 1418 else 1419 #endif 1420 #ifndef CURL_DISABLE_CRYPTO_AUTH 1421 if(enabledmechs & SASL_MECH_DIGEST_MD5) { 1422 mech = SASL_MECH_STRING_DIGEST_MD5; 1423 state1 = SASL_DIGESTMD5; 1424 sasl->authused = SASL_MECH_DIGEST_MD5; 1425 } 1426 else if(enabledmechs & SASL_MECH_CRAM_MD5) { 1427 mech = SASL_MECH_STRING_CRAM_MD5; 1428 state1 = SASL_CRAMMD5; 1429 sasl->authused = SASL_MECH_CRAM_MD5; 1430 } 1431 else 1432 #endif 1433 #ifdef USE_NTLM 1434 if(enabledmechs & SASL_MECH_NTLM) { 1435 mech = SASL_MECH_STRING_NTLM; 1436 state1 = SASL_NTLM; 1437 state2 = SASL_NTLM_TYPE2MSG; 1438 sasl->authused = SASL_MECH_NTLM; 1439 1440 if(force_ir || data->set.sasl_ir) 1441 result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd, 1442 &conn->ntlm, &resp, &len); 1443 } 1444 else 1445 #endif 1446 if((enabledmechs & SASL_MECH_XOAUTH2) || conn->xoauth2_bearer) { 1447 mech = SASL_MECH_STRING_XOAUTH2; 1448 state1 = SASL_XOAUTH2; 1449 sasl->authused = SASL_MECH_XOAUTH2; 1450 1451 if(force_ir || data->set.sasl_ir) 1452 result = sasl_create_xoauth2_message(data, conn->user, 1453 conn->xoauth2_bearer, 1454 &resp, &len); 1455 } 1456 else if(enabledmechs & SASL_MECH_LOGIN) { 1457 mech = SASL_MECH_STRING_LOGIN; 1458 state1 = SASL_LOGIN; 1459 state2 = SASL_LOGIN_PASSWD; 1460 sasl->authused = SASL_MECH_LOGIN; 1461 1462 if(force_ir || data->set.sasl_ir) 1463 result = sasl_create_login_message(data, conn->user, &resp, &len); 1464 } 1465 else if(enabledmechs & SASL_MECH_PLAIN) { 1466 mech = SASL_MECH_STRING_PLAIN; 1467 state1 = SASL_PLAIN; 1468 sasl->authused = SASL_MECH_PLAIN; 1469 1470 if(force_ir || data->set.sasl_ir) 1471 result = sasl_create_plain_message(data, conn->user, conn->passwd, 1472 &resp, &len); 1473 } 1474 } 1475 1476 if(!result) { 1477 if(resp && sasl->params->maxirlen && 1478 strlen(mech) + len > sasl->params->maxirlen) { 1479 free(resp); 1480 resp = NULL; 1481 } 1482 1483 if(mech) { 1484 result = sasl->params->sendauth(conn, mech, resp); 1485 if(!result) { 1486 *progress = SASL_INPROGRESS; 1487 state(sasl, conn, resp? state2: state1); 1488 } 1489 } 1490 } 1491 1492 free(resp); 1493 1494 return result; 1495 } 1496 1497 /* 1498 * Curl_sasl_continue() 1499 * 1500 * Continue the authentication. 1501 */ 1502 CURLcode Curl_sasl_continue(struct SASL *sasl, struct connectdata *conn, 1503 int code, saslprogress *progress) 1504 { 1505 CURLcode result = CURLE_OK; 1506 struct SessionHandle *data = conn->data; 1507 saslstate newstate = SASL_FINAL; 1508 char *resp = NULL; 1509 #if !defined(CURL_DISABLE_CRYPTO_AUTH) 1510 char *serverdata; 1511 char *chlg = NULL; 1512 size_t chlglen = 0; 1513 #endif 1514 size_t len = 0; 1515 1516 *progress = SASL_INPROGRESS; 1517 1518 if(sasl->state == SASL_FINAL) { 1519 if(code != sasl->params->finalcode) 1520 result = CURLE_LOGIN_DENIED; 1521 *progress = SASL_DONE; 1522 state(sasl, conn, SASL_STOP); 1523 return result; 1524 } 1525 1526 if(sasl->state != SASL_CANCEL && code != sasl->params->contcode) { 1527 *progress = SASL_DONE; 1528 state(sasl, conn, SASL_STOP); 1529 return CURLE_LOGIN_DENIED; 1530 } 1531 1532 switch(sasl->state) { 1533 case SASL_STOP: 1534 *progress = SASL_DONE; 1535 return result; 1536 case SASL_PLAIN: 1537 result = sasl_create_plain_message(data, conn->user, conn->passwd, &resp, 1538 &len); 1539 break; 1540 case SASL_LOGIN: 1541 result = sasl_create_login_message(data, conn->user, &resp, &len); 1542 newstate = SASL_LOGIN_PASSWD; 1543 break; 1544 case SASL_LOGIN_PASSWD: 1545 result = sasl_create_login_message(data, conn->passwd, &resp, &len); 1546 break; 1547 case SASL_EXTERNAL: 1548 result = sasl_create_external_message(data, conn->user, &resp, &len); 1549 break; 1550 1551 #ifndef CURL_DISABLE_CRYPTO_AUTH 1552 case SASL_CRAMMD5: 1553 sasl->params->getmessage(data->state.buffer, &serverdata); 1554 result = sasl_decode_cram_md5_message(serverdata, &chlg, &chlglen); 1555 if(!result) 1556 result = sasl_create_cram_md5_message(data, chlg, conn->user, 1557 conn->passwd, &resp, &len); 1558 free(chlg); 1559 break; 1560 case SASL_DIGESTMD5: 1561 sasl->params->getmessage(data->state.buffer, &serverdata); 1562 result = Curl_sasl_create_digest_md5_message(data, serverdata, 1563 conn->user, conn->passwd, 1564 sasl->params->service, 1565 &resp, &len); 1566 newstate = SASL_DIGESTMD5_RESP; 1567 break; 1568 case SASL_DIGESTMD5_RESP: 1569 if(!(resp = strdup(""))) 1570 result = CURLE_OUT_OF_MEMORY; 1571 break; 1572 #endif 1573 1574 #ifdef USE_NTLM 1575 case SASL_NTLM: 1576 /* Create the type-1 message */ 1577 result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd, 1578 &conn->ntlm, &resp, &len); 1579 newstate = SASL_NTLM_TYPE2MSG; 1580 break; 1581 case SASL_NTLM_TYPE2MSG: 1582 /* Decode the type-2 message */ 1583 sasl->params->getmessage(data->state.buffer, &serverdata); 1584 result = Curl_sasl_decode_ntlm_type2_message(data, serverdata, 1585 &conn->ntlm); 1586 if(!result) 1587 result = Curl_sasl_create_ntlm_type3_message(data, conn->user, 1588 conn->passwd, &conn->ntlm, 1589 &resp, &len); 1590 break; 1591 #endif 1592 1593 #if defined(USE_KERBEROS5) 1594 case SASL_GSSAPI: 1595 result = Curl_sasl_create_gssapi_user_message(data, conn->user, 1596 conn->passwd, 1597 sasl->params->service, 1598 sasl->mutual_auth, NULL, 1599 &conn->krb5, 1600 &resp, &len); 1601 newstate = SASL_GSSAPI_TOKEN; 1602 break; 1603 case SASL_GSSAPI_TOKEN: 1604 sasl->params->getmessage(data->state.buffer, &serverdata); 1605 if(sasl->mutual_auth) { 1606 /* Decode the user token challenge and create the optional response 1607 message */ 1608 result = Curl_sasl_create_gssapi_user_message(data, NULL, NULL, NULL, 1609 sasl->mutual_auth, 1610 serverdata, &conn->krb5, 1611 &resp, &len); 1612 newstate = SASL_GSSAPI_NO_DATA; 1613 } 1614 else 1615 /* Decode the security challenge and create the response message */ 1616 result = Curl_sasl_create_gssapi_security_message(data, serverdata, 1617 &conn->krb5, 1618 &resp, &len); 1619 break; 1620 case SASL_GSSAPI_NO_DATA: 1621 sasl->params->getmessage(data->state.buffer, &serverdata); 1622 /* Decode the security challenge and create the response message */ 1623 result = Curl_sasl_create_gssapi_security_message(data, serverdata, 1624 &conn->krb5, 1625 &resp, &len); 1626 break; 1627 #endif 1628 1629 case SASL_XOAUTH2: 1630 /* Create the authorisation message */ 1631 result = sasl_create_xoauth2_message(data, conn->user, 1632 conn->xoauth2_bearer, &resp, &len); 1633 break; 1634 case SASL_CANCEL: 1635 /* Remove the offending mechanism from the supported list */ 1636 sasl->authmechs ^= sasl->authused; 1637 1638 /* Start an alternative SASL authentication */ 1639 result = Curl_sasl_start(sasl, conn, sasl->force_ir, progress); 1640 newstate = sasl->state; /* Use state from Curl_sasl_start() */ 1641 break; 1642 default: 1643 failf(data, "Unsupported SASL authentication mechanism"); 1644 result = CURLE_UNSUPPORTED_PROTOCOL; /* Should not happen */ 1645 break; 1646 } 1647 1648 switch(result) { 1649 case CURLE_BAD_CONTENT_ENCODING: 1650 /* Cancel dialog */ 1651 result = sasl->params->sendcont(conn, "*"); 1652 newstate = SASL_CANCEL; 1653 break; 1654 case CURLE_OK: 1655 if(resp) 1656 result = sasl->params->sendcont(conn, resp); 1657 break; 1658 default: 1659 newstate = SASL_STOP; /* Stop on error */ 1660 *progress = SASL_DONE; 1661 break; 1662 } 1663 1664 free(resp); 1665 1666 state(sasl, conn, newstate); 1667 1668 return result; 1669 } 1670