Commit 7e6d51a7 authored by Steve Holme's avatar Steve Holme
Browse files

http_digest: Moved challenge decoding into SASL module

parent 25264131
Loading
Loading
Loading
Loading
+210 −2
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
 * KIND, either express or implied.
 *
 * RFC2195 CRAM-MD5 authentication
 * RFC2617 Basic and Digest Access Authentication
 * RFC2831 DIGEST-MD5 authentication
 * RFC4422 Simple Authentication and Security Layer (SASL)
 * RFC4616 PLAIN authentication
@@ -57,15 +58,90 @@
extern void Curl_sasl_gssapi_cleanup(struct kerberos5data *krb5);
#endif

#if !defined(CURL_DISABLE_CRYPTO_AUTH) && !defined(USE_WINDOWS_SSPI)
#if !defined(CURL_DISABLE_CRYPTO_AUTH)

#if !defined(USE_WINDOWS_SSPI)
#define DIGEST_QOP_VALUE_AUTH             (1 << 0)
#define DIGEST_QOP_VALUE_AUTH_INT         (1 << 1)
#define DIGEST_QOP_VALUE_AUTH_CONF        (1 << 2)
#endif /* !USE_WINDOWS_SSPI */

#define DIGEST_QOP_VALUE_STRING_AUTH      "auth"
#define DIGEST_QOP_VALUE_STRING_AUTH_INT  "auth-int"
#define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf"

#define DIGEST_MAX_VALUE_LENGTH           256
#define DIGEST_MAX_CONTENT_LENGTH         1024

/*
 * Return 0 on success and then the buffers are filled in fine.
 *
 * Non-zero means failure to parse.
 */
static int sasl_digest_get_pair(const char *str, char *value, char *content,
                                const char **endptr)
{
  int c;
  bool starts_with_quote = FALSE;
  bool escape = FALSE;

  for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--); )
    *value++ = *str++;
  *value=0;

  if('=' != *str++)
    /* eek, no match */
    return 1;

  if('\"' == *str) {
    /* this starts with a quote so it must end with one as well! */
    str++;
    starts_with_quote = TRUE;
  }

  for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) {
    switch(*str) {
    case '\\':
      if(!escape) {
        /* possibly the start of an escaped quote */
        escape = TRUE;
        *content++ = '\\'; /* even though this is an escape character, we still
                              store it as-is in the target buffer */
        continue;
      }
      break;
    case ',':
      if(!starts_with_quote) {
        /* this signals the end of the content if we didn't get a starting
           quote and then we do "sloppy" parsing */
        c=0; /* the end */
        continue;
      }
      break;
    case '\r':
    case '\n':
      /* end of string */
      c=0;
      continue;
    case '\"':
      if(!escape && starts_with_quote) {
        /* end of string */
        c=0;
        continue;
      }
      break;
    }
    escape = FALSE;
    *content++ = *str;
  }
  *content=0;

  *endptr = str;

  return 0; /* all is fine! */
}

#if !defined(USE_WINDOWS_SSPI)
/* Retrieves the value for a corresponding key from the challenge string
 * returns TRUE if the key could be found, FALSE if it does not exists
 */
@@ -122,7 +198,9 @@ static CURLcode sasl_digest_get_qop_values(const char *options, int *value)

  return CURLE_OK;
}
#endif
#endif /* !USE_WINDOWS_SSPI */

#endif /* !CURL_DISABLE_CRYPTO_AUTH */

#if !defined(USE_WINDOWS_SSPI)
/*
@@ -582,6 +660,136 @@ CURLcode Curl_sasl_create_digest_md5_message(struct SessionHandle *data,
}
#endif  /* !USE_WINDOWS_SSPI */

/*
 * Curl_sasl_decode_digest_http_message()
 *
 * This is used to decode a HTTP DIGEST challenge message into the seperate
 * attributes.
 *
 * Parameters:
 *
 * chlg    [in]     - Pointer to the challenge message.
 * digest  [in/out] - The digest data struct being used and modified.
 *
 * Returns CURLE_OK on success.
 */
CURLcode Curl_sasl_decode_digest_http_message(const char *chlg,
                                              struct digestdata *digest)
{
  bool before = FALSE; /* got a nonce before */
  bool foundAuth = FALSE;
  bool foundAuthInt = FALSE;
  char *token = NULL;
  char *tmp = NULL;

  /* If we already have received a nonce, keep that in mind */
  if(digest->nonce)
    before = TRUE;

  /* Clean up any former leftovers and initialise to defaults */
  Curl_sasl_digest_cleanup(digest);

  for(;;) {
    char value[DIGEST_MAX_VALUE_LENGTH];
    char content[DIGEST_MAX_CONTENT_LENGTH];

    /* Extract a value=content pair */
    if(!sasl_digest_get_pair(chlg, value, content, &chlg)) {
      if(Curl_raw_equal(value, "nonce")) {
        digest->nonce = strdup(content);
        if(!digest->nonce)
          return CURLE_OUT_OF_MEMORY;
      }
      else if(Curl_raw_equal(value, "stale")) {
        if(Curl_raw_equal(content, "true")) {
          digest->stale = TRUE;
          digest->nc = 1; /* we make a new nonce now */
        }
      }
      else if(Curl_raw_equal(value, "realm")) {
        digest->realm = strdup(content);
        if(!digest->realm)
          return CURLE_OUT_OF_MEMORY;
      }
      else if(Curl_raw_equal(value, "opaque")) {
        digest->opaque = strdup(content);
        if(!digest->opaque)
          return CURLE_OUT_OF_MEMORY;
      }
      else if(Curl_raw_equal(value, "qop")) {
        char *tok_buf;
        /* Tokenize the list and choose auth if possible, use a temporary
            clone of the buffer since strtok_r() ruins it */
        tmp = strdup(content);
        if(!tmp)
          return CURLE_OUT_OF_MEMORY;

        token = strtok_r(tmp, ",", &tok_buf);
        while(token != NULL) {
          if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH)) {
            foundAuth = TRUE;
          }
          else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) {
            foundAuthInt = TRUE;
          }
          token = strtok_r(NULL, ",", &tok_buf);
        }

        free(tmp);

        /* Select only auth o auth-int. Otherwise, ignore */
        if(foundAuth) {
          digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH);
          if(!digest->qop)
            return CURLE_OUT_OF_MEMORY;
        }
        else if(foundAuthInt) {
          digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT);
          if(!digest->qop)
            return CURLE_OUT_OF_MEMORY;
        }
      }
      else if(Curl_raw_equal(value, "algorithm")) {
        digest->algorithm = strdup(content);
        if(!digest->algorithm)
          return CURLE_OUT_OF_MEMORY;

        if(Curl_raw_equal(content, "MD5-sess"))
          digest->algo = CURLDIGESTALGO_MD5SESS;
        else if(Curl_raw_equal(content, "MD5"))
          digest->algo = CURLDIGESTALGO_MD5;
        else
          return CURLE_BAD_CONTENT_ENCODING;
      }
      else {
        /* unknown specifier, ignore it! */
      }
    }
    else
      break; /* we're done here */

    /* Pass all additional spaces here */
    while(*chlg && ISSPACE(*chlg))
      chlg++;

    /* Allow the list to be comma-separated */
    if(',' == *chlg)
      chlg++;
  }

  /* We had a nonce since before, and we got another one now without
     'stale=true'. This means we provided bad credentials in the previous
     request */
  if(before && !digest->stale)
    return CURLE_BAD_CONTENT_ENCODING;

  /* We got this header without a nonce, that's a bad Digest line! */
  if(!digest->nonce)
    return CURLE_BAD_CONTENT_ENCODING;

  return CURLE_OK;
}

/*
 * Curl_sasl_digest_cleanup()
 *
+4 −0
Original line number Diff line number Diff line
@@ -105,6 +105,10 @@ CURLcode Curl_sasl_create_digest_md5_message(struct SessionHandle *data,
                                             const char *service,
                                             char **outptr, size_t *outlen);

/* This is used to decode a HTTP DIGEST challenge message */
CURLcode Curl_sasl_decode_digest_http_message(const char *chlg,
                                              struct digestdata *digest);

/* This is used to clean up the digest specific data */
void Curl_sasl_digest_cleanup(struct digestdata *digest);
#endif
+6 −186
Original line number Diff line number Diff line
@@ -42,77 +42,6 @@
/* The last #include file should be: */
#include "memdebug.h"

#define MAX_VALUE_LENGTH 256
#define MAX_CONTENT_LENGTH 1024

/*
 * Return 0 on success and then the buffers are filled in fine.
 *
 * Non-zero means failure to parse.
 */
static int get_pair(const char *str, char *value, char *content,
                    const char **endptr)
{
  int c;
  bool starts_with_quote = FALSE;
  bool escape = FALSE;

  for(c=MAX_VALUE_LENGTH-1; (*str && (*str != '=') && c--); )
    *value++ = *str++;
  *value=0;

  if('=' != *str++)
    /* eek, no match */
    return 1;

  if('\"' == *str) {
    /* this starts with a quote so it must end with one as well! */
    str++;
    starts_with_quote = TRUE;
  }

  for(c=MAX_CONTENT_LENGTH-1; *str && c--; str++) {
    switch(*str) {
    case '\\':
      if(!escape) {
        /* possibly the start of an escaped quote */
        escape = TRUE;
        *content++ = '\\'; /* even though this is an escape character, we still
                              store it as-is in the target buffer */
        continue;
      }
      break;
    case ',':
      if(!starts_with_quote) {
        /* this signals the end of the content if we didn't get a starting
           quote and then we do "sloppy" parsing */
        c=0; /* the end */
        continue;
      }
      break;
    case '\r':
    case '\n':
      /* end of string */
      c=0;
      continue;
    case '\"':
      if(!escape && starts_with_quote) {
        /* end of string */
        c=0;
        continue;
      }
      break;
    }
    escape = FALSE;
    *content++ = *str;
  }
  *content=0;

  *endptr = str;

  return 0; /* all is fine! */
}

