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