Bug 1631492 - Should not send AUTH XOAUTH command exceeding 255 characters. r=benc
authorMagnus Melin <mkmelin+mozilla@iki.fi>
Wed, 22 Apr 2020 13:41:35 +0300
changeset 38016 71abc0529d32fe645c1a2299c39c2f4732f76015
parent 38015 dd7a7c68060715235e24d7de73d7d2e17187824a
child 38017 542fb0021c53f7d462c07f07419d80664c4f0e33
push id2595
push userclokep@gmail.com
push dateMon, 04 May 2020 19:02:04 +0000
treeherdercomm-beta@f53913797371 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbenc
bugs1631492
Bug 1631492 - Should not send AUTH XOAUTH command exceeding 255 characters. r=benc If the command would get too long, send the command on one line, and the actual string on the next line. The next line is treated as a blob and arbitrary sized content is allowed.
mailnews/local/src/nsPop3Protocol.cpp
mailnews/local/src/nsPop3Protocol.h
--- a/mailnews/local/src/nsPop3Protocol.cpp
+++ b/mailnews/local/src/nsPop3Protocol.cpp
@@ -1563,17 +1563,17 @@ nsresult nsPop3Protocol::ChooseAuthMetho
                    "MSN = 0x%X, PLAIN = 0x%X, LOGIN = 0x%X, USER/PASS = 0x%X, "
                    "XOAUTH2 = 0x%X)"),
            POP3_HAS_AUTH_GSSAPI, POP3_HAS_AUTH_CRAM_MD5, POP3_HAS_AUTH_APOP,
            POP3_HAS_AUTH_NTLM, POP3_HAS_AUTH_MSN, POP3_HAS_AUTH_PLAIN,
            POP3_HAS_AUTH_LOGIN, POP3_HAS_AUTH_USER, POP3_HAS_AUTH_XOAUTH2));
 
   MOZ_LOG(
       POP3LOGMODULE, LogLevel::Debug,
-      (POP3LOG("(Ehabled - GSSAPI=%d, CRAM=%d, APOP=%d, NTLM=%d, "
+      (POP3LOG("(Enabled - GSSAPI=%d, CRAM=%d, APOP=%d, NTLM=%d, "
                "MSN=%d, PLAIN=%d, LOGIN=%d, USER/PASS=%d, "
                "XOAUTH2=%d)"),
        !!(POP3_HAS_AUTH_GSSAPI & availCaps),
        !!(POP3_HAS_AUTH_CRAM_MD5 & availCaps),
        !!(POP3_HAS_AUTH_APOP & availCaps), !!(POP3_HAS_AUTH_NTLM & availCaps),
        !!(POP3_HAS_AUTH_MSN & availCaps), !!(POP3_HAS_AUTH_PLAIN & availCaps),
        !!(POP3_HAS_AUTH_LOGIN & availCaps), !!(POP3_HAS_AUTH_USER & availCaps),
        !!(POP3_HAS_AUTH_XOAUTH2 & availCaps)));
@@ -1743,33 +1743,77 @@ int32_t nsPop3Protocol::AuthOAuth2Respon
     MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
             (POP3LOG("OAuth2 authorizattion failed")));
     return -1;
   }
   m_pop3ConData->pause_for_read = true;
   return 0;
 }
 
