74c6e92383cb82898c17bd0f739bfd6a103ddc89
[platform/upstream/curl.git] / lib / http_digest.c
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$
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 "curl_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  */
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
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
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*/
266 static 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
274 CURLcode 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 #ifdef CURL_DOES_CONVERSIONS
300   CURLcode rc;
301 /* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines.
302    It converts digest text to ASCII so the MD5 will be correct for
303    what ultimately goes over the network.
304 */
305 #define CURL_OUTPUT_DIGEST_CONV(a, b) \
306   rc = Curl_convert_to_network(a, (char *)b, strlen((const char*)b)); \
307   if(rc != CURLE_OK) { \
308     free(b); \
309     return rc; \
310   }
311 #else
312 #define CURL_OUTPUT_DIGEST_CONV(a, b)
313 #endif /* CURL_DOES_CONVERSIONS */
314
315   if(proxy) {
316     d = &data->state.proxydigest;
317     allocuserpwd = &conn->allocptr.proxyuserpwd;
318     userp = conn->proxyuser;
319     passwdp = conn->proxypasswd;
320     authp = &data->state.authproxy;
321   }
322   else {
323     d = &data->state.digest;
324     allocuserpwd = &conn->allocptr.userpwd;
325     userp = conn->user;
326     passwdp = conn->passwd;
327     authp = &data->state.authhost;
328   }
329
330   if(*allocuserpwd) {
331     Curl_safefree(*allocuserpwd);
332     *allocuserpwd = NULL;
333   }
334
335   /* not set means empty */
336   if(!userp)
337     userp="";
338
339   if(!passwdp)
340     passwdp="";
341
342   if(!d->nonce) {
343     authp->done = FALSE;
344     return CURLE_OK;
345   }
346   authp->done = TRUE;
347
348   if(!d->nc)
349     d->nc = 1;
350
351   if(!d->cnonce) {
352     /* Generate a cnonce */
353     now = Curl_tvnow();
354     snprintf(cnoncebuf, sizeof(cnoncebuf), "%06ld", (long)now.tv_sec);
355     if(Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf), &cnonce))
356       d->cnonce = cnonce;
357     else
358       return CURLE_OUT_OF_MEMORY;
359   }
360
361   /*
362     if the algorithm is "MD5" or unspecified (which then defaults to MD5):
363
364     A1 = unq(username-value) ":" unq(realm-value) ":" passwd
365
366     if the algorithm is "MD5-sess" then:
367
368     A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd )
369          ":" unq(nonce-value) ":" unq(cnonce-value)
370   */
371
372   md5this = (unsigned char *)
373     aprintf("%s:%s:%s", userp, d->realm, passwdp);
374   if(!md5this)
375     return CURLE_OUT_OF_MEMORY;
376
377   CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
378   Curl_md5it(md5buf, md5this);
379   free(md5this); /* free this again */
380
381   ha1 = malloc(33); /* 32 digits and 1 zero byte */
382   if(!ha1)
383     return CURLE_OUT_OF_MEMORY;
384
385   md5_to_ascii(md5buf, ha1);
386
387   if(d->algo == CURLDIGESTALGO_MD5SESS) {
388     /* nonce and cnonce are OUTSIDE the hash */
389     tmp = aprintf("%s:%s:%s", ha1, d->nonce, d->cnonce);
390     if(!tmp)
391       return CURLE_OUT_OF_MEMORY;
392     CURL_OUTPUT_DIGEST_CONV(data, tmp); /* convert on non-ASCII machines */
393     Curl_md5it(md5buf, (unsigned char *)tmp);
394     free(tmp); /* free this again */
395     md5_to_ascii(md5buf, ha1);
396   }
397
398   /*
399     If the "qop" directive's value is "auth" or is unspecified, then A2 is:
400
401       A2       = Method ":" digest-uri-value
402
403           If the "qop" value is "auth-int", then A2 is:
404
405       A2       = Method ":" digest-uri-value ":" H(entity-body)
406
407     (The "Method" value is the HTTP request method as specified in section
408     5.1.1 of RFC 2616)
409   */
410
411   /* So IE browsers < v7 cut off the URI part at the query part when they
412      evaluate the MD5 and some (IIS?) servers work with them so we may need to
413      do the Digest IE-style. Note that the different ways cause different MD5
414      sums to get sent.
415
416      Apache servers can be set to do the Digest IE-style automatically using
417      the BrowserMatch feature:
418      http://httpd.apache.org/docs/2.2/mod/mod_auth_digest.html#msie
419
420      Further details on Digest implementation differences:
421      http://www.fngtps.com/2006/09/http-authentication
422   */
423   if(authp->iestyle && ((tmp = strchr((char *)uripath, '?')) != NULL)) {
424     md5this = (unsigned char *)aprintf("%s:%.*s", request,
425                                        (int)(tmp - (char *)uripath), uripath);
426   }
427   else
428     md5this = (unsigned char *)aprintf("%s:%s", request, uripath);
429
430   if(!md5this) {
431     free(ha1);
432     return CURLE_OUT_OF_MEMORY;
433   }
434
435   if(d->qop && Curl_raw_equal(d->qop, "auth-int")) {
436     /* We don't support auth-int at the moment. I can't see a easy way to get
437        entity-body here */
438     /* TODO: Append H(entity-body)*/
439   }
440   CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
441   Curl_md5it(md5buf, md5this);
442   free(md5this); /* free this again */
443   md5_to_ascii(md5buf, ha2);
444
445   if(d->qop) {
446     md5this = (unsigned char *)aprintf("%s:%s:%08x:%s:%s:%s",
447                                        ha1,
448                                        d->nonce,
449                                        d->nc,
450                                        d->cnonce,
451                                        d->qop,
452                                        ha2);
453   }
454   else {
455     md5this = (unsigned char *)aprintf("%s:%s:%s",
456                                        ha1,
457                                        d->nonce,
458                                        ha2);
459   }
460   free(ha1);
461   if(!md5this)
462     return CURLE_OUT_OF_MEMORY;
463
464   CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
465   Curl_md5it(md5buf, md5this);
466   free(md5this); /* free this again */
467   md5_to_ascii(md5buf, request_digest);
468
469   /* for test case 64 (snooped from a Mozilla 1.3a request)
470
471     Authorization: Digest username="testuser", realm="testrealm", \
472     nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
473   */
474
475   if(d->qop) {
476     *allocuserpwd =
477       aprintf( "%sAuthorization: Digest "
478                "username=\"%s\", "
479                "realm=\"%s\", "
480                "nonce=\"%s\", "
481                "uri=\"%s\", "
482                "cnonce=\"%s\", "
483                "nc=%08x, "
484                "qop=\"%s\", "
485                "response=\"%s\"",
486                proxy?"Proxy-":"",
487                userp,
488                d->realm,
489                d->nonce,
490                uripath, /* this is the PATH part of the URL */
491                d->cnonce,
492                d->nc,
493                d->qop,
494                request_digest);
495
496     if(Curl_raw_equal(d->qop, "auth"))
497       d->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0 padded
498                   which tells to the server how many times you are using the
499                   same nonce in the qop=auth mode. */
500   }
501   else {
502     *allocuserpwd =
503       aprintf( "%sAuthorization: Digest "
504                "username=\"%s\", "
505                "realm=\"%s\", "
506                "nonce=\"%s\", "
507                "uri=\"%s\", "
508                "response=\"%s\"",
509                proxy?"Proxy-":"",
510                userp,
511                d->realm,
512                d->nonce,
513                uripath, /* this is the PATH part of the URL */
514                request_digest);
515   }
516   if(!*allocuserpwd)
517     return CURLE_OUT_OF_MEMORY;
518
519   /* Add optional fields */
520   if(d->opaque) {
521     /* append opaque */
522     tmp = aprintf("%s, opaque=\"%s\"", *allocuserpwd, d->opaque);
523     if(!tmp)
524       return CURLE_OUT_OF_MEMORY;
525     free(*allocuserpwd);
526     *allocuserpwd = tmp;
527   }
528
529   if(d->algorithm) {
530     /* append algorithm */
531     tmp = aprintf("%s, algorithm=\"%s\"", *allocuserpwd, d->algorithm);
532     if(!tmp)
533       return CURLE_OUT_OF_MEMORY;
534     free(*allocuserpwd);
535     *allocuserpwd = tmp;
536   }
537
538   /* append CRLF + zero (3 bytes) to the userpwd header */
539   tmp = realloc(*allocuserpwd, strlen(*allocuserpwd) + 3);
540   if(!tmp)
541     return CURLE_OUT_OF_MEMORY;
542   strcat(tmp, "\r\n");
543   *allocuserpwd = tmp;
544
545   return CURLE_OK;
546 }
547
548 void Curl_digest_cleanup_one(struct digestdata *d)
549 {
550   if(d->nonce)
551     free(d->nonce);
552   d->nonce = NULL;
553
554   if(d->cnonce)
555     free(d->cnonce);
556   d->cnonce = NULL;
557
558   if(d->realm)
559     free(d->realm);
560   d->realm = NULL;
561
562   if(d->opaque)
563     free(d->opaque);
564   d->opaque = NULL;
565
566   if(d->qop)
567     free(d->qop);
568   d->qop = NULL;
569
570   if(d->algorithm)
571     free(d->algorithm);
572   d->algorithm = NULL;
573
574   d->nc = 0;
575   d->algo = CURLDIGESTALGO_MD5; /* default algorithm */
576   d->stale = FALSE; /* default means normal, not stale */
577 }
578
579
580 void Curl_digest_cleanup(struct SessionHandle *data)
581 {
582   Curl_digest_cleanup_one(&data->state.digest);
583   Curl_digest_cleanup_one(&data->state.proxydigest);
584 }
585
586 #endif