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