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