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