- Andre Guibert de Bruet correctly pointed out an over-alloc with one wasted
[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       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*/
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
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 + zero (3 bytes) to the userpwd header */
547   tmp = realloc(*allocuserpwd, strlen(*allocuserpwd) + 3);
548   if(!tmp)
549     return CURLE_OUT_OF_MEMORY;
550   strcat(tmp, "\r\n");
551   *allocuserpwd = tmp;
552
553   return CURLE_OK;
554 }
555
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
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