1/*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 1998 - 2014, 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#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) 26 27#include "urldata.h" 28#include "rawstr.h" 29#include "curl_base64.h" 30#include "curl_md5.h" 31#include "http_digest.h" 32#include "strtok.h" 33#include "curl_memory.h" 34#include "vtls/vtls.h" /* for Curl_rand() */ 35#include "non-ascii.h" /* included for Curl_convert_... prototypes */ 36#include "warnless.h" 37 38#define _MPRINTF_REPLACE /* use our functions only */ 39#include <curl/mprintf.h> 40 41/* The last #include file should be: */ 42#include "memdebug.h" 43 44#define MAX_VALUE_LENGTH 256 45#define MAX_CONTENT_LENGTH 1024 46 47static void digest_cleanup_one(struct digestdata *dig); 48 49/* 50 * Return 0 on success and then the buffers are filled in fine. 51 * 52 * Non-zero means failure to parse. 53 */ 54static int get_pair(const char *str, char *value, char *content, 55 const char **endptr) 56{ 57 int c; 58 bool starts_with_quote = FALSE; 59 bool escape = FALSE; 60 61 for(c=MAX_VALUE_LENGTH-1; (*str && (*str != '=') && c--); ) 62 *value++ = *str++; 63 *value=0; 64 65 if('=' != *str++) 66 /* eek, no match */ 67 return 1; 68 69 if('\"' == *str) { 70 /* this starts with a quote so it must end with one as well! */ 71 str++; 72 starts_with_quote = TRUE; 73 } 74 75 for(c=MAX_CONTENT_LENGTH-1; *str && c--; str++) { 76 switch(*str) { 77 case '\\': 78 if(!escape) { 79 /* possibly the start of an escaped quote */ 80 escape = TRUE; 81 *content++ = '\\'; /* even though this is an escape character, we still 82 store it as-is in the target buffer */ 83 continue; 84 } 85 break; 86 case ',': 87 if(!starts_with_quote) { 88 /* this signals the end of the content if we didn't get a starting 89 quote and then we do "sloppy" parsing */ 90 c=0; /* the end */ 91 continue; 92 } 93 break; 94 case '\r': 95 case '\n': 96 /* end of string */ 97 c=0; 98 continue; 99 case '\"': 100 if(!escape && starts_with_quote) { 101 /* end of string */ 102 c=0; 103 continue; 104 } 105 break; 106 } 107 escape = FALSE; 108 *content++ = *str; 109 } 110 *content=0; 111 112 *endptr = str; 113 114 return 0; /* all is fine! */ 115} 116 117/* Test example headers: 118 119WWW-Authenticate: Digest realm="testrealm", nonce="1053604598" 120Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598" 121 122*/ 123 124CURLdigest Curl_input_digest(struct connectdata *conn, 125 bool proxy, 126 const char *header) /* rest of the *-authenticate: 127 header */ 128{ 129 char *token = NULL; 130 char *tmp = NULL; 131 bool foundAuth = FALSE; 132 bool foundAuthInt = FALSE; 133 struct SessionHandle *data=conn->data; 134 bool before = FALSE; /* got a nonce before */ 135 struct digestdata *d; 136 137 if(proxy) { 138 d = &data->state.proxydigest; 139 } 140 else { 141 d = &data->state.digest; 142 } 143 144 if(checkprefix("Digest", header)) { 145 header += strlen("Digest"); 146 147 /* If we already have received a nonce, keep that in mind */ 148 if(d->nonce) 149 before = TRUE; 150 151 /* clear off any former leftovers and init to defaults */ 152 digest_cleanup_one(d); 153 154 for(;;) { 155 char value[MAX_VALUE_LENGTH]; 156 char content[MAX_CONTENT_LENGTH]; 157 158 while(*header && ISSPACE(*header)) 159 header++; 160 161 /* extract a value=content pair */ 162 if(!get_pair(header, value, content, &header)) { 163 if(Curl_raw_equal(value, "nonce")) { 164 d->nonce = strdup(content); 165 if(!d->nonce) 166 return CURLDIGEST_NOMEM; 167 } 168 else if(Curl_raw_equal(value, "stale")) { 169 if(Curl_raw_equal(content, "true")) { 170 d->stale = TRUE; 171 d->nc = 1; /* we make a new nonce now */ 172 } 173 } 174 else if(Curl_raw_equal(value, "realm")) { 175 d->realm = strdup(content); 176 if(!d->realm) 177 return CURLDIGEST_NOMEM; 178 } 179 else if(Curl_raw_equal(value, "opaque")) { 180 d->opaque = strdup(content); 181 if(!d->opaque) 182 return CURLDIGEST_NOMEM; 183 } 184 else if(Curl_raw_equal(value, "qop")) { 185 char *tok_buf; 186 /* tokenize the list and choose auth if possible, use a temporary 187 clone of the buffer since strtok_r() ruins it */ 188 tmp = strdup(content); 189 if(!tmp) 190 return CURLDIGEST_NOMEM; 191 token = strtok_r(tmp, ",", &tok_buf); 192 while(token != NULL) { 193 if(Curl_raw_equal(token, "auth")) { 194 foundAuth = TRUE; 195 } 196 else if(Curl_raw_equal(token, "auth-int")) { 197 foundAuthInt = TRUE; 198 } 199 token = strtok_r(NULL, ",", &tok_buf); 200 } 201 free(tmp); 202 /*select only auth o auth-int. Otherwise, ignore*/ 203 if(foundAuth) { 204 d->qop = strdup("auth"); 205 if(!d->qop) 206 return CURLDIGEST_NOMEM; 207 } 208 else if(foundAuthInt) { 209 d->qop = strdup("auth-int"); 210 if(!d->qop) 211 return CURLDIGEST_NOMEM; 212 } 213 } 214 else if(Curl_raw_equal(value, "algorithm")) { 215 d->algorithm = strdup(content); 216 if(!d->algorithm) 217 return CURLDIGEST_NOMEM; 218 if(Curl_raw_equal(content, "MD5-sess")) 219 d->algo = CURLDIGESTALGO_MD5SESS; 220 else if(Curl_raw_equal(content, "MD5")) 221 d->algo = CURLDIGESTALGO_MD5; 222 else 223 return CURLDIGEST_BADALGO; 224 } 225 else { 226 /* unknown specifier, ignore it! */ 227 } 228 } 229 else 230 break; /* we're done here */ 231 232 /* pass all additional spaces here */ 233 while(*header && ISSPACE(*header)) 234 header++; 235 if(',' == *header) 236 /* allow the list to be comma-separated */ 237 header++; 238 } 239 /* We had a nonce since before, and we got another one now without 240 'stale=true'. This means we provided bad credentials in the previous 241 request */ 242 if(before && !d->stale) 243 return CURLDIGEST_BAD; 244 245 /* We got this header without a nonce, that's a bad Digest line! */ 246 if(!d->nonce) 247 return CURLDIGEST_BAD; 248 } 249 else 250 /* else not a digest, get out */ 251 return CURLDIGEST_NONE; 252 253 return CURLDIGEST_FINE; 254} 255 256/* convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/ 257static void md5_to_ascii(unsigned char *source, /* 16 bytes */ 258 unsigned char *dest) /* 33 bytes */ 259{ 260 int i; 261 for(i=0; i<16; i++) 262 snprintf((char *)&dest[i*2], 3, "%02x", source[i]); 263} 264 265/* Perform quoted-string escaping as described in RFC2616 and its errata */ 266static char *string_quoted(const char *source) 267{ 268 char *dest, *d; 269 const char *s = source; 270 size_t n = 1; /* null terminator */ 271 272 /* Calculate size needed */ 273 while(*s) { 274 ++n; 275 if(*s == '"' || *s == '\\') { 276 ++n; 277 } 278 ++s; 279 } 280 281 dest = malloc(n); 282 if(dest) { 283 s = source; 284 d = dest; 285 while(*s) { 286 if(*s == '"' || *s == '\\') { 287 *d++ = '\\'; 288 } 289 *d++ = *s++; 290 } 291 *d = 0; 292 } 293 294 return dest; 295} 296 297CURLcode Curl_output_digest(struct connectdata *conn, 298 bool proxy, 299 const unsigned char *request, 300 const unsigned char *uripath) 301{ 302 /* We have a Digest setup for this, use it! Now, to get all the details for 303 this sorted out, I must urge you dear friend to read up on the RFC2617 304 section 3.2.2, */ 305 size_t urilen; 306 unsigned char md5buf[16]; /* 16 bytes/128 bits */ 307 unsigned char request_digest[33]; 308 unsigned char *md5this; 309 unsigned char ha1[33];/* 32 digits and 1 zero byte */ 310 unsigned char ha2[33];/* 32 digits and 1 zero byte */ 311 char cnoncebuf[33]; 312 char *cnonce = NULL; 313 size_t cnonce_sz = 0; 314 char *tmp = NULL; 315 char **allocuserpwd; 316 size_t userlen; 317 const char *userp; 318 char *userp_quoted; 319 const char *passwdp; 320 struct auth *authp; 321 322 struct SessionHandle *data = conn->data; 323 struct digestdata *d; 324 CURLcode rc; 325/* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines. 326 It converts digest text to ASCII so the MD5 will be correct for 327 what ultimately goes over the network. 328*/ 329#define CURL_OUTPUT_DIGEST_CONV(a, b) \ 330 rc = Curl_convert_to_network(a, (char *)b, strlen((const char*)b)); \ 331 if(rc != CURLE_OK) { \ 332 free(b); \ 333 return rc; \ 334 } 335 336 if(proxy) { 337 d = &data->state.proxydigest; 338 allocuserpwd = &conn->allocptr.proxyuserpwd; 339 userp = conn->proxyuser; 340 passwdp = conn->proxypasswd; 341 authp = &data->state.authproxy; 342 } 343 else { 344 d = &data->state.digest; 345 allocuserpwd = &conn->allocptr.userpwd; 346 userp = conn->user; 347 passwdp = conn->passwd; 348 authp = &data->state.authhost; 349 } 350 351 Curl_safefree(*allocuserpwd); 352 353 /* not set means empty */ 354 if(!userp) 355 userp=""; 356 357 if(!passwdp) 358 passwdp=""; 359 360 if(!d->nonce) { 361 authp->done = FALSE; 362 return CURLE_OK; 363 } 364 authp->done = TRUE; 365 366 if(!d->nc) 367 d->nc = 1; 368 369 if(!d->cnonce) { 370 snprintf(cnoncebuf, sizeof(cnoncebuf), "%08x%08x%08x%08x", 371 Curl_rand(data), Curl_rand(data), 372 Curl_rand(data), Curl_rand(data)); 373 rc = Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf), 374 &cnonce, &cnonce_sz); 375 if(rc) 376 return rc; 377 d->cnonce = cnonce; 378 } 379 380 /* 381 if the algorithm is "MD5" or unspecified (which then defaults to MD5): 382 383 A1 = unq(username-value) ":" unq(realm-value) ":" passwd 384 385 if the algorithm is "MD5-sess" then: 386 387 A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd ) 388 ":" unq(nonce-value) ":" unq(cnonce-value) 389 */ 390 391 md5this = (unsigned char *) 392 aprintf("%s:%s:%s", userp, d->realm, passwdp); 393 if(!md5this) 394 return CURLE_OUT_OF_MEMORY; 395 396 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ 397 Curl_md5it(md5buf, md5this); 398 Curl_safefree(md5this); 399 md5_to_ascii(md5buf, ha1); 400 401 if(d->algo == CURLDIGESTALGO_MD5SESS) { 402 /* nonce and cnonce are OUTSIDE the hash */ 403 tmp = aprintf("%s:%s:%s", ha1, d->nonce, d->cnonce); 404 if(!tmp) 405 return CURLE_OUT_OF_MEMORY; 406 CURL_OUTPUT_DIGEST_CONV(data, tmp); /* convert on non-ASCII machines */ 407 Curl_md5it(md5buf, (unsigned char *)tmp); 408 Curl_safefree(tmp); 409 md5_to_ascii(md5buf, ha1); 410 } 411 412 /* 413 If the "qop" directive's value is "auth" or is unspecified, then A2 is: 414 415 A2 = Method ":" digest-uri-value 416 417 If the "qop" value is "auth-int", then A2 is: 418 419 A2 = Method ":" digest-uri-value ":" H(entity-body) 420 421 (The "Method" value is the HTTP request method as specified in section 422 5.1.1 of RFC 2616) 423 */ 424 425 /* So IE browsers < v7 cut off the URI part at the query part when they 426 evaluate the MD5 and some (IIS?) servers work with them so we may need to 427 do the Digest IE-style. Note that the different ways cause different MD5 428 sums to get sent. 429 430 Apache servers can be set to do the Digest IE-style automatically using 431 the BrowserMatch feature: 432 http://httpd.apache.org/docs/2.2/mod/mod_auth_digest.html#msie 433 434 Further details on Digest implementation differences: 435 http://www.fngtps.com/2006/09/http-authentication 436 */ 437 438 if(authp->iestyle && ((tmp = strchr((char *)uripath, '?')) != NULL)) 439 urilen = tmp - (char *)uripath; 440 else 441 urilen = strlen((char *)uripath); 442 443 md5this = (unsigned char *)aprintf("%s:%.*s", request, urilen, uripath); 444 445 if(d->qop && Curl_raw_equal(d->qop, "auth-int")) { 446 /* We don't support auth-int for PUT or POST at the moment. 447 TODO: replace md5 of empty string with entity-body for PUT/POST */ 448 unsigned char *md5this2 = (unsigned char *) 449 aprintf("%s:%s", md5this, "d41d8cd98f00b204e9800998ecf8427e"); 450 Curl_safefree(md5this); 451 md5this = md5this2; 452 } 453 454 if(!md5this) 455 return CURLE_OUT_OF_MEMORY; 456 457 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ 458 Curl_md5it(md5buf, md5this); 459 Curl_safefree(md5this); 460 md5_to_ascii(md5buf, ha2); 461 462 if(d->qop) { 463 md5this = (unsigned char *)aprintf("%s:%s:%08x:%s:%s:%s", 464 ha1, 465 d->nonce, 466 d->nc, 467 d->cnonce, 468 d->qop, 469 ha2); 470 } 471 else { 472 md5this = (unsigned char *)aprintf("%s:%s:%s", 473 ha1, 474 d->nonce, 475 ha2); 476 } 477 if(!md5this) 478 return CURLE_OUT_OF_MEMORY; 479 480 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ 481 Curl_md5it(md5buf, md5this); 482 Curl_safefree(md5this); 483 md5_to_ascii(md5buf, request_digest); 484 485 /* for test case 64 (snooped from a Mozilla 1.3a request) 486 487 Authorization: Digest username="testuser", realm="testrealm", \ 488 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca" 489 490 Digest parameters are all quoted strings. Username which is provided by 491 the user will need double quotes and backslashes within it escaped. For 492 the other fields, this shouldn't be an issue. realm, nonce, and opaque 493 are copied as is from the server, escapes and all. cnonce is generated 494 with web-safe characters. uri is already percent encoded. nc is 8 hex 495 characters. algorithm and qop with standard values only contain web-safe 496 chracters. 497 */ 498 userp_quoted = string_quoted(userp); 499 if(!userp_quoted) 500 return CURLE_OUT_OF_MEMORY; 501 502 if(d->qop) { 503 *allocuserpwd = 504 aprintf( "%sAuthorization: Digest " 505 "username=\"%s\", " 506 "realm=\"%s\", " 507 "nonce=\"%s\", " 508 "uri=\"%.*s\", " 509 "cnonce=\"%s\", " 510 "nc=%08x, " 511 "qop=%s, " 512 "response=\"%s\"", 513 proxy?"Proxy-":"", 514 userp_quoted, 515 d->realm, 516 d->nonce, 517 urilen, uripath, /* this is the PATH part of the URL */ 518 d->cnonce, 519 d->nc, 520 d->qop, 521 request_digest); 522 523 if(Curl_raw_equal(d->qop, "auth")) 524 d->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0 padded 525 which tells to the server how many times you are using the 526 same nonce in the qop=auth mode. */ 527 } 528 else { 529 *allocuserpwd = 530 aprintf( "%sAuthorization: Digest " 531 "username=\"%s\", " 532 "realm=\"%s\", " 533 "nonce=\"%s\", " 534 "uri=\"%.*s\", " 535 "response=\"%s\"", 536 proxy?"Proxy-":"", 537 userp_quoted, 538 d->realm, 539 d->nonce, 540 urilen, uripath, /* this is the PATH part of the URL */ 541 request_digest); 542 } 543 Curl_safefree(userp_quoted); 544 if(!*allocuserpwd) 545 return CURLE_OUT_OF_MEMORY; 546 547 /* Add optional fields */ 548 if(d->opaque) { 549 /* append opaque */ 550 tmp = aprintf("%s, opaque=\"%s\"", *allocuserpwd, d->opaque); 551 if(!tmp) 552 return CURLE_OUT_OF_MEMORY; 553 free(*allocuserpwd); 554 *allocuserpwd = tmp; 555 } 556 557 if(d->algorithm) { 558 /* append algorithm */ 559 tmp = aprintf("%s, algorithm=\"%s\"", *allocuserpwd, d->algorithm); 560 if(!tmp) 561 return CURLE_OUT_OF_MEMORY; 562 free(*allocuserpwd); 563 *allocuserpwd = tmp; 564 } 565 566 /* append CRLF + zero (3 bytes) to the userpwd header */ 567 userlen = strlen(*allocuserpwd); 568 tmp = realloc(*allocuserpwd, userlen + 3); 569 if(!tmp) 570 return CURLE_OUT_OF_MEMORY; 571 strcpy(&tmp[userlen], "\r\n"); /* append the data */ 572 *allocuserpwd = tmp; 573 574 return CURLE_OK; 575} 576 577static void digest_cleanup_one(struct digestdata *d) 578{ 579 Curl_safefree(d->nonce); 580 Curl_safefree(d->cnonce); 581 Curl_safefree(d->realm); 582 Curl_safefree(d->opaque); 583 Curl_safefree(d->qop); 584 Curl_safefree(d->algorithm); 585 586 d->nc = 0; 587 d->algo = CURLDIGESTALGO_MD5; /* default algorithm */ 588 d->stale = FALSE; /* default means normal, not stale */ 589} 590 591 592void Curl_digest_cleanup(struct SessionHandle *data) 593{ 594 digest_cleanup_one(&data->state.digest); 595 digest_cleanup_one(&data->state.proxydigest); 596} 597 598#endif 599