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