+int32_t nsPop3Protocol::OAuth2AuthStep() {
+  MOZ_ASSERT(mOAuth2Support, "Can't do anything without OAuth2 support");
+
+  if (!m_pop3ConData->command_succeeded) {
+    m_OAuth2String.Truncate();
+    m_pop3ConData->next_state = POP3_ERROR_DONE;
+    return -1;
+  }
+
+  nsAutoCString cmdLine2;
+  cmdLine2 += m_OAuth2String;
+  cmdLine2 += CRLF;
+
+  m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP;
+  m_password_already_sent = true;
+  m_OAuth2String.Truncate();
+  if (Pop3SendData(cmdLine2.get(), true)) {
+    MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
+            (POP3LOG("POP: XOAUTH2 authentication (second step) failed")));
+    m_pop3ConData->next_state = POP3_ERROR_DONE;
+  }
+  return 0;
+}
+
 /** msgIOAuth2ModuleListener implementation */
 nsresult nsPop3Protocol::OnSuccess(const nsACString &aOAuth2String) {
   MOZ_ASSERT(mOAuth2Support, "Can't do anything without OAuth2 support");
   // Send the AUTH XOAUTH2 command, and then siphon us back to the regular
   // authentication login stream.
   nsAutoCString cmd;
   cmd.AppendLiteral("AUTH XOAUTH2 ");
   cmd += aOAuth2String;
   cmd += CRLF;
-  m_pop3ConData->next_state = POP3_WAIT_FOR_RESPONSE;
-  m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP;
-  m_pop3ConData->pause_for_read = true;
-  m_password_already_sent = true;
-  if (Pop3SendData(cmd.get(), true)) {
-    MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
-            (POP3LOG("POP: XOAUTH2 authentication failed")));
-    m_pop3ConData->next_state = POP3_ERROR_DONE;
+  // RFC 2449 limits command length to 255 octets. If our command is over that,
+  // send AUTH XOAUTH2 on one line, and the actual data on the next.
+  // The second line is treated as a blob allowing arbitrary size content.
+  if (cmd.Length() <= 255) {
+    m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP;
+    m_password_already_sent = true;
+    if (Pop3SendData(cmd.get(), true)) {
+      MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
+              (POP3LOG("POP: XOAUTH2 authentication failed")));
+      m_pop3ConData->next_state = POP3_ERROR_DONE;
+    }
+  } else {
+    // Stash the string away, in POP3_AUTH_OAUTH2_AUTH_STEP we'll send this too.
+    m_OAuth2String.Assign(aOAuth2String);
+
+    nsAutoCString cmdLine1;
+    cmdLine1.AppendLiteral("AUTH XOAUTH2");
+    cmdLine1 += CRLF;
+    m_pop3ConData->next_state = POP3_WAIT_FOR_RESPONSE;
+    m_pop3ConData->pause_for_read = true;
+    m_pop3ConData->next_state_after_response = POP3_AUTH_OAUTH2_AUTH_STEP;
+    if (Pop3SendData(cmdLine1.get(), false)) {
+      MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
+              (POP3LOG("POP: XOAUTH2 authentication (line1) failed")));
+      m_pop3ConData->next_state = POP3_ERROR_DONE;
+      ProcessProtocolState(nullptr, nullptr, 0, 0);
+      return NS_ERROR_FAILURE;
+    }
   }
   ProcessProtocolState(nullptr, nullptr, 0, 0);
   return NS_OK;
 }
 
 /** msgIOAuth2ModuleListener implementation */
 nsresult nsPop3Protocol::OnFailure(nsresult aError) {
   MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
@@ -3597,16 +3641,20 @@ nsresult nsPop3Protocol::ProcessProtocol
         UpdateStatus("hostContact");
         status = AuthGSSAPIResponse(true);
         break;
 
       case POP3_AUTH_GSSAPI_STEP:
         status = AuthGSSAPIResponse(false);
         break;
 
+      case POP3_AUTH_OAUTH2_AUTH_STEP:
+        status = OAuth2AuthStep();
+        break;
+
       case POP3_AUTH_OAUTH2_RESPONSE:
         status = AuthOAuth2Response();
         break;
 
       case POP3_SEND_USERNAME:
         if (NS_FAILED(
                 StartGetAsyncPassword(POP3_OBTAIN_PASSWORD_BEFORE_USERNAME)))
           status = -1;
--- a/mailnews/local/src/nsPop3Protocol.h
+++ b/mailnews/local/src/nsPop3Protocol.h
@@ -140,17 +140,18 @@ enum Pop3StatesEnum {
    */
   POP3_OBTAIN_PASSWORD_EARLY,                   // 45
   POP3_FINISH_OBTAIN_PASSWORD_EARLY,            // 46
   POP3_OBTAIN_PASSWORD_BEFORE_USERNAME,         // 47
   POP3_FINISH_OBTAIN_PASSWORD_BEFORE_USERNAME,  // 48
   POP3_OBTAIN_PASSWORD_BEFORE_PASSWORD,         // 49
   POP3_FINISH_OBTAIN_PASSWORD_BEFORE_PASSWORD,  // 50
 
-  POP3_AUTH_OAUTH2_RESPONSE,  // 51
+  POP3_AUTH_OAUTH2_RESPONSE,   // 51
+  POP3_AUTH_OAUTH2_AUTH_STEP,  // 52
 };
 
 #define KEEP 'k'        /* If we want to keep this item on server. */
 #define DELETE_CHAR 'd' /* If we want to delete this item. */
 #define TOO_BIG 'b'     /* item left on server because it was too big */
 #define FETCH_BODY 'f'  /* Fetch full body of a partial msg */
 
 typedef struct Pop3UidlEntry { /* information about this message */
@@ -179,17 +180,21 @@ typedef struct _Pop3ConData {
   bool headers_only;         /* Whether to just fetch headers on initial
                                 downloads. */
   int32_t size_limit;        /* Leave messages bigger than this on the
                                 server and only download a partial
                                 message. */
   uint32_t capability_flags; /* What capability this server has? */
 
   Pop3StatesEnum next_state; /* the next state or action to be taken */
+
+  /* When in the generic POP3_WAIT_FOR_RESPONSE state, this indicates which
+   * state we want to go to when a successful response arrives. */
   Pop3StatesEnum next_state_after_response;
+
   bool pause_for_read; /* Pause now for next read? */
 
   bool command_succeeded; /* did the last command succeed? */
   bool list_done;         /* did we get the complete list of msgIDs? */
   int32_t first_msg;
 
   uint32_t obuffer_size;
   uint32_t obuffer_fp;
@@ -281,16 +286,21 @@ class nsPop3Protocol : public nsMsgProto
   virtual ~nsPop3Protocol();
   nsCString m_ApopTimestamp;
   nsCOMPtr<nsIStringBundle> mLocalBundle;
 
   nsCString m_username;
   nsCString m_senderInfo;
   nsCString m_commandResponse;
   nsCString m_GSSAPICache;
+  /**
+   * For keeping track of the OAuth2 string to send, if it's long and can't be
+   * sent with the command directly.
+   */
+  nsCString m_OAuth2String;
 
   // Used for asynchronous password prompts to store the password temporarily.
   nsString m_passwordResult;
 
   // progress state information
   void UpdateProgressPercent(int64_t totalDone, int64_t total);
   void UpdateStatus(const char* aStatusName);
   void UpdateStatusWithString(const char16_t* aString);
@@ -383,16 +393,17 @@ class nsPop3Protocol : public nsMsgProto
   int32_t GetXtndXlstMsgid(nsIInputStream* inputStream, uint32_t length);
   int32_t SendUidlList();
   int32_t GetUidlList(nsIInputStream* inputStream, uint32_t length);
   int32_t GetMsg();
   int32_t SendTop();
   int32_t SendXsender();
   int32_t XsenderResponse();
   int32_t SendRetr();
+  int32_t OAuth2AuthStep();
 
   int32_t RetrResponse(nsIInputStream* inputStream, uint32_t length);
   int32_t TopResponse(nsIInputStream* inputStream, uint32_t length);
   int32_t SendDele();
   int32_t DeleResponse();
   int32_t CommitState(bool remove_last_entry);
 
   Pop3StatesEnum GetNextPasswordObtainState();