/* Test example headers:

WWW-Authenticate: Digest realm="testrealm", nonce="1053604598"
@@ -125,12 +54,7 @@ CURLcode Curl_input_digest(struct connectdata *conn,
                           const char *header) /* rest of the *-authenticate:
                                                  header */
{
  char *token = NULL;
  char *tmp = NULL;
  bool foundAuth = FALSE;
  bool foundAuthInt = FALSE;
  struct SessionHandle *data=conn->data;
  bool before = FALSE; /* got a nonce before */
  struct digestdata *d;

  if(proxy) {
@@ -140,118 +64,14 @@ CURLcode Curl_input_digest(struct connectdata *conn,
    d = &data->state.digest;
  }

  if(checkprefix("Digest", header)) {
    header += strlen("Digest");

    /* If we already have received a nonce, keep that in mind */
    if(d->nonce)
      before = TRUE;

    /* clear off any former leftovers and init to defaults */
    Curl_sasl_digest_cleanup(d);

    for(;;) {
      char value[MAX_VALUE_LENGTH];
      char content[MAX_CONTENT_LENGTH];

      while(*header && ISSPACE(*header))
        header++;

      /* extract a value=content pair */
      if(!get_pair(header, value, content, &header)) {
        if(Curl_raw_equal(value, "nonce")) {
          d->nonce = strdup(content);
          if(!d->nonce)
            return CURLE_OUT_OF_MEMORY;
        }
        else if(Curl_raw_equal(value, "stale")) {
          if(Curl_raw_equal(content, "true")) {
            d->stale = TRUE;
            d->nc = 1; /* we make a new nonce now */
          }
        }
        else if(Curl_raw_equal(value, "realm")) {
          d->realm = strdup(content);
          if(!d->realm)
            return CURLE_OUT_OF_MEMORY;
        }
        else if(Curl_raw_equal(value, "opaque")) {
          d->opaque = strdup(content);
          if(!d->opaque)
            return CURLE_OUT_OF_MEMORY;
        }
        else if(Curl_raw_equal(value, "qop")) {
          char *tok_buf;
          /* tokenize the list and choose auth if possible, use a temporary
             clone of the buffer since strtok_r() ruins it */
          tmp = strdup(content);
          if(!tmp)
            return CURLE_OUT_OF_MEMORY;

          token = strtok_r(tmp, ",", &tok_buf);
          while(token != NULL) {
            if(Curl_raw_equal(token, "auth")) {
              foundAuth = TRUE;
            }
            else if(Curl_raw_equal(token, "auth-int")) {
              foundAuthInt = TRUE;
            }
            token = strtok_r(NULL, ",", &tok_buf);
          }
          free(tmp);
          /*select only auth o auth-int. Otherwise, ignore*/
          if(foundAuth) {
            d->qop = strdup("auth");
            if(!d->qop)
              return CURLE_OUT_OF_MEMORY;
          }
          else if(foundAuthInt) {
            d->qop = strdup("auth-int");
            if(!d->qop)
              return CURLE_OUT_OF_MEMORY;
          }
        }
        else if(Curl_raw_equal(value, "algorithm")) {
          d->algorithm = strdup(content);
          if(!d->algorithm)
            return CURLE_OUT_OF_MEMORY;

          if(Curl_raw_equal(content, "MD5-sess"))
            d->algo = CURLDIGESTALGO_MD5SESS;
          else if(Curl_raw_equal(content, "MD5"))
            d->algo = CURLDIGESTALGO_MD5;
          else
  if(!checkprefix("Digest", header))
    return CURLE_BAD_CONTENT_ENCODING;
        }
        else {
          /* unknown specifier, ignore it! */
        }
      }
      else
        break; /* we're done here */

      /* pass all additional spaces here */
  header += strlen("Digest");
  while(*header && ISSPACE(*header))
    header++;
      if(',' == *header)
        /* allow the list to be comma-separated */
        header++;
    }
    /* We had a nonce since before, and we got another one now without
       'stale=true'. This means we provided bad credentials in the previous
       request */
    if(before && !d->stale)
      return CURLE_BAD_CONTENT_ENCODING;

    /* We got this header without a nonce, that's a bad Digest line! */
    if(!d->nonce)
      return CURLE_BAD_CONTENT_ENCODING;
  }
  else
    /* else not a digest, get out */
    return CURLE_BAD_CONTENT_ENCODING;

  return CURLE_OK;
  return Curl_sasl_decode_digest_http_message(header, d);
}

/* convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/