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