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
52static void digest_cleanup_one(struct digestdata *dig);
53
54/*
55 * Return 0 on success and then the buffers are filled in fine.
56 *
57 * Non-zero means failure to parse.
58 */
59static int get_pair(const char *str, char *value, char *content,
60                    const char **endptr)
61{
62  int c;
63  bool starts_with_quote = FALSE;
64  bool escape = FALSE;
65
66  for(c=MAX_VALUE_LENGTH-1; (*str && (*str != '=') && c--); )
67    *value++ = *str++;
68  *value=0;
69
70  if('=' != *str++)
71    /* eek, no match */
72    return 1;
73
74  if('\"' == *str) {
75    /* this starts with a quote so it must end with one as well! */
76    str++;
77    starts_with_quote = TRUE;
78  }
79
80  for(c=MAX_CONTENT_LENGTH-1; *str && c--; str++) {
81    switch(*str) {
82    case '\\':
83      if(!escape) {
84        /* possibly the start of an escaped quote */
85        escape = TRUE;
86        *content++ = '\\'; /* even though this is an escape character, we still
87                              store it as-is in the target buffer */
88        continue;
89      }
90      break;
91    case ',':
92      if(!starts_with_quote) {
93        /* this signals the end of the content if we didn't get a starting
94           quote and then we do "sloppy" parsing */
95        c=0; /* the end */
96        continue;
97      }
98      break;
99    case '\r':
100    case '\n':
101      /* end of string */
102      c=0;
103      continue;
104    case '\"':
105      if(!escape && starts_with_quote) {
106        /* end of string */
107        c=0;
108        continue;
109      }
110      break;
111    }
112    escape = FALSE;
113    *content++ = *str;
114  }
115  *content=0;
116
117  *endptr = str;
118
119  return 0; /* all is fine! */
120}
121
122/* Test example headers:
123
124WWW-Authenticate: Digest realm="testrealm", nonce="1053604598"
125Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598"
126
127*/
128
129CURLdigest Curl_input_digest(struct connectdata *conn,
130                             bool proxy,
131                             const char *header) /* rest of the *-authenticate:
132                                                    header */
133{
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    digest_cleanup_one(d);
162
163    for(;;) {
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*/
266static 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
274CURLcode 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  CURLcode rc;
300/* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines.
301   It converts digest text to ASCII so the MD5 will be correct for
302   what ultimately goes over the network.
303*/
304#define CURL_OUTPUT_DIGEST_CONV(a, b) \
305  rc = Curl_convert_to_network(a, (char *)b, strlen((const char*)b)); \
306  if(rc != CURLE_OK) { \
307    free(b); \
308    return rc; \
309  }
310
311  if(proxy) {
312    d = &data->state.proxydigest;
313    allocuserpwd = &conn->allocptr.proxyuserpwd;
314    userp = conn->proxyuser;
315    passwdp = conn->proxypasswd;
316    authp = &data->state.authproxy;
317  }
318  else {
319    d = &data->state.digest;
320    allocuserpwd = &conn->allocptr.userpwd;
321    userp = conn->user;
322    passwdp = conn->passwd;
323    authp = &data->state.authhost;
324  }
325
326  if(*allocuserpwd) {
327    Curl_safefree(*allocuserpwd);
328    *allocuserpwd = NULL;
329  }
330
331  /* not set means empty */
332  if(!userp)
333    userp="";
334
335  if(!passwdp)
336    passwdp="";
337
338  if(!d->nonce) {
339    authp->done = FALSE;
340    return CURLE_OK;
341  }
342  authp->done = TRUE;
343
344  if(!d->nc)
345    d->nc = 1;
346
347  if(!d->cnonce) {
348    /* Generate a cnonce */
349    now = Curl_tvnow();
350    snprintf(cnoncebuf, sizeof(cnoncebuf), "%06ld", (long)now.tv_sec);
351    if(Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf), &cnonce))
352      d->cnonce = cnonce;
353    else
354      return CURLE_OUT_OF_MEMORY;
355  }
356
357  /*
358    if the algorithm is "MD5" or unspecified (which then defaults to MD5):
359
360    A1 = unq(username-value) ":" unq(realm-value) ":" passwd
361
362    if the algorithm is "MD5-sess" then:
363
364    A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd )
365         ":" unq(nonce-value) ":" unq(cnonce-value)
366  */
367
368  md5this = (unsigned char *)
369    aprintf("%s:%s:%s", userp, d->realm, passwdp);
370  if(!md5this)
371    return CURLE_OUT_OF_MEMORY;
372
373  CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
374  Curl_md5it(md5buf, md5this);
375  free(md5this); /* free this again */
376
377  ha1 = malloc(33); /* 32 digits and 1 zero byte */
378  if(!ha1)
379    return CURLE_OUT_OF_MEMORY;
380
381  md5_to_ascii(md5buf, ha1);
382
383  if(d->algo == CURLDIGESTALGO_MD5SESS) {
384    /* nonce and cnonce are OUTSIDE the hash */
385    tmp = aprintf("%s:%s:%s", ha1, d->nonce, d->cnonce);
386    if(!tmp)
387      return CURLE_OUT_OF_MEMORY;
388    CURL_OUTPUT_DIGEST_CONV(data, tmp); /* convert on non-ASCII machines */
389    Curl_md5it(md5buf, (unsigned char *)tmp);
390    free(tmp); /* free this again */
391    md5_to_ascii(md5buf, ha1);
392  }
393
394  /*
395    If the "qop" directive's value is "auth" or is unspecified, then A2 is:
396
397      A2       = Method ":" digest-uri-value
398
399          If the "qop" value is "auth-int", then A2 is:
400
401      A2       = Method ":" digest-uri-value ":" H(entity-body)
402
403    (The "Method" value is the HTTP request method as specified in section
404    5.1.1 of RFC 2616)
405  */
406
407  /* So IE browsers < v7 cut off the URI part at the query part when they
408     evaluate the MD5 and some (IIS?) servers work with them so we may need to
409     do the Digest IE-style. Note that the different ways cause different MD5
410     sums to get sent.
411
412     Apache servers can be set to do the Digest IE-style automatically using
413     the BrowserMatch feature:
414     http://httpd.apache.org/docs/2.2/mod/mod_auth_digest.html#msie
415
416     Further details on Digest implementation differences:
417     http://www.fngtps.com/2006/09/http-authentication
418  */
419  if(authp->iestyle && ((tmp = strchr((char *)uripath, '?')) != NULL)) {
420    md5this = (unsigned char *)aprintf("%s:%.*s", request,
421                                       (int)(tmp - (char *)uripath), uripath);
422  }
423  else
424    md5this = (unsigned char *)aprintf("%s:%s", request, uripath);
425
426  if(!md5this) {
427    free(ha1);
428    return CURLE_OUT_OF_MEMORY;
429  }
430
431  if(d->qop && Curl_raw_equal(d->qop, "auth-int")) {
432    /* We don't support auth-int at the moment. I can't see a easy way to get
433       entity-body here */
434    /* TODO: Append H(entity-body)*/
435  }
436  CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
437  Curl_md5it(md5buf, md5this);
438  free(md5this); /* free this again */
439  md5_to_ascii(md5buf, ha2);
440
441  if(d->qop) {
442    md5this = (unsigned char *)aprintf("%s:%s:%08x:%s:%s:%s",
443                                       ha1,
444                                       d->nonce,
445                                       d->nc,
446                                       d->cnonce,
447                                       d->qop,
448                                       ha2);
449  }
450  else {
451    md5this = (unsigned char *)aprintf("%s:%s:%s",
452                                       ha1,
453                                       d->nonce,
454                                       ha2);
455  }
456  free(ha1);
457  if(!md5this)
458    return CURLE_OUT_OF_MEMORY;
459
460  CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
461  Curl_md5it(md5buf, md5this);
462  free(md5this); /* free this again */
463  md5_to_ascii(md5buf, request_digest);
464
465  /* for test case 64 (snooped from a Mozilla 1.3a request)
466
467    Authorization: Digest username="testuser", realm="testrealm", \
468    nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
469  */
470
471  if(d->qop) {
472    *allocuserpwd =
473      aprintf( "%sAuthorization: Digest "
474               "username=\"%s\", "
475               "realm=\"%s\", "
476               "nonce=\"%s\", "
477               "uri=\"%s\", "
478               "cnonce=\"%s\", "
479               "nc=%08x, "
480               "qop=\"%s\", "
481               "response=\"%s\"",
482               proxy?"Proxy-":"",
483               userp,
484               d->realm,
485               d->nonce,
486               uripath, /* this is the PATH part of the URL */
487               d->cnonce,
488               d->nc,
489               d->qop,
490               request_digest);
491
492    if(Curl_raw_equal(d->qop, "auth"))
493      d->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0 padded
494                  which tells to the server how many times you are using the
495                  same nonce in the qop=auth mode. */
496  }
497  else {
498    *allocuserpwd =
499      aprintf( "%sAuthorization: Digest "
500               "username=\"%s\", "
501               "realm=\"%s\", "
502               "nonce=\"%s\", "
503               "uri=\"%s\", "
504               "response=\"%s\"",
505               proxy?"Proxy-":"",
506               userp,
507               d->realm,
508               d->nonce,
509               uripath, /* this is the PATH part of the URL */
510               request_digest);
511  }
512  if(!*allocuserpwd)
513    return CURLE_OUT_OF_MEMORY;
514
515  /* Add optional fields */
516  if(d->opaque) {
517    /* append opaque */
518    tmp = aprintf("%s, opaque=\"%s\"", *allocuserpwd, d->opaque);
519    if(!tmp)
520      return CURLE_OUT_OF_MEMORY;
521    free(*allocuserpwd);
522    *allocuserpwd = tmp;
523  }
524
525  if(d->algorithm) {
526    /* append algorithm */
527    tmp = aprintf("%s, algorithm=\"%s\"", *allocuserpwd, d->algorithm);
528    if(!tmp)
529      return CURLE_OUT_OF_MEMORY;
530    free(*allocuserpwd);
531    *allocuserpwd = tmp;
532  }
533
534  /* append CRLF + zero (3 bytes) to the userpwd header */
535  tmp = realloc(*allocuserpwd, strlen(*allocuserpwd) + 3);
536  if(!tmp)
537    return CURLE_OUT_OF_MEMORY;
538  strcat(tmp, "\r\n");
539  *allocuserpwd = tmp;
540
541  return CURLE_OK;
542}
543
544static void digest_cleanup_one(struct digestdata *d)
545{
546  if(d->nonce)
547    free(d->nonce);
548  d->nonce = NULL;
549
550  if(d->cnonce)
551    free(d->cnonce);
552  d->cnonce = NULL;
553
554  if(d->realm)
555    free(d->realm);
556  d->realm = NULL;
557
558  if(d->opaque)
559    free(d->opaque);
560  d->opaque = NULL;
561
562  if(d->qop)
563    free(d->qop);
564  d->qop = NULL;
565
566  if(d->algorithm)
567    free(d->algorithm);
568  d->algorithm = NULL;
569
570  d->nc = 0;
571  d->algo = CURLDIGESTALGO_MD5; /* default algorithm */
572  d->stale = FALSE; /* default means normal, not stale */
573}
574
575
576void Curl_digest_cleanup(struct SessionHandle *data)
577{
578  digest_cleanup_one(&data->state.digest);
579  digest_cleanup_one(&data->state.proxydigest);
580}
581
582#endif
583