1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
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.
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.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
22 ***************************************************************************/
25 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH)
26 /* -- WIN32 approved -- */
36 #include "curl_base64.h"
38 #include "http_digest.h"
40 #include "url.h" /* for Curl_safefree() */
41 #include "curl_memory.h"
42 #include "easyif.h" /* included for Curl_convert_... prototypes */
44 #define _MPRINTF_REPLACE /* use our functions only */
45 #include <curl/mprintf.h>
47 /* The last #include file should be: */
50 #define MAX_VALUE_LENGTH 256
51 #define MAX_CONTENT_LENGTH 1024
54 * Return 0 on success and then the buffers are filled in fine.
56 * Non-zero means failure to parse.
58 static int get_pair(const char *str, char *value, char *content,
62 bool starts_with_quote = FALSE;
65 for(c=MAX_VALUE_LENGTH-1; (*str && (*str != '=') && c--); )
74 /* this starts with a quote so it must end with one as well! */
76 starts_with_quote = TRUE;
79 for(c=MAX_CONTENT_LENGTH-1; *str && c--; str++) {
83 /* possibly the start of an escaped quote */
85 *content++ = '\\'; /* even though this is an escape character, we still
86 store it as-is in the target buffer */
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 */
104 if(!escape && starts_with_quote) {
118 return 0; /* all is fine! */
121 /* Test example headers:
123 WWW-Authenticate: Digest realm="testrealm", nonce="1053604598"
124 Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598"
128 CURLdigest Curl_input_digest(struct connectdata *conn,
130 const char *header) /* rest of the *-authenticate:
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;
143 d = &data->state.proxydigest;
146 d = &data->state.digest;
149 /* skip initial whitespaces */
150 while(*header && ISSPACE(*header))
153 if(checkprefix("Digest", header)) {
154 header += strlen("Digest");
156 /* If we already have received a nonce, keep that in mind */
160 /* clear off any former leftovers and init to defaults */
161 Curl_digest_cleanup_one(d);
164 char value[MAX_VALUE_LENGTH];
165 char content[MAX_CONTENT_LENGTH];
168 while(*header && ISSPACE(*header))
171 /* extract a value=content pair */
172 if(!get_pair(header, value, content, &header)) {
174 if(Curl_raw_equal(value, "nonce")) {
175 d->nonce = strdup(content);
177 return CURLDIGEST_NOMEM;
179 else if(Curl_raw_equal(value, "stale")) {
180 if(Curl_raw_equal(content, "true")) {
182 d->nc = 1; /* we make a new nonce now */
185 else if(Curl_raw_equal(value, "realm")) {
186 d->realm = strdup(content);
188 return CURLDIGEST_NOMEM;
190 else if(Curl_raw_equal(value, "opaque")) {
191 d->opaque = strdup(content);
193 return CURLDIGEST_NOMEM;
195 else if(Curl_raw_equal(value, "qop")) {
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);
201 return CURLDIGEST_NOMEM;
202 token = strtok_r(tmp, ",", &tok_buf);
203 while(token != NULL) {
204 if(Curl_raw_equal(token, "auth")) {
207 else if(Curl_raw_equal(token, "auth-int")) {
210 token = strtok_r(NULL, ",", &tok_buf);
213 /*select only auth o auth-int. Otherwise, ignore*/
215 d->qop = strdup("auth");
217 return CURLDIGEST_NOMEM;
219 else if(foundAuthInt) {
220 d->qop = strdup("auth-int");
222 return CURLDIGEST_NOMEM;
225 else if(Curl_raw_equal(value, "algorithm")) {
226 d->algorithm = strdup(content);
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;
234 return CURLDIGEST_BADALGO;
237 /* unknown specifier, ignore it! */
239 totlen = strlen(value)+strlen(content)+1;
241 if(header[strlen(value)+1] == '\"')
242 /* the contents were within quotes, then add 2 for them to the
247 break; /* we're done here */
249 /* pass all additional spaces here */
250 while(*header && ISSPACE(*header))
253 /* allow the list to be comma-separated */
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
259 if(before && !d->stale)
260 return CURLDIGEST_BAD;
262 /* We got this header without a nonce, that's a bad Digest line! */
264 return CURLDIGEST_BAD;
267 /* else not a digest, get out */
268 return CURLDIGEST_NONE;
270 return CURLDIGEST_FINE;
273 /* convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/
274 static void md5_to_ascii(unsigned char *source, /* 16 bytes */
275 unsigned char *dest) /* 33 bytes */
279 snprintf((char *)&dest[i*2], 3, "%02x", source[i]);
282 CURLcode Curl_output_digest(struct connectdata *conn,
284 const unsigned char *request,
285 const unsigned char *uripath)
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
290 unsigned char md5buf[16]; /* 16 bytes/128 bits */
291 unsigned char request_digest[33];
292 unsigned char *md5this;
294 unsigned char ha2[33];/* 32 digits and 1 zero byte */
305 struct SessionHandle *data = conn->data;
306 struct digestdata *d;
307 #ifdef CURL_DOES_CONVERSIONS
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.
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) { \
320 #define CURL_OUTPUT_DIGEST_CONV(a, b)
321 #endif /* CURL_DOES_CONVERSIONS */
324 d = &data->state.proxydigest;
325 allocuserpwd = &conn->allocptr.proxyuserpwd;
326 userp = conn->proxyuser;
327 passwdp = conn->proxypasswd;
328 authp = &data->state.authproxy;
331 d = &data->state.digest;
332 allocuserpwd = &conn->allocptr.userpwd;
334 passwdp = conn->passwd;
335 authp = &data->state.authhost;
339 Curl_safefree(*allocuserpwd);
340 *allocuserpwd = NULL;
343 /* not set means empty */
360 /* Generate a cnonce */
362 snprintf(cnoncebuf, sizeof(cnoncebuf), "%06ld", (long)now.tv_sec);
363 if(Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf), &cnonce))
366 return CURLE_OUT_OF_MEMORY;
370 if the algorithm is "MD5" or unspecified (which then defaults to MD5):
372 A1 = unq(username-value) ":" unq(realm-value) ":" passwd
374 if the algorithm is "MD5-sess" then:
376 A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd )
377 ":" unq(nonce-value) ":" unq(cnonce-value)
380 md5this = (unsigned char *)
381 aprintf("%s:%s:%s", userp, d->realm, passwdp);
383 return CURLE_OUT_OF_MEMORY;
385 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
386 Curl_md5it(md5buf, md5this);
387 free(md5this); /* free this again */
389 ha1 = malloc(33); /* 32 digits and 1 zero byte */
391 return CURLE_OUT_OF_MEMORY;
393 md5_to_ascii(md5buf, ha1);
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);
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);
407 If the "qop" directive's value is "auth" or is unspecified, then A2 is:
409 A2 = Method ":" digest-uri-value
411 If the "qop" value is "auth-int", then A2 is:
413 A2 = Method ":" digest-uri-value ":" H(entity-body)
415 (The "Method" value is the HTTP request method as specified in section
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
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
428 Further details on Digest implementation differences:
429 http://www.fngtps.com/2006/09/http-authentication
431 if(authp->iestyle && ((tmp = strchr((char *)uripath, '?')) != NULL)) {
432 md5this = (unsigned char *)aprintf("%s:%.*s", request,
433 (int)(tmp - (char *)uripath), uripath);
436 md5this = (unsigned char *)aprintf("%s:%s", request, uripath);
440 return CURLE_OUT_OF_MEMORY;
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
446 /* TODO: Append H(entity-body)*/
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);
454 md5this = (unsigned char *)aprintf("%s:%s:%08x:%s:%s:%s",
463 md5this = (unsigned char *)aprintf("%s:%s:%s",
470 return CURLE_OUT_OF_MEMORY;
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);
477 /* for test case 64 (snooped from a Mozilla 1.3a request)
479 Authorization: Digest username="testuser", realm="testrealm", \
480 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
485 aprintf( "%sAuthorization: Digest "
498 uripath, /* this is the PATH part of the URL */
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. */
511 aprintf( "%sAuthorization: Digest "
521 uripath, /* this is the PATH part of the URL */
525 return CURLE_OUT_OF_MEMORY;
527 /* Add optional fields */
530 tmp = aprintf("%s, opaque=\"%s\"", *allocuserpwd, d->opaque);
532 return CURLE_OUT_OF_MEMORY;
538 /* append algorithm */
539 tmp = aprintf("%s, algorithm=\"%s\"", *allocuserpwd, d->algorithm);
541 return CURLE_OUT_OF_MEMORY;
546 /* append CRLF + zero (3 bytes) to the userpwd header */
547 tmp = realloc(*allocuserpwd, strlen(*allocuserpwd) + 3);
549 return CURLE_OUT_OF_MEMORY;
556 void Curl_digest_cleanup_one(struct digestdata *d)
583 d->algo = CURLDIGESTALGO_MD5; /* default algorithm */
584 d->stale = FALSE; /* default means normal, not stale */
588 void Curl_digest_cleanup(struct SessionHandle *data)
590 Curl_digest_cleanup_one(&data->state.digest);
591 Curl_digest_cleanup_one(&data->state.proxydigest);