From ede1d346275c10e3b4b6d1456881217779488beb Mon Sep 17 00:00:00 2001
From: kylehuff <code@curetheitch.com>
Date: Fri, 23 Aug 2013 14:05:21 -0400
Subject: [PATCH 1/4] curllib smtp: 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 "AUTH XOAUTH2 <bearer token>".

The base64 encoded token passed in the AUTH commend contains:
  "user=<username>^Aauth=Bearer <bearer token>^A^A"
---
 include/curl/curl.h          |    3 ++
 include/curl/typecheck-gcc.h |    1 +
 lib/curl_sasl.c              |   50 ++++++++++++++++++++++++++++++++
 lib/curl_sasl.h              |    8 +++++
 lib/smtp.c                   |   64 +++++++++++++++++++++++++++++++++++++++++-
 lib/smtp.h                   |    1 +
 lib/url.c                    |   15 ++++++++++
 lib/urldata.h                |    5 +++
 8 files changed, 146 insertions(+), 1 deletions(-)

diff --git a/include/curl/curl.h b/include/curl/curl.h
index 0fb00e8..73f38c1 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -1553,6 +1553,9 @@ typedef enum {
    * prototype defines. (Deprecates CURLOPT_PROGRESSFUNCTION) */
   CINIT(XFERINFOFUNCTION, FUNCTIONPOINT, 219),
 
+  /* The XOAUTH2 bearer token */
+  CINIT(XOAUTH2_BEARER,  OBJECTPOINT, 220),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 
diff --git a/include/curl/typecheck-gcc.h b/include/curl/typecheck-gcc.h
index f8917e8..a9fee0c 100644
--- a/include/curl/typecheck-gcc.h
+++ b/include/curl/typecheck-gcc.h
@@ -264,6 +264,7 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_slist,
    (option) == CURLOPT_RTSP_SESSION_ID ||                                     \
    (option) == CURLOPT_RTSP_STREAM_URI ||                                     \
    (option) == CURLOPT_RTSP_TRANSPORT ||                                      \
+   (option) == CURLOPT_XOAUTH2_BEARER ||                                      \
    0)
 
 /* evaluates to true if option takes a curl_write_callback argument */
diff --git a/lib/curl_sasl.c b/lib/curl_sasl.c
index 924be4b..b9eb441 100644
--- a/lib/curl_sasl.c
+++ b/lib/curl_sasl.c
@@ -161,6 +161,56 @@ CURLcode Curl_sasl_create_login_message(struct SessionHandle *data,
   return Curl_base64_encode(data, valuep, vlen, outptr, outlen);
 }
 
+/*
+ * Curl_sasl_create_xoauth2_message()
+ *
+ * This is used to generate an already encoded XOAUTH2 message ready
+ * for sending to the recipient.
+ *
+ * Parameters:
+ *
+ * data    [in]     - The session handle.
+ * user   [in]      - The user name.
+ * bearer [in]      - The XOAUTH Bearer token.
+ * outptr  [in/out] - The address where a pointer to newly allocated memory
+ *                    holding the result will be stored upon completion.
+ * outlen  [out]    - The length of the output message.
+ *
+ * Returns CURLE_OK on success.
+ */
+CURLcode Curl_sasl_create_xoauth2_message(struct SessionHandle *data,
+                                        const char *user,
+                                        const char *bearer,
+                                        char **outptr, size_t *outlen)
+{
+  char xoauth[2 * MAX_CURL_USER_LENGTH + MAX_CURL_XOAUTH2_LENGTH];
+  size_t ulen;
+  size_t blen;
+
+  ulen = strlen(user);
+  blen = strlen(bearer);
+
+  if(2 * ulen + blen + 2 > sizeof(xoauth)) {
+    *outlen = 0;
+    *outptr = NULL;
+
+    /* xoauth too small */
+    return CURLE_OUT_OF_MEMORY;
+  }
+
+  memcpy(xoauth, "user=", 5);
+  memcpy(xoauth + 5, user, ulen);
+  xoauth[ulen + 5] = '\1';
+  memcpy(xoauth + ulen + 6, "auth=Bearer ", 12);
+  memcpy(xoauth + ulen + 18, bearer, blen);
+  xoauth[ulen + 18 + blen] = '\1';
+  xoauth[ulen + 18 + blen + 1] = '\1';
+
+  /* Base64 encode the reply */
+  return Curl_base64_encode(data, xoauth, ulen + 18 + blen + 2, outptr,
+                            outlen);
+}
+
 #ifndef CURL_DISABLE_CRYPTO_AUTH
 /*
  * Curl_sasl_create_cram_md5_message()
diff --git a/lib/curl_sasl.h b/lib/curl_sasl.h
index 22dcf80..302ba94 100644
--- a/lib/curl_sasl.h
+++ b/lib/curl_sasl.h
@@ -32,6 +32,7 @@
 #define SASL_MECH_GSSAPI        (1 << 4)
 #define SASL_MECH_EXTERNAL      (1 << 5)
 #define SASL_MECH_NTLM          (1 << 6)
+#define SASL_MECH_XOAUTH2       (1 << 7)
 
 /* Authentication mechanism values */
 #define SASL_AUTH_NONE          0
