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
23#include "setup.h"
24
25#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH)
26
27#include "urldata.h"
28#include "sendf.h"
29#include "rawstr.h"
30#include "curl_base64.h"
31#include "curl_md5.h"
32#include "http_digest.h"
33#include "strtok.h"
34#include "url.h" /* for Curl_safefree() */
35#include "curl_memory.h"
36#include "non-ascii.h" /* included for Curl_convert_... prototypes */
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
47static 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 */
54static 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
119WWW-Authenticate: Digest realm="testrealm", nonce="1053604598"
120Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598"
121
122*/
123
124CURLdigest 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  /* skip initial whitespaces */
145  while(*header && ISSPACE(*header))
146    header++;
147
148  if(checkprefix("Digest", header)) {
149    header += strlen("Digest");
150
151    /* If we already have received a nonce, keep that in mind */
152    if(d->nonce)
153      before = TRUE;
154
155    /* clear off any former leftovers and init to defaults */
156    digest_cleanup_one(d);
157
158    for(;;) {
159      char value[MAX_VALUE_LENGTH];
160      char content[MAX_CONTENT_LENGTH];
161
162      while(*header && ISSPACE(*header))
163        header++;
164
165      /* extract a value=content pair */
166      if(!get_pair(header, value, content, &header)) {
167        if(Curl_raw_equal(value, "nonce")) {
168          d->nonce = strdup(content);
169          if(!d->nonce)
170            return CURLDIGEST_NOMEM;
171        }
172        else if(Curl_raw_equal(value, "stale")) {
173          if(Curl_raw_equal(content, "true")) {
174            d->stale = TRUE;
175            d->nc = 1; /* we make a new nonce now */
176          }
177        }
178        else if(Curl_raw_equal(value, "realm")) {
179          d->realm = strdup(content);
180          if(!d->realm)
181            return CURLDIGEST_NOMEM;
182        }
183        else if(Curl_raw_equal(value, "opaque")) {
184          d->opaque = strdup(content);
185          if(!d->opaque)
186            return CURLDIGEST_NOMEM;
187        }
188        else if(Curl_raw_equal(value, "qop")) {
189          char *tok_buf;
190          /* tokenize the list and choose auth if possible, use a temporary
191             clone of the buffer since strtok_r() ruins it */
192          tmp = strdup(content);
193          if(!tmp)
194            return CURLDIGEST_NOMEM;
195          token = strtok_r(tmp, ",", &tok_buf);
196          while(token != NULL) {
197            if(Curl_raw_equal(token, "auth")) {
198              foundAuth = TRUE;
199            }
200            else if(Curl_raw_equal(token, "auth-int")) {
201              foundAuthInt = TRUE;
202            }
203            token = strtok_r(NULL, ",", &tok_buf);
204          }
205          free(tmp);
206          /*select only auth o auth-int. Otherwise, ignore*/
207          if(foundAuth) {
208            d->qop = strdup("auth");
209            if(!d->qop)
210              return CURLDIGEST_NOMEM;
211          }
212          else if(foundAuthInt) {
213            d->qop = strdup("auth-int");
214            if(!d->qop)
215              return CURLDIGEST_NOMEM;
216          }
217        }
218        else if(Curl_raw_equal(value, "algorithm")) {
219          d->algorithm = strdup(content);
220          if(!d->algorithm)
221            return CURLDIGEST_NOMEM;
222          if(Curl_raw_equal(content, "MD5-sess"))
223            d->algo = CURLDIGESTALGO_MD5SESS;
224          else if(Curl_raw_equal(content, "MD5"))
225            d->algo = CURLDIGESTALGO_MD5;
226          else
227            return CURLDIGEST_BADALGO;
228        }
229        else {
230          /* unknown specifier, ignore it! */
231        }
232      }
233      else
234        break; /* we're done here */
235
236      /* pass all additional spaces here */
237      while(*header && ISSPACE(*header))
238        header++;
239      if(',' == *header)
240        /* allow the list to be comma-separated */
241        header++;
242    }
243    /* We had a nonce since before, and we got another one now without
244       'stale=true'. This means we provided bad credentials in the previous
245       request */
246    if(before && !d->stale)
247      return CURLDIGEST_BAD;
248
249    /* We got this header without a nonce, that's a bad Digest line! */
250    if(!d->nonce)
251      return CURLDIGEST_BAD;
252  }
253  else
254    /* else not a digest, get out */
255    return CURLDIGEST_NONE;
256
257  return CURLDIGEST_FINE;
258}
259
260/* convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/
261static void md5_to_ascii(unsigned char *source, /* 16 bytes */
262                         unsigned char *dest) /* 33 bytes */
263{
264  int i;
265  for(i=0; i<16; i++)
266    snprintf((char *)&dest[i*2], 3, "%02x", source[i]);
267}
268
269CURLcode Curl_output_digest(struct connectdata *conn,
270                            bool proxy,
271                            const unsigned char *request,
272                            const unsigned char *uripath)
273{
274  /* We have a Digest setup for this, use it!  Now, to get all the details for
275     this sorted out, I must urge you dear friend to read up on the RFC2617
276     section 3.2.2, */
277  unsigned char md5buf[16]; /* 16 bytes/128 bits */
278  unsigned char request_digest[33];
279  unsigned char *md5this;
280  unsigned char *ha1;
281  unsigned char ha2[33];/* 32 digits and 1 zero byte */
282  char cnoncebuf[7];
283  char *cnonce = NULL;
284  size_t cnonce_sz = 0;
285  char *tmp = NULL;
286  struct timeval now;
287
288  char **allocuserpwd;
289  const char *userp;
290  const char *passwdp;
291  struct auth *authp;
292
293  struct SessionHandle *data = conn->data;
294  struct digestdata *d;
295  CURLcode rc;
296/* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines.
297   It converts digest text to ASCII so the MD5 will be correct for
298   what ultimately goes over the network.
299*/
300#define CURL_OUTPUT_DIGEST_CONV(a, b) \
301  rc = Curl_convert_to_network(a, (char *)b, strlen((const char*)b)); \
302  if(rc != CURLE_OK) { \
303    free(b); \
304    return rc; \
305  }
306
307  if(proxy) {
308    d = &data->state.proxydigest;
309    allocuserpwd = &conn->allocptr.proxyuserpwd;
310    userp = conn->proxyuser;
311    passwdp = conn->proxypasswd;
312    authp = &data->state.authproxy;
313  }
314  else {
315    d = &data->state.digest;
316    allocuserpwd = &conn->allocptr.userpwd;
317    userp = conn->user;
318    passwdp = conn->passwd;
319    authp = &data->state.authhost;
320  }
321
322  if(*allocuserpwd) {
323    Curl_safefree(*allocuserpwd);
324    *allocuserpwd = NULL;
325  }
326
327  /* not set means empty */
328  if(!userp)
329    userp="";
330
331  if(!passwdp)
332    passwdp="";
333
334  if(!d->nonce) {
335    authp->done = FALSE;
336    return CURLE_OK;
337  }
338  authp->done = TRUE;
339
340  if(!d->nc)
341    d->nc = 1;
342
343  if(!d->cnonce) {
344    /* Generate a cnonce */
345    now = Curl_tvnow();
346    snprintf(cnoncebuf, sizeof(cnoncebuf), "%06ld", (long)now.tv_sec);
347
348    rc = Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf),
349                            &cnonce, &cnonce_sz);
350    if(rc)
351      return rc;
352    d->cnonce = cnonce;
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
542static void 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
574void Curl_digest_cleanup(struct SessionHandle *data)
575{
576  digest_cleanup_one(&data->state.digest);
577  digest_cleanup_one(&data->state.proxydigest);
578}
579
580#endif
581