From 31733db174c954a7ba893cccc8eb2a3a926f9548 Mon Sep 17 00:00:00 2001
From: gsengun <gokhansengun@gmail.com>
Date: Mon, 9 Jan 2012 18:59:10 +0200
Subject: [PATCH 2/2] Add DIGEST-MD5 authentication support to SMTP

---
 lib/curl_md5.h |   36 ++++++++-
 lib/md5.c      |   51 ++++++++++++
 lib/smtp.c     |  242 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 lib/smtp.h     |    2 +
 4 files changed, 327 insertions(+), 4 deletions(-)

diff --git a/lib/curl_md5.h b/lib/curl_md5.h
index ddc61e3..817fb4e 100644
--- a/lib/curl_md5.h
+++ b/lib/curl_md5.h
@@ -25,10 +25,44 @@
 #ifndef CURL_DISABLE_CRYPTO_AUTH
 #include "curl_hmac.h"
 
-extern const HMAC_params Curl_HMAC_MD5[1];
+#define MD5_DIGEST_LEN  16
+
+typedef void    (* Curl_MD5_init_func)(void * context);
+typedef void    (* Curl_MD5_update_func)(void * context,
+                                      const unsigned char * data,
+                                      unsigned int len);
+typedef void    (* Curl_MD5_final_func)(unsigned char * result,
+                                      void * context);
+
+
+typedef struct {
+  Curl_MD5_init_func       md5_init;     /* Initialize context procedure. */
+  Curl_MD5_update_func     md5_update;   /* Update context with data. */
+  Curl_MD5_final_func      md5_final;    /* Get final result procedure. */
+  unsigned int             md5_ctxtsize;  /* Context structure size. */
+  unsigned int             md5_resultlen; /* Result length (bytes). */
+} MD5_params;
+
+typedef struct {
+  const MD5_params*  md5_hash;    /* Hash function definition. */
+  void*              md5_hashctx; /* Hash function context. */
+} MD5_context;
+
+/* Prototypes. */
 
 void Curl_md5it(unsigned char *output,
                 const unsigned char *input);
+
+MD5_context * Curl_MD5_init(const MD5_params * md5params);
+
+int Curl_MD5_update(MD5_context * context,
+                     const unsigned char * data,
+                     unsigned int len);
+int Curl_MD5_final(MD5_context * context, unsigned char * result);
+
+extern const MD5_params Curl_DIGEST_MD5[1];
+extern const HMAC_params Curl_HMAC_MD5[1];
+
 #endif
 
 #endif /* HEADER_CURL_MD5_H */
diff --git a/lib/md5.c b/lib/md5.c
index cf8e053..9c5ac06 100644
--- a/lib/md5.c
+++ b/lib/md5.c
@@ -406,6 +406,16 @@ const HMAC_params Curl_HMAC_MD5[] = {
   }
 };
 
+const MD5_params Curl_DIGEST_MD5[] = {
+  {
+    (Curl_MD5_init_func) MD5_Init,       /* digest initialization function. */
+    (Curl_MD5_update_func) MD5_Update,   /* digest update function. */
+    (Curl_MD5_final_func) MD5_Final,     /* digest computation end function. */
+    sizeof(MD5_CTX),                     /* Size of digest context struct. */
+    16                                   /* Result size. */
+  }
+};
+
 
 void Curl_md5it(unsigned char *outbuffer, /* 16 bytes */
                 const unsigned char *input)
@@ -416,4 +426,45 @@ void Curl_md5it(unsigned char *outbuffer, /* 16 bytes */
   MD5_Final(outbuffer, &ctx);
 }
 
+MD5_context * Curl_MD5_init(const MD5_params * md5params)
+{
+  MD5_context* ctxt;
+
+  /* Create MD5 context. */
+  ctxt = malloc(sizeof *ctxt);
+
+  if(!ctxt)
+    return ctxt;
+
+  ctxt->md5_hashctx = malloc(md5params->md5_ctxtsize);
+
+  if(!ctxt->md5_hashctx)
+    return ctxt->md5_hashctx;
+
+  ctxt->md5_hash = md5params;
+
+  (*md5params->md5_init)(ctxt->md5_hashctx);
+
+  return ctxt;
+}
+
+int Curl_MD5_update(MD5_context * context,
+                     const unsigned char * data,
+                     unsigned int len)
+{
+  (*context->md5_hash->md5_update)(context->md5_hashctx, data, len);
+  return 0;
+}
+
+int Curl_MD5_final(MD5_context * context, unsigned char * result)
+{
+  (*context->md5_hash->md5_final)(result, context->md5_hashctx);
+
+  free(context->md5_hashctx);
+  free(context);
+
+  return 0;
+}
+
+
 #endif /* CURL_DISABLE_CRYPTO_AUTH */
diff --git a/lib/smtp.c b/lib/smtp.c
index 4cb25ef..12a50b4 100644
--- a/lib/smtp.c
+++ b/lib/smtp.c
@@ -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"
@@ -298,6 +300,8 @@ static void state(struct connectdata *conn,
     "AUTHLOGIN",
     "AUTHPASSWD",
     "AUTHCRAM",
+    "AUTHDIGESTMD5",
+    "AUTHDIGESTMD5_RESP",
     "AUTHNTLM",
     "AUTHNTLM_TYPE2MSG",
     "AUTH",
@@ -425,7 +429,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_AUTHCRAM;
     smtpc->authused = SMTP_AUTH_CRAM_MD5;
@@ -708,6 +717,200 @@ static CURLcode smtp_state_authpasswd_resp(struct connectdata *conn,
 
 #ifndef CURL_DISABLE_CRYPTO_AUTH
 
+/* retrieve the value for corresponding key from the challenge string
+ * return 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 == NULL)
+    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;
+}
+
+/* for AUTH DIGEST-MD5 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, 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), '\"') == FALSE) {
+    Curl_safefree(chlg);
+    return CURLE_LOGIN_DENIED;
+  }
+
+  /* retrieve realm string from the challenge */
+  if(smtp_digest_get_key_value(chlg, "realm=\"", realm,
+                                     sizeof(realm), '\"') == FALSE) {
+    /* 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), ',') == FALSE) {
+    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 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]);
+
+  /* prepare 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 CRAM-MD5 responses. */
 static CURLcode smtp_state_authcram_resp(struct connectdata *conn,
                                          int smtpcode,
@@ -721,8 +924,8 @@ static CURLcode smtp_state_authcram_resp(struct connectdata *conn,
   size_t len = 0;
   char *rplyb64 = NULL;
   HMAC_context *ctxt;
-  unsigned char digest[16];
-  char reply[MAX_CURL_USER_LENGTH + 32 /* 2 * size of MD5 digest */ + 1];
+  unsigned char digest[MD5_DIGEST_LEN];
+  char reply[MAX_CURL_USER_LENGTH + 2*MD5_DIGEST_LEN + 1];
 
   (void)instate; /* no use for this yet */
 
@@ -869,6 +1072,31 @@ static CURLcode smtp_state_auth_ntlm_type2msg_resp(struct connectdata *conn,
 }
 #endif
 
+
+static CURLcode smtp_state_auth_digest_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;
+}
+
+
 /* for final responses to AUTH sequences. */
 static CURLcode smtp_state_auth_resp(struct connectdata *conn,
                                      int smtpcode,
@@ -1121,6 +1349,10 @@ static CURLcode smtp_statemach_act(struct connectdata *conn)
       break;
 
 #ifndef CURL_DISABLE_CRYPTO_AUTH
+    case SMTP_AUTHDIGESTMD5:
+      result = smtp_state_authdigest_resp(conn, smtpcode, smtpc->state);
+      break;
+
     case SMTP_AUTHCRAM:
       result = smtp_state_authcram_resp(conn, smtpcode, smtpc->state);
       break;
@@ -1141,6 +1373,10 @@ static CURLcode smtp_statemach_act(struct connectdata *conn)
       result = smtp_state_auth_resp(conn, smtpcode, smtpc->state);
       break;
 
+    case SMTP_AUTHDIGESTMD5_RESP:
+      result = smtp_state_auth_digest_resp(conn, smtpcode, smtpc->state);
+      break;
+
     case SMTP_MAIL:
       result = smtp_state_mail_resp(conn, smtpcode, smtpc->state);
       break;
diff --git a/lib/smtp.h b/lib/smtp.h
index 144d496..0a60e51 100644
--- a/lib/smtp.h
+++ b/lib/smtp.h
@@ -40,6 +40,8 @@ typedef enum {
   SMTP_AUTHLOGIN,
   SMTP_AUTHPASSWD,
   SMTP_AUTHCRAM,
+  SMTP_AUTHDIGESTMD5,
+  SMTP_AUTHDIGESTMD5_RESP,
   SMTP_AUTHNTLM,
   SMTP_AUTHNTLM_TYPE2MSG,
   SMTP_AUTH,
-- 
1.7.3.4

