From dc9bad5236fdb98aa6f0c531aa6ec2d8f17e5e35 Mon Sep 17 00:00:00 2001
From: kylehuff <code@curetheitch.com>
Date: Fri, 23 Aug 2013 14:05:47 -0400
Subject: [PATCH 2/4] curllib imap: add basic XOAUTH2 support

Adds the ability to specify an XOAUTH2 bearer token [RFC6750] via
the option CURLOPT_XOAUTH2_BEARER for authentication using RFC6749
"OAuth 2.0 Authorization Framework".

The Bearer token is expected to be valid for the user specified in
conn->user. If CURLOPT_XOAUTH2_BEARER is defined and the connec-
tion has an advertised auth mechanism of "XOAUTH2", the user and
access token are formatted as a base64 encoded string and sent to
the server as "A01 AUTHENTICATE XOAUTH2 <bearer token>".

The base64 encoded token passed in the AUTH commend contains:
  "user=<username>^Aauth=Bearer <bearer token>^A^A"
---
 lib/imap.c |   66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 lib/imap.h |    1 +
 2 files changed, 66 insertions(+), 1 deletions(-)

diff --git a/lib/imap.c b/lib/imap.c
index a8d9519..b88aab4 100644
--- a/lib/imap.c
+++ b/lib/imap.c
@@ -26,6 +26,7 @@
  * RFC4616 PLAIN authentication
  * RFC4959 IMAP Extension for SASL Initial Client Response
  * RFC5092 IMAP URL Scheme
+ * RFC6749 OAuth 2.0 Authorization Framework
  *
  ***************************************************************************/
 
@@ -339,6 +340,7 @@ static bool imap_endofresp(struct connectdata *conn, char *line, size_t len,
       case IMAP_AUTHENTICATE_PLAIN:
       case IMAP_AUTHENTICATE_LOGIN:
       case IMAP_AUTHENTICATE_LOGIN_PASSWD:
+      case IMAP_AUTHENTICATE_XOAUTH2:
       case IMAP_AUTHENTICATE_CRAMMD5:
       case IMAP_AUTHENTICATE_DIGESTMD5:
       case IMAP_AUTHENTICATE_DIGESTMD5_RESP:
@@ -381,6 +383,7 @@ static void state(struct connectdata *conn, imapstate newstate)
     "AUTHENTICATE_PLAIN",
     "AUTHENTICATE_LOGIN",
     "AUTHENTICATE_LOGIN_PASSWD",
+    "AUTHENTICATE_XOAUTH2",
     "AUTHENTICATE_CRAMMD5",
     "AUTHENTICATE_DIGESTMD5",
     "AUTHENTICATE_DIGESTMD5_RESP",
@@ -575,7 +578,21 @@ static CURLcode imap_perform_authenticate(struct connectdata *conn)
   }
   else
 #endif
-  if((imapc->authmechs & SASL_MECH_LOGIN) &&
+
+  if((imapc->authmechs & SASL_MECH_XOAUTH2) &&
+     (imapc->prefmech & SASL_MECH_XOAUTH2) &&
+     conn->xoauth2_bearer) {
+    mech = "XOAUTH2";
+    state1 = IMAP_AUTHENTICATE_XOAUTH2;
+    state2 = IMAP_AUTHENTICATE_FINAL;
+    imapc->authused = SASL_MECH_XOAUTH2;
+
+    if(imapc->ir_supported || data->set.sasl_ir)
+      result = Curl_sasl_create_xoauth2_message(conn->data, conn->user,
+                                              conn->xoauth2_bearer,
+                                              &initresp, &len);
+  }
+  else if((imapc->authmechs & SASL_MECH_LOGIN) &&
      (imapc->prefmech & SASL_MECH_LOGIN)) {
     mech = "LOGIN";
     state1 = IMAP_AUTHENTICATE_LOGIN;
@@ -869,6 +886,8 @@ static CURLcode imap_state_capability_resp(struct connectdata *conn,
           imapc->authmechs |= SASL_MECH_LOGIN;
         if(wordlen == 5 && !memcmp(line, "PLAIN", 5))
           imapc->authmechs |= SASL_MECH_PLAIN;
+        if(wordlen == 7 && !memcmp(line, "XOAUTH2", 7))
+          imapc->authmechs |= SASL_MECH_XOAUTH2;
         else if(wordlen == 8 && !memcmp(line, "CRAM-MD5", 8))
           imapc->authmechs |= SASL_MECH_CRAM_MD5;
         else if(wordlen == 10 && !memcmp(line, "DIGEST-MD5", 10))
@@ -1042,6 +1061,43 @@ static CURLcode imap_state_auth_login_password_resp(struct connectdata *conn,
   return result;
 }
 
+/* For AUTH XOAUTH2 (without initial response) responses */
+static CURLcode imap_state_auth_xoauth2_resp(struct connectdata *conn,
+                                                    int imapcode,
+                                                    imapstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  size_t len = 0;
+  char *xoauth = NULL;
+
+  (void)instate; /* no use for this yet */
+
+  if(imapcode != '+') {
+    failf(data, "Access denied: %d", imapcode);
+    result = CURLE_LOGIN_DENIED;
+  }
+  else {
+    /* Create the authorisation message */
+    result = Curl_sasl_create_xoauth2_message(conn->data, conn->user,
+                                          conn->xoauth2_bearer, &xoauth, &len);
+
+    /* Send the message */
+    if(!result) {
+      if(xoauth) {
+        result = Curl_pp_sendf(&conn->proto.imapc.pp, "%s", xoauth);
+
+        if(!result)
+          state(conn, IMAP_AUTHENTICATE_FINAL);
+      }
+
+      Curl_safefree(xoauth);
+    }
+  }
+
+  return result;
+}
+
 #ifndef CURL_DISABLE_CRYPTO_AUTH
 /* For AUTHENTICATE CRAM-MD5 responses */
 static CURLcode imap_state_auth_cram_resp(struct connectdata *conn,
@@ -1555,6 +1611,12 @@ static CURLcode imap_statemach_act(struct connectdata *conn)
       result = imap_state_starttls_resp(conn, imapcode, imapc->state);
       break;
 
+    case IMAP_AUTHENTICATE_XOAUTH2:
+      if(conn->xoauth2_bearer) {
+        result = imap_state_auth_xoauth2_resp(conn, imapcode, imapc->state);
+        break;
+      }
+
     case IMAP_AUTHENTICATE_PLAIN:
       result = imap_state_auth_plain_resp(conn, imapcode, imapc->state);
       break;
@@ -2213,6 +2275,8 @@ static CURLcode imap_parse_url_options(struct connectdata *conn)
         imapc->prefmech = SASL_MECH_LOGIN;
       else if(strequal(value, "PLAIN"))
         imapc->prefmech = SASL_MECH_PLAIN;
+      else if(strequal(value, "XOAUTH2"))
+        imapc->prefmech = SASL_MECH_XOAUTH2;
       else if(strequal(value, "CRAM-MD5"))
         imapc->prefmech = SASL_MECH_CRAM_MD5;
       else if(strequal(value, "DIGEST-MD5"))
diff --git a/lib/imap.h b/lib/imap.h
index bc0a83d..0980bc4 100644
--- a/lib/imap.h
+++ b/lib/imap.h
@@ -38,6 +38,7 @@ typedef enum {
   IMAP_AUTHENTICATE_PLAIN,
   IMAP_AUTHENTICATE_LOGIN,
   IMAP_AUTHENTICATE_LOGIN_PASSWD,
+  IMAP_AUTHENTICATE_XOAUTH2,
   IMAP_AUTHENTICATE_CRAMMD5,
   IMAP_AUTHENTICATE_DIGESTMD5,
   IMAP_AUTHENTICATE_DIGESTMD5_RESP,
-- 
1.7.5.4