@@ -49,6 +50,13 @@ CURLcode Curl_sasl_create_login_message(struct SessionHandle *data,
                                         const char *valuep, char **outptr,
                                         size_t *outlen);
 
+/* This is used to generate a base64 encoded XOAUTH2 authentication message
+   containing he user name and bearer token */
+CURLcode Curl_sasl_create_xoauth2_message(struct SessionHandle *data,
+                                        const char *user,
+                                        const char *bearer,
+                                        char **outptr, size_t *outlen);
+
 #ifndef CURL_DISABLE_CRYPTO_AUTH
 /* This is used to generate a base64 encoded CRAM-MD5 response message */
 CURLcode Curl_sasl_create_cram_md5_message(struct SessionHandle *data,
diff --git a/lib/smtp.c b/lib/smtp.c
index a56f549..bcbb511 100644
--- a/lib/smtp.c
+++ b/lib/smtp.c
@@ -27,6 +27,7 @@
  * RFC4954 SMTP Authentication
  * RFC5321 SMTP protocol
  * Draft   SMTP URL Interface
+ * RFC6749 OAuth 2.0 Authorization Framework
  *
  ***************************************************************************/
 
@@ -280,6 +281,8 @@ static bool smtp_endofresp(struct connectdata *conn, char *line, size_t len,
           smtpc->authmechs |= SASL_MECH_LOGIN;
         else if(wordlen == 5 && !memcmp(line, "PLAIN", 5))
           smtpc->authmechs |= SASL_MECH_PLAIN;
+        else if(wordlen == 7 && !memcmp(line, "XOAUTH2", 7))
+          smtpc->authmechs |= SASL_MECH_XOAUTH2;
         else if(wordlen == 8 && !memcmp(line, "CRAM-MD5", 8))
           smtpc->authmechs |= SASL_MECH_CRAM_MD5;
         else if(wordlen == 10 && !memcmp(line, "DIGEST-MD5", 10))
@@ -332,6 +335,7 @@ static void state(struct connectdata *conn, smtpstate newstate)
     "DATA",
     "POSTDATA",
     "QUIT",
+    "AUTH_XOAUTH2",
     /* LAST */
   };
 
@@ -496,7 +500,20 @@ static CURLcode smtp_perform_authenticate(struct connectdata *conn)
     }
   else
 #endif
-  if((smtpc->authmechs & SASL_MECH_LOGIN) &&
+
+  if((smtpc->authmechs & SASL_MECH_XOAUTH2) &&
+     (smtpc->prefmech & SASL_MECH_XOAUTH2) &&
+     conn->xoauth2_bearer) {
+    mech = "XOAUTH2";
+    state1 = SMTP_AUTH_XOAUTH2;
+    smtpc->authused = SASL_MECH_XOAUTH2;
+
+    if(data->set.sasl_ir)
+      result = Curl_sasl_create_xoauth2_message(conn->data, conn->user,
+                                              conn->xoauth2_bearer,
+                                              &initresp, &len);
+  }
+  else if((smtpc->authmechs & SASL_MECH_LOGIN) &&
      (smtpc->prefmech & SASL_MECH_LOGIN)) {
     mech = "LOGIN";
     state1 = SMTP_AUTH_LOGIN;
@@ -885,6 +902,43 @@ static CURLcode smtp_state_auth_login_password_resp(struct connectdata *conn,
   return result;
 }
 
+/* For AUTH XOAUTH2 (without initial response) responses */
+static CURLcode smtp_state_auth_xoauth2_resp(struct connectdata *conn,
+                                           int smtpcode,
+                                           smtpstate 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(smtpcode != 334) {
+    failf(data, "Access denied: %d", smtpcode);
+    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.smtpc.pp, "%s", xoauth);
+
+        if(!result)
+          state(conn, SMTP_AUTH_FINAL);
+      }
+
+      Curl_safefree(xoauth);
+    }
+  }
+
+  return result;
+}
+
 #ifndef CURL_DISABLE_CRYPTO_AUTH
 /* For AUTH CRAM-MD5 responses */
 static CURLcode smtp_state_auth_cram_resp(struct connectdata *conn,
@@ -1271,6 +1325,12 @@ static CURLcode smtp_statemach_act(struct connectdata *conn)
                                                    smtpc->state);
       break;
 
+    case SMTP_AUTH_XOAUTH2:
+      if(conn->xoauth2_bearer) {
+        result = smtp_state_auth_xoauth2_resp(conn, smtpcode, smtpc->state);
+        break;
+      }
+
 #ifndef CURL_DISABLE_CRYPTO_AUTH
     case SMTP_AUTH_CRAMMD5:
       result = smtp_state_auth_cram_resp(conn, smtpcode, smtpc->state);
@@ -1730,6 +1790,8 @@ static CURLcode smtp_parse_url_options(struct connectdata *conn)
         smtpc->prefmech = SASL_MECH_LOGIN;
       else if(strequal(value, "PLAIN"))
         smtpc->prefmech = SASL_MECH_PLAIN;
+      else if(strequal(value, "XOAUTH2"))
+        smtpc->prefmech = SASL_MECH_XOAUTH2;
       else if(strequal(value, "CRAM-MD5"))
         smtpc->prefmech = SASL_MECH_CRAM_MD5;
       else if(strequal(value, "DIGEST-MD5"))
diff --git a/lib/smtp.h b/lib/smtp.h
index 4aff0c5..0129cb7 100644
--- a/lib/smtp.h
+++ b/lib/smtp.h
@@ -40,6 +40,7 @@ typedef enum {
   SMTP_AUTH_LOGIN,
   SMTP_AUTH_LOGIN_PASSWD,
   SMTP_AUTH_CRAMMD5,
+  SMTP_AUTH_XOAUTH2,
   SMTP_AUTH_DIGESTMD5,
   SMTP_AUTH_DIGESTMD5_RESP,
   SMTP_AUTH_NTLM,
diff --git a/lib/url.c b/lib/url.c
index 657681d..25b2d95 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -1569,6 +1569,13 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
     result = setstropt(&data->set.str[STRING_PASSWORD],
                        va_arg(param, char *));
     break;
+  case CURLOPT_XOAUTH2_BEARER:
+    /*
+     * XOAUTH2 bearer token to use in the operation
+     */
+    result = setstropt(&data->set.str[STRING_BEARER],
+                       va_arg(param, char *));
+    break;
   case CURLOPT_POSTQUOTE:
     /*
      * List of RAW FTP commands to use after a transfer
@@ -2488,6 +2495,7 @@ static void conn_free(struct connectdata *conn)
 
   Curl_safefree(conn->user);
   Curl_safefree(conn->passwd);
+  Curl_safefree(conn->xoauth2_bearer);
   Curl_safefree(conn->options);
   Curl_safefree(conn->proxyuser);
   Curl_safefree(conn->proxypasswd);
@@ -5181,6 +5189,13 @@ static CURLcode create_conn(struct SessionHandle *data,
     }
   }
 
+  if(data->set.str[STRING_BEARER]) {
+    conn->xoauth2_bearer = strdup(data->set.str[STRING_BEARER]);
+    if(!conn->xoauth2_bearer) {
+      return CURLE_OUT_OF_MEMORY;
+    }
+  }
+
 #ifndef CURL_DISABLE_PROXY
   /*************************************************************
    * Extract the user and password from the authentication string
diff --git a/lib/urldata.h b/lib/urldata.h
index be139ef..cc27f4a 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -876,6 +876,8 @@ struct connectdata {
   char *passwd;  /* password string, allocated */
   char *options; /* options string, allocated */
 
+  char *xoauth2_bearer; /* bearer token for xoauth2, allocated */
+
   char *proxyuser;    /* proxy user name string, allocated */
   char *proxypasswd;  /* proxy password string, allocated */
   curl_proxytype proxytype; /* what kind of proxy that is in use */
@@ -1152,6 +1154,7 @@ typedef enum {
  * Session-data MUST be put in the connectdata struct and here.  */
 #define MAX_CURL_USER_LENGTH 256
 #define MAX_CURL_PASSWORD_LENGTH 256
+#define MAX_CURL_XOAUTH2_LENGTH 256
 
 struct auth {
   unsigned long want;  /* Bitmask set to the authentication methods wanted by
@@ -1367,6 +1370,8 @@ enum dupstring {
   STRING_TLSAUTH_PASSWORD,     /* TLS auth <password> */
 #endif
 
+  STRING_BEARER,          /* <bearer>, if used */
+
   /* -- end of strings -- */
   STRING_LAST /* not used, just an end-of-list marker */
 };
-- 
1.7.5.4

