smtp: Add support for DIGEST-MD5 authentication
authorGökhan Şengün <gsengun@linux-5d7d.site>
Fri, 30 Mar 2012 20:50:56 +0000 (23:50 +0300)
committerSteve Holme <steve_holme@hotmail.com>
Sat, 31 Mar 2012 21:31:19 +0000 (22:31 +0100)
lib/smtp.c
lib/smtp.h

index 45dec6a..d2cc3d0 100644 (file)
@@ -22,6 +22,7 @@
  * RFC3207 SMTP over TLS
  * RFC4954 SMTP Authentication
  * RFC2195 CRAM-MD5 authentication
+ * RFC2831 DIGEST-MD5 authentication
  * RFC4616 PLAIN authentication
  *
  ***************************************************************************/
@@ -82,6 +83,7 @@
 #include "rawstr.h"
 #include "strtoofft.h"
 #include "curl_base64.h"
+#include "curl_rand.h"
 #include "curl_md5.h"
 #include "curl_hmac.h"
 #include "curl_gethostname.h"
@@ -279,6 +281,33 @@ static int smtp_endofresp(struct pingpong *pp, int *resp)
   return result;
 }
 
+#ifndef CURL_DISABLE_CRYPTO_AUTH
+/* 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
+ */
+static bool smtp_digest_get_key_value(const unsigned char *chlg,
+                                      const char *key,
+                                      char *value,
+                                      size_t max_val_len,
+                                      char end_char)
+{
+  char *find_pos;
+  size_t i;
+
+  find_pos = strstr((const char *) chlg, key);
+  if(!find_pos)
+    return FALSE;
+
+  find_pos += strlen(key);
+
+  for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i)
+    value[i] = *find_pos++;
+  value[i] = '\0';
+
+  return TRUE;
+}
+#endif
+
 /* This is the ONLY way to change SMTP state! */
 static void state(struct connectdata *conn,
                   smtpstate newstate)
@@ -297,6 +326,8 @@ static void state(struct connectdata *conn,
     "AUTHLOGIN",
     "AUTHPASSWD",
     "AUTHCRAM",
+    "AUTHDIGESTMD5",
+    "AUTHDIGESTMD5_RESP",
     "AUTHNTLM",
     "AUTHNTLM_TYPE2MSG",
     "AUTH",
@@ -426,7 +457,12 @@ static CURLcode smtp_authenticate(struct connectdata *conn)
   /* Check supported authentication mechanisms by decreasing order of
      security. */
 #ifndef CURL_DISABLE_CRYPTO_AUTH
-  if(smtpc->authmechs & SMTP_AUTH_CRAM_MD5) {
+  if(smtpc->authmechs & SMTP_AUTH_DIGEST_MD5) {
+    mech = "DIGEST-MD5";
+    state1 = SMTP_AUTHDIGESTMD5;
+    smtpc->authused = SMTP_AUTH_DIGEST_MD5;
+  }
+  else if(smtpc->authmechs & SMTP_AUTH_CRAM_MD5) {
     mech = "CRAM-MD5";
     state1 = SMTP_AUTHCRAMMD5;
     smtpc->authused = SMTP_AUTH_CRAM_MD5;
@@ -816,6 +852,199 @@ static CURLcode smtp_state_authcram_resp(struct connectdata *conn,
   return result;
 }
 
+/* for AUTH DIGEST-MD5 challenge responses */
+static CURLcode smtp_state_authdigest_resp(struct connectdata *conn,
+                                           int smtpcode,
+                                           smtpstate instate)
+{
+  static const char table16[] = "0123456789abcdef";
+
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  char *chlg64 = data->state.buffer;
+  unsigned char *chlg;
+  size_t chlglen;
+  size_t len = 0;
+  size_t i;
+  char *rplyb64 = NULL;
+  MD5_context *ctxt;
+  unsigned char digest[MD5_DIGEST_LEN];
+  char HA1_hex[2 * MD5_DIGEST_LEN + 1];
+  char HA2_hex[2 * MD5_DIGEST_LEN + 1];
+  char resp_hash_hex[2 * MD5_DIGEST_LEN + 1];
+
+  char nonce[64];
+  char realm[128];
+  char alg[64];
+  char nonceCount[] = "00000001";
+  char cnonce[]     = "12345678"; /* will be changed */
+  char method[]     = "AUTHENTICATE";
+  char qop[]        = "auth";
+  char uri[128]     = "smtp/";
+  char response[512];
+
+  (void)instate; /* no use for this yet */
+
+  if(smtpcode != 334) {
+    failf(data, "Access denied: %d", smtpcode);
+    return CURLE_LOGIN_DENIED;
+  }
+
+  /* Get the challenge */
+  for(chlg64 += 4; *chlg64 == ' ' || *chlg64 == '\t'; chlg64++)
+    ;
+
+  chlg = (unsigned char *) NULL;
+  chlglen = 0;
+
+  result = Curl_base64_decode(chlg64, &chlg, &chlglen);
+
+  if(result)
+    return result;
+
+  /* Retrieve nonce string from the challenge */
+  if(!smtp_digest_get_key_value(chlg, "nonce=\"", nonce,
+                                     sizeof(nonce), '\"')) {
+    Curl_safefree(chlg);
+    return CURLE_LOGIN_DENIED;
+  }
+
+  /* Retrieve realm string from the challenge */
+  if(!smtp_digest_get_key_value(chlg, "realm=\"", realm,
+                                     sizeof(realm), '\"')) {
+    /* Challenge does not have a realm, set empty string [RFC2831] page 6 */
+    strcpy(realm, "");
+  }
+
+  /* Retrieve algorithm string from the challenge */
+  if(!smtp_digest_get_key_value(chlg, "algorithm=", alg,
+                                     sizeof(alg), ',')) {
+    Curl_safefree(chlg);
+    return CURLE_LOGIN_DENIED;
+  }
+
+  Curl_safefree(chlg);
+
+  /* We do not support other algorithms */
+  if(strcmp(alg, "md5-sess") != 0)
+    return CURLE_LOGIN_DENIED;
+
+  /* Generate 64 bits of random data */
+  for(i = 0; i < 8; i++)
+    cnonce[i] = table16[Curl_rand()%16];
+
+  /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */
+  ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
+  Curl_MD5_update(ctxt, (const unsigned char *) conn->user,
+                  strlen(conn->user));
+  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
+  Curl_MD5_update(ctxt, (const unsigned char *) realm, strlen(realm));
+  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
+  Curl_MD5_update(ctxt, (const unsigned char *) conn->passwd,
+                  strlen(conn->passwd));
+  Curl_MD5_final(ctxt, digest);
+
+  ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
+  Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN);
+  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
+  Curl_MD5_update(ctxt, (const unsigned char *) nonce, strlen(nonce));
+  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
+  Curl_MD5_update(ctxt, (const unsigned char *) cnonce, strlen(cnonce));
+  Curl_MD5_final(ctxt, digest);
+
+  /* Convert calculated 16 octet hex into 32 bytes string */
+  for(i = 0; i < MD5_DIGEST_LEN; i++)
+    snprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]);
+
+  /* Orepare URL string, append realm to the protocol */
+  strcat(uri, realm);
+
+  /* Calculate H(A2) */
+  ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
+  Curl_MD5_update(ctxt, (const unsigned char *) method, strlen(method));
+  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
+  Curl_MD5_update(ctxt, (const unsigned char *) uri, strlen(uri));
+  Curl_MD5_final(ctxt, digest);
+
+  for(i = 0; i < MD5_DIGEST_LEN; i++)
+    snprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]);
+
+  /* Now calculate the response hash */
+  ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
+  Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN);
+  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
+  Curl_MD5_update(ctxt, (const unsigned char *) nonce, strlen(nonce));
+  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
+
+  Curl_MD5_update(ctxt, (const unsigned char *) nonceCount,
+                         strlen(nonceCount));
+  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
+  Curl_MD5_update(ctxt, (const unsigned char *) cnonce, strlen(cnonce));
+  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
+  Curl_MD5_update(ctxt, (const unsigned char *) qop, strlen(qop));
+  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
+
+  Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN);
+  Curl_MD5_final(ctxt, digest);
+
+  for(i = 0; i < MD5_DIGEST_LEN; i++)
+    snprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]);
+
+  strcpy(response, "username=\"");
+  strcat(response, conn->user);
+  strcat(response, "\",realm=\"");
+  strcat(response, realm);
+  strcat(response, "\",nonce=\"");
+  strcat(response, nonce);
+  strcat(response, "\",cnonce=\"");
+  strcat(response, cnonce);
+  strcat(response, "\",nc=");
+  strcat(response, nonceCount);
+  strcat(response, ",digest-uri=\"");
+  strcat(response, uri);
+  strcat(response, "\",response=");
+  strcat(response, resp_hash_hex);
+
+  /* Encode it to base64 and send it */
+  result = Curl_base64_encode(data, response, 0, &rplyb64, &len);
+
+  if(!result) {
+    if(rplyb64) {
+      result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", rplyb64);
+
+      if(!result)
+        state(conn, SMTP_AUTHDIGESTMD5_RESP);
+    }
+    Curl_safefree(rplyb64);
+  }
+
+  return result;
+}
+
+/* For AUTH DIGEST-MD5 challenge-response responses */
+static CURLcode smtp_state_authdigest_resp_resp(struct connectdata *conn,
+                                                int smtpcode,
+                                                smtpstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+
+  (void)instate; /* no use for this yet */
+
+  if(smtpcode != 334) {
+    failf(data, "Authentication failed: %d", smtpcode);
+    result = CURLE_LOGIN_DENIED;
+  }
+  else {
+    result = Curl_pp_sendf(&conn->proto.smtpc.pp, "");
+
+    if(!result)
+      state(conn, SMTP_AUTH);
+  }
+
+  return result;
+}
+
 #endif
 
 #ifdef USE_NTLM
@@ -1170,6 +1399,14 @@ static CURLcode smtp_statemach_act(struct connectdata *conn)
     case SMTP_AUTHCRAMMD5:
       result = smtp_state_authcram_resp(conn, smtpcode, smtpc->state);
       break;
+
+    case SMTP_AUTHDIGESTMD5:
+      result = smtp_state_authdigest_resp(conn, smtpcode, smtpc->state);
+      break;
+
+    case SMTP_AUTHDIGESTMD5_RESP:
+      result = smtp_state_authdigest_resp_resp(conn, smtpcode, smtpc->state);
+      break;
 #endif
 
 #ifdef USE_NTLM
index 8164037..502f65c 100644 (file)
@@ -40,6 +40,8 @@ typedef enum {
   SMTP_AUTHLOGIN,
   SMTP_AUTHPASSWD,
   SMTP_AUTHCRAMMD5,
+  SMTP_AUTHDIGESTMD5,
+  SMTP_AUTHDIGESTMD5_RESP,
   SMTP_AUTHNTLM,
   SMTP_AUTHNTLM_TYPE2MSG,
   SMTP_AUTH,