1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  *
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
6  *
7  * This Original Code has been modified by IBM Corporation. Modifications made
8  * by IBM described herein are Copyright (c) International Business Machines
9  * Corporation, 2000. Modifications to Mozilla code or documentation identified
10  * per MPL Section 3.3
11  *
12  * Jason Eager <jce2@po.cwru.edu>
13  *
14  * Date             Modified by     Description of modification
15  * 04/20/2000       IBM Corp.      OS/2 VisualAge build.
16  * 06/07/2000       Jason Eager    Added check for out of disk space
17  */
18 
19 #include "nscore.h"
20 #include "msgCore.h"  // precompiled header...
21 #include "nsNetUtil.h"
22 #include "nspr.h"
23 #include "plbase64.h"
24 #include "nsIMsgMailNewsUrl.h"
25 #include "nsISafeOutputStream.h"
26 #include "nsPop3Protocol.h"
27 #include "nsIPop3URL.h"
28 #include "MailNewsTypes.h"
29 #include "nsString.h"
30 #include "nsIPrompt.h"
31 #include "nsIMsgIncomingServer.h"
32 #include "nsTextFormatter.h"
33 #include "nsCOMPtr.h"
34 #include "nsIMsgWindow.h"
35 #include "nsIMsgFolder.h"  // TO include biffState enum. Change to bool later...
36 #include "nsIMsgLocalMailFolder.h"
37 #include "nsIDocShell.h"
38 #include "nsMsgUtils.h"
39 #include "nsISocketTransport.h"
40 #include "nsISSLSocketControl.h"
41 #include "nsITransportSecurityInfo.h"
42 #include "nsILineInputStream.h"
43 #include "nsIInterfaceRequestor.h"
44 #include "nsICancelable.h"
45 #include "nsMsgMessageFlags.h"
46 #include "nsMsgBaseCID.h"
47 #include "nsIProxyInfo.h"
48 #include "nsCRT.h"
49 #include "mozilla/Services.h"
50 #include "mozilla/Logging.h"
51 #include "mozilla/Attributes.h"
52 #include "mozilla/Preferences.h"
53 
54 using namespace mozilla;
55 
56 LazyLogModule POP3LOGMODULE("POP3");
57 #define POP3LOG(str) "[this=%p] " str, this
58 
net_pop3_remove_messages_marked_delete(PLHashEntry * he,int msgindex,void * arg)59 static int net_pop3_remove_messages_marked_delete(PLHashEntry* he, int msgindex,
60                                                   void* arg) {
61   Pop3UidlEntry* uidlEntry = (Pop3UidlEntry*)he->value;
62   return (uidlEntry->status == DELETE_CHAR) ? HT_ENUMERATE_REMOVE
63                                             : HT_ENUMERATE_NEXT;
64 }
65 
TimeInSecondsFromPRTime(PRTime prTime)66 uint32_t TimeInSecondsFromPRTime(PRTime prTime) {
67   return (uint32_t)(prTime / PR_USEC_PER_SEC);
68 }
69 
put_hash(PLHashTable * table,const char * key,char value,uint32_t dateReceived)70 static void put_hash(PLHashTable* table, const char* key, char value,
71                      uint32_t dateReceived) {
72   // don't put not used slots or empty uid into hash
73   if (key && *key) {
74     Pop3UidlEntry* tmp = PR_NEWZAP(Pop3UidlEntry);
75     if (tmp) {
76       tmp->uidl = PL_strdup(key);
77       if (tmp->uidl) {
78         tmp->dateReceived = dateReceived;
79         tmp->status = value;
80         PL_HashTableAdd(table, (const void*)tmp->uidl, (void*)tmp);
81       } else
82         PR_Free(tmp);
83     }
84   }
85 }
86 
net_pop3_copy_hash_entries(PLHashEntry * he,int msgindex,void * arg)87 static int net_pop3_copy_hash_entries(PLHashEntry* he, int msgindex,
88                                       void* arg) {
89   Pop3UidlEntry* uidlEntry = (Pop3UidlEntry*)he->value;
90   put_hash((PLHashTable*)arg, uidlEntry->uidl, uidlEntry->status,
91            uidlEntry->dateReceived);
92   return HT_ENUMERATE_NEXT;
93 }
94 
AllocUidlTable(void *,size_t size)95 static void* AllocUidlTable(void* /* pool */, size_t size) {
96   return PR_MALLOC(size);
97 }
98 
FreeUidlTable(void *,void * item)99 static void FreeUidlTable(void* /* pool */, void* item) { PR_Free(item); }
100 
AllocUidlInfo(void * pool,const void * key)101 static PLHashEntry* AllocUidlInfo(void* pool, const void* key) {
102   return PR_NEWZAP(PLHashEntry);
103 }
104 
FreeUidlInfo(void *,PLHashEntry * he,unsigned flag)105 static void FreeUidlInfo(void* /* pool */, PLHashEntry* he, unsigned flag) {
106   if (flag == HT_FREE_ENTRY) {
107     Pop3UidlEntry* uidlEntry = (Pop3UidlEntry*)he->value;
108     if (uidlEntry) {
109       PR_Free(uidlEntry->uidl);
110       PR_Free(uidlEntry);
111     }
112     PR_Free(he);
113   }
114 }
115 
116 static PLHashAllocOps gHashAllocOps = {AllocUidlTable, FreeUidlTable,
117                                        AllocUidlInfo, FreeUidlInfo};
118 
net_pop3_load_state(const char * searchhost,const char * searchuser,nsIFile * mailDirectory)119 static Pop3UidlHost* net_pop3_load_state(const char* searchhost,
120                                          const char* searchuser,
121                                          nsIFile* mailDirectory) {
122   Pop3UidlHost* result = nullptr;
123   Pop3UidlHost* current = nullptr;
124   Pop3UidlHost* tmp;
125 
126   result = PR_NEWZAP(Pop3UidlHost);
127   if (!result) return nullptr;
128   result->host = PL_strdup(searchhost);
129   result->user = PL_strdup(searchuser);
130   result->hash = PL_NewHashTable(20, PL_HashString, PL_CompareStrings,
131                                  PL_CompareValues, &gHashAllocOps, nullptr);
132 
133   if (!result->host || !result->user || !result->hash) {
134     PR_Free(result->host);
135     PR_Free(result->user);
136     if (result->hash) PL_HashTableDestroy(result->hash);
137     PR_Free(result);
138     return nullptr;
139   }
140 
141   nsCOMPtr<nsIFile> popState;
142   mailDirectory->Clone(getter_AddRefs(popState));
143   if (!popState) return nullptr;
144   popState->AppendNative("popstate.dat"_ns);
145 
146   nsCOMPtr<nsIInputStream> fileStream;
147   nsresult rv =
148       NS_NewLocalFileInputStream(getter_AddRefs(fileStream), popState);
149   // It is OK if the file doesn't exist. No state is stored yet.
150   // Return empty list without warning.
151   if (rv == NS_ERROR_FILE_NOT_FOUND) return result;
152   // Warn for other errors.
153   NS_ENSURE_SUCCESS(rv, result);
154 
155   nsCOMPtr<nsILineInputStream> lineInputStream(
156       do_QueryInterface(fileStream, &rv));
157   NS_ENSURE_SUCCESS(rv, result);
158 
159   bool more = true;
160   nsCString line;
161 
162   while (more && NS_SUCCEEDED(rv)) {
163     lineInputStream->ReadLine(line, &more);
164     if (line.IsEmpty()) continue;
165     char firstChar = line.CharAt(0);
166     if (firstChar == '#') continue;
167     if (firstChar == '*') {
168       /* It's a host&user line. */
169       current = nullptr;
170       char* lineBuf =
171           line.BeginWriting() + 1;  // ok because we know the line isn't empty
172       char* host = NS_strtok(" \t\r\n", &lineBuf);
173       /* without space to also get realnames - see bug 225332 */
174       char* user = NS_strtok("\t\r\n", &lineBuf);
175       if (!host || !user) continue;
176       for (tmp = result; tmp; tmp = tmp->next) {
177         if (!strcmp(host, tmp->host) && !strcmp(user, tmp->user)) {
178           current = tmp;
179           break;
180         }
181       }
182       if (!current) {
183         current = PR_NEWZAP(Pop3UidlHost);
184         if (current) {
185           current->host = strdup(host);
186           current->user = strdup(user);
187           current->hash =
188               PL_NewHashTable(20, PL_HashString, PL_CompareStrings,
189                               PL_CompareValues, &gHashAllocOps, nullptr);
190           if (!current->host || !current->user || !current->hash) {
191             PR_Free(current->host);
192             PR_Free(current->user);
193             if (current->hash) PL_HashTableDestroy(current->hash);
194             PR_Free(current);
195           } else {
196             current->next = result->next;
197             result->next = current;
198           }
199         }
200       }
201     } else {
202       /* It's a line with a UIDL on it. */
203       if (current) {
204         for (int32_t pos = line.FindChar('\t'); pos != -1;
205              pos = line.FindChar('\t', pos))
206           line.Replace(pos, 1, ' ');
207 
208         nsTArray<nsCString> lineElems;
209         ParseString(line, ' ', lineElems);
210         if (lineElems.Length() < 2) continue;
211         nsCString* flags = &lineElems[0];
212         nsCString* uidl = &lineElems[1];
213         uint32_t dateReceived = TimeInSecondsFromPRTime(
214             PR_Now());  // if we don't find a date str, assume now.
215         if (lineElems.Length() > 2) dateReceived = atoi(lineElems[2].get());
216         if (!flags->IsEmpty() && !uidl->IsEmpty()) {
217           char flag = flags->CharAt(0);
218           if ((flag == KEEP) || (flag == DELETE_CHAR) || (flag == TOO_BIG) ||
219               (flag == FETCH_BODY)) {
220             put_hash(current->hash, uidl->get(), flag, dateReceived);
221           } else {
222             NS_ASSERTION(false, "invalid flag in popstate.dat");
223           }
224         }
225       }
226     }
227   }
228   fileStream->Close();
229 
230   return result;
231 }
232 
hash_clear_mapper(PLHashEntry * he,int msgindex,void * arg)233 static int hash_clear_mapper(PLHashEntry* he, int msgindex, void* arg) {
234   Pop3UidlEntry* uidlEntry = (Pop3UidlEntry*)he->value;
235   PR_Free(uidlEntry->uidl);
236   PR_Free(uidlEntry);
237   he->value = nullptr;
238 
239   return HT_ENUMERATE_REMOVE;
240 }
241 
hash_empty_mapper(PLHashEntry * he,int msgindex,void * arg)242 static int hash_empty_mapper(PLHashEntry* he, int msgindex, void* arg) {
243   *((bool*)arg) = false;
244   return HT_ENUMERATE_STOP;
245 }
246 
hash_empty(PLHashTable * hash)247 static bool hash_empty(PLHashTable* hash) {
248   bool result = true;
249   PL_HashTableEnumerateEntries(hash, hash_empty_mapper, (void*)&result);
250   return result;
251 }
252 
net_pop3_write_mapper(PLHashEntry * he,int msgindex,void * arg)253 static int net_pop3_write_mapper(PLHashEntry* he, int msgindex, void* arg) {
254   nsIOutputStream* file = (nsIOutputStream*)arg;
255   Pop3UidlEntry* uidlEntry = (Pop3UidlEntry*)he->value;
256   NS_ASSERTION(
257       (uidlEntry->status == KEEP) || (uidlEntry->status == DELETE_CHAR) ||
258           (uidlEntry->status == FETCH_BODY) || (uidlEntry->status == TOO_BIG),
259       "invalid status");
260   char* tmpBuffer =
261       PR_smprintf("%c %s %d" MSG_LINEBREAK, uidlEntry->status,
262                   (char*)uidlEntry->uidl, uidlEntry->dateReceived);
263   PR_ASSERT(tmpBuffer);
264   uint32_t numBytesWritten;
265   file->Write(tmpBuffer, strlen(tmpBuffer), &numBytesWritten);
266   PR_Free(tmpBuffer);
267   return HT_ENUMERATE_NEXT;
268 }
269 
net_pop3_delete_old_msgs_mapper(PLHashEntry * he,int msgindex,void * arg)270 static int net_pop3_delete_old_msgs_mapper(PLHashEntry* he, int msgindex,
271                                            void* arg) {
272   PRTime cutOffDate = (PRTime)arg;
273   Pop3UidlEntry* uidlEntry = (Pop3UidlEntry*)he->value;
274   if (uidlEntry->dateReceived < cutOffDate)
275     uidlEntry->status = DELETE_CHAR;  // mark for deletion
276   return HT_ENUMERATE_NEXT;
277 }
278 
net_pop3_write_state(Pop3UidlHost * host,nsIFile * mailDirectory)279 static void net_pop3_write_state(Pop3UidlHost* host, nsIFile* mailDirectory) {
280   int32_t len = 0;
281   nsCOMPtr<nsIFile> popState;
282 
283   mailDirectory->Clone(getter_AddRefs(popState));
284   if (!popState) return;
285   popState->AppendNative("popstate.dat"_ns);
286 
287   nsCOMPtr<nsIOutputStream> fileOutputStream;
288   nsresult rv = MsgNewSafeBufferedFileOutputStream(
289       getter_AddRefs(fileOutputStream), popState, -1, 00600);
290   if (NS_FAILED(rv)) return;
291 
292   const char tmpBuffer[] =
293       "# POP3 State File" MSG_LINEBREAK
294       "# This is a generated file!  Do not edit." MSG_LINEBREAK MSG_LINEBREAK;
295 
296   uint32_t numBytesWritten;
297   fileOutputStream->Write(tmpBuffer, strlen(tmpBuffer), &numBytesWritten);
298 
299   for (; host && (len >= 0); host = host->next) {
300     if (!hash_empty(host->hash)) {
301       fileOutputStream->Write("*", 1, &numBytesWritten);
302       fileOutputStream->Write(host->host, strlen(host->host), &numBytesWritten);
303       fileOutputStream->Write(" ", 1, &numBytesWritten);
304       fileOutputStream->Write(host->user, strlen(host->user), &numBytesWritten);
305       fileOutputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN,
306                               &numBytesWritten);
307       PL_HashTableEnumerateEntries(host->hash, net_pop3_write_mapper,
308                                    (void*)fileOutputStream);
309     }
310   }
311   nsCOMPtr<nsISafeOutputStream> safeStream =
312       do_QueryInterface(fileOutputStream);
313   NS_ASSERTION(safeStream, "expected a safe output stream!");
314   if (safeStream) {
315     rv = safeStream->Finish();
316     if (NS_FAILED(rv)) {
317       NS_WARNING("failed to save pop state! possible data loss");
318     }
319   }
320 }
321 
net_pop3_free_state(Pop3UidlHost * host)322 static void net_pop3_free_state(Pop3UidlHost* host) {
323   Pop3UidlHost* h;
324   while (host) {
325     h = host->next;
326     PR_Free(host->host);
327     PR_Free(host->user);
328     PL_HashTableDestroy(host->hash);
329     PR_Free(host);
330     host = h;
331   }
332 }
333 
334 /*
335    Look for a specific UIDL string in our hash tables, if we have it then we
336    need to mark the message for deletion so that it can be deleted later. If the
337    uidl of the message is not found, then the message was downloaded completely
338    and already deleted from the server. So this only applies to messages kept on
339    the server or too big for download.
340  */
341 /* static */
MarkMsgInHashTable(PLHashTable * hashTable,const Pop3UidlEntry * uidlE,bool * changed)342 void nsPop3Protocol::MarkMsgInHashTable(PLHashTable* hashTable,
343                                         const Pop3UidlEntry* uidlE,
344                                         bool* changed) {
345   if (uidlE->uidl) {
346     Pop3UidlEntry* uidlEntry =
347         (Pop3UidlEntry*)PL_HashTableLookup(hashTable, uidlE->uidl);
348     if (uidlEntry) {
349       if (uidlEntry->status != uidlE->status) {
350         uidlEntry->status = uidlE->status;
351         *changed = true;
352       }
353     }
354   }
355 }
356 
357 /* static */
MarkMsgForHost(const char * hostName,const char * userName,nsIFile * mailDirectory,nsTArray<Pop3UidlEntry * > & UIDLArray)358 nsresult nsPop3Protocol::MarkMsgForHost(const char* hostName,
359                                         const char* userName,
360                                         nsIFile* mailDirectory,
361                                         nsTArray<Pop3UidlEntry*>& UIDLArray) {
362   if (!hostName || !userName || !mailDirectory) return NS_ERROR_NULL_POINTER;
363 
364   Pop3UidlHost* uidlHost =
365       net_pop3_load_state(hostName, userName, mailDirectory);
366   if (!uidlHost) return NS_ERROR_OUT_OF_MEMORY;
367 
368   bool changed = false;
369 
370   uint32_t count = UIDLArray.Length();
371   for (uint32_t i = 0; i < count; i++) {
372     MarkMsgInHashTable(uidlHost->hash, UIDLArray[i], &changed);
373   }
374 
375   if (changed) net_pop3_write_state(uidlHost, mailDirectory);
376   net_pop3_free_state(uidlHost);
377   return NS_OK;
378 }
379 
380 // nsPop3Protocol class implementation
381 
NS_IMPL_ADDREF_INHERITED(nsPop3Protocol,nsMsgProtocol)382 NS_IMPL_ADDREF_INHERITED(nsPop3Protocol, nsMsgProtocol)
383 NS_IMPL_RELEASE_INHERITED(nsPop3Protocol, nsMsgProtocol)
384 
385 NS_INTERFACE_MAP_BEGIN(nsPop3Protocol)
386   NS_INTERFACE_MAP_ENTRY(nsIPop3Protocol)
387   NS_INTERFACE_MAP_ENTRY(msgIOAuth2ModuleListener)
388   NS_INTERFACE_MAP_ENTRY(nsIMsgAsyncPromptListener)
389   NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
390 NS_INTERFACE_MAP_END_INHERITING(nsMsgProtocol)
391 
392 nsPop3Protocol::nsPop3Protocol(nsIURI* aURL)
393     : nsMsgProtocol(aURL),
394       m_bytesInMsgReceived(0),
395       m_totalFolderSize(0),
396       m_totalDownloadSize(0),
397       m_totalBytesReceived(0),
398       m_pop3ConData(nullptr) {}
399 
Initialize(nsIURI * aURL)400 nsresult nsPop3Protocol::Initialize(nsIURI* aURL) {
401   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Initialize()")));
402 
403   m_pop3ConData = (Pop3ConData*)PR_NEWZAP(Pop3ConData);
404   if (!m_pop3ConData) return NS_ERROR_OUT_OF_MEMORY;
405 
406   m_totalBytesReceived = 0;
407   m_bytesInMsgReceived = 0;
408   m_totalFolderSize = 0;
409   m_totalDownloadSize = 0;
410   m_totalBytesReceived = 0;
411   m_tlsEnabled = false;
412   m_socketType = nsMsgSocketType::trySTARTTLS;
413   m_prefAuthMethods = POP3_AUTH_MECH_UNDEFINED;
414   m_failedAuthMethods = 0;
415   m_password_already_sent = false;
416   m_currentAuthMethod = POP3_AUTH_MECH_UNDEFINED;
417   m_needToRerunUrl = false;
418 
419   m_url = aURL;
420 
421   m_lineStreamBuffer = new nsMsgLineStreamBuffer(OUTPUT_BUFFER_SIZE, true);
422 
423   nsCOMPtr<nsIStringBundleService> bundleService =
424       mozilla::services::GetStringBundleService();
425   NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
426   return bundleService->CreateBundle(
427       "chrome://messenger/locale/localMsgs.properties",
428       getter_AddRefs(mLocalBundle));
429 }
430 
431 // nsIProtocolProxyCallback
432 NS_IMETHODIMP
OnProxyAvailable(nsICancelable * aRequest,nsIChannel * aChannel,nsIProxyInfo * aProxyInfo,nsresult aStatus)433 nsPop3Protocol::OnProxyAvailable(nsICancelable* aRequest, nsIChannel* aChannel,
434                                  nsIProxyInfo* aProxyInfo, nsresult aStatus) {
435   // If we're called with NS_BINDING_ABORTED, we came here via Cancel().
436   // Otherwise, no checking of 'aStatus' here, see
437   // nsHttpChannel::OnProxyAvailable(). Status is non-fatal and we just kick on.
438   if (aStatus == NS_BINDING_ABORTED) return NS_ERROR_FAILURE;
439 
440   nsresult rv = InitializeInternal(aProxyInfo);
441   if (NS_FAILED(rv)) {
442     Cancel(rv);
443     return rv;
444   }
445 
446   rv = LoadUrlInternal(m_url);
447   if (NS_FAILED(rv)) {
448     Cancel(rv);
449   }
450 
451   return rv;
452 }
453 
InitializeInternal(nsIProxyInfo * aProxyInfo)454 nsresult nsPop3Protocol::InitializeInternal(nsIProxyInfo* aProxyInfo) {
455   nsresult rv;
456   m_proxyRequest = nullptr;
457 
458   NS_ENSURE_TRUE(m_url, NS_ERROR_NOT_INITIALIZED);
459 
460   // extract out message feedback if there is any.
461   nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
462   if (mailnewsUrl) {
463     nsCOMPtr<nsIMsgIncomingServer> server;
464     mailnewsUrl->GetServer(getter_AddRefs(server));
465     NS_ENSURE_TRUE(server, NS_MSG_INVALID_OR_MISSING_SERVER);
466 
467     // Query for OAuth2 support. If the POP server preferences don't allow
468     // for OAuth2, then don't carry around the OAuth2 module any longer
469     // since we won't need it.
470     mOAuth2Support = do_CreateInstance(MSGIOAUTH2MODULE_CONTRACTID);
471     if (mOAuth2Support) {
472       bool supportsOAuth = false;
473       mOAuth2Support->InitFromMail(server, &supportsOAuth);
474       if (!supportsOAuth) mOAuth2Support = nullptr;
475     }
476 
477     rv = server->GetSocketType(&m_socketType);
478     NS_ENSURE_SUCCESS(rv, rv);
479 
480     int32_t authMethod = 0;
481     rv = server->GetAuthMethod(&authMethod);
482     NS_ENSURE_SUCCESS(rv, rv);
483     InitPrefAuthMethods(authMethod);
484 
485     m_pop3Server = do_QueryInterface(server);
486     if (m_pop3Server)
487       m_pop3Server->GetPop3CapabilityFlags(&m_pop3ConData->capability_flags);
488   }
489 
490   // When we are making a secure connection, we need to make sure that we
491   // pass an interface requestor down to the socket transport so that PSM can
492   // retrieve a nsIPrompt instance if needed.
493   nsCOMPtr<nsIInterfaceRequestor> ir;
494   if (m_socketType != nsMsgSocketType::plain) {
495     nsCOMPtr<nsIMsgWindow> msgwin;
496     if (mailnewsUrl) mailnewsUrl->GetMsgWindow(getter_AddRefs(msgwin));
497     if (!msgwin) GetTopmostMsgWindow(getter_AddRefs(msgwin));
498     if (msgwin) {
499       nsCOMPtr<nsIDocShell> docshell;
500       msgwin->GetRootDocShell(getter_AddRefs(docshell));
501       ir = do_QueryInterface(docshell);
502       nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
503       msgwin->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks));
504       if (notificationCallbacks) {
505         nsCOMPtr<nsIInterfaceRequestor> aggregrateIR;
506         NS_NewInterfaceRequestorAggregation(notificationCallbacks, ir,
507                                             getter_AddRefs(aggregrateIR));
508         ir = aggregrateIR;
509       }
510     }
511   }
512 
513   int32_t port = 0;
514   m_url->GetPort(&port);
515 
516   nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
517   nsAutoCString hostName;
518   if (server) server->GetRealHostName(hostName);
519 
520   const char* connectionType = nullptr;
521   if (m_socketType == nsMsgSocketType::SSL)
522     connectionType = "ssl";
523   else if (m_socketType == nsMsgSocketType::trySTARTTLS ||
524            m_socketType == nsMsgSocketType::alwaysSTARTTLS)
525     connectionType = "starttls";
526 
527   rv = OpenNetworkSocketWithInfo(hostName.get(), port, connectionType,
528                                  aProxyInfo, ir);
529   if (NS_FAILED(rv) && m_socketType == nsMsgSocketType::trySTARTTLS) {
530     m_socketType = nsMsgSocketType::plain;
531     rv = OpenNetworkSocketWithInfo(hostName.get(), port, nullptr, aProxyInfo,
532                                    ir);
533   }
534 
535   return rv;
536 }
537 
~nsPop3Protocol()538 nsPop3Protocol::~nsPop3Protocol() {
539   Cleanup();
540   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("~nsPop3Protocol()")));
541 }
542 
Cleanup()543 void nsPop3Protocol::Cleanup() {
544   if (m_pop3ConData->newuidl) {
545     PL_HashTableDestroy(m_pop3ConData->newuidl);
546     m_pop3ConData->newuidl = nullptr;
547   }
548 
549   net_pop3_free_state(m_pop3ConData->uidlinfo);
550 
551   FreeMsgInfo();
552   PR_Free(m_pop3ConData->only_uidl);
553   PR_Free(m_pop3ConData);
554 }
555 
SetCapFlag(uint32_t flag)556 void nsPop3Protocol::SetCapFlag(uint32_t flag) {
557   m_pop3ConData->capability_flags |= flag;
558 }
559 
ClearCapFlag(uint32_t flag)560 void nsPop3Protocol::ClearCapFlag(uint32_t flag) {
561   m_pop3ConData->capability_flags &= ~flag;
562 }
563 
TestCapFlag(uint32_t flag)564 bool nsPop3Protocol::TestCapFlag(uint32_t flag) {
565   return m_pop3ConData->capability_flags & flag;
566 }
567 
GetCapFlags()568 uint32_t nsPop3Protocol::GetCapFlags() {
569   return m_pop3ConData->capability_flags;
570 }
571 
FormatCounterString(const nsString & stringName,uint32_t count1,uint32_t count2,nsString & resultString)572 nsresult nsPop3Protocol::FormatCounterString(const nsString& stringName,
573                                              uint32_t count1, uint32_t count2,
574                                              nsString& resultString) {
575   AutoTArray<nsString, 2> formatStrings;
576   formatStrings.AppendElement()->AppendInt(count1);
577   formatStrings.AppendElement()->AppendInt(count2);
578 
579   return mLocalBundle->FormatStringFromName(
580       NS_ConvertUTF16toUTF8(stringName).get(), formatStrings, resultString);
581 }
582 
UpdateStatus(const char * aStatusName)583 void nsPop3Protocol::UpdateStatus(const char* aStatusName) {
584   nsString statusMessage;
585   mLocalBundle->GetStringFromName(aStatusName, statusMessage);
586   UpdateStatusWithString(statusMessage.get());
587 }
588 
UpdateStatusWithString(const char16_t * aStatusString)589 void nsPop3Protocol::UpdateStatusWithString(const char16_t* aStatusString) {
590   if (mProgressEventSink) {
591     mozilla::DebugOnly<nsresult> rv = mProgressEventSink->OnStatus(
592         this, NS_OK, aStatusString);  // XXX i18n message
593     NS_ASSERTION(NS_SUCCEEDED(rv), "dropping error result");
594   }
595 }
596 
UpdateProgressPercent(int64_t totalDone,int64_t total)597 void nsPop3Protocol::UpdateProgressPercent(int64_t totalDone, int64_t total) {
598   if (mProgressEventSink)
599     mProgressEventSink->OnProgress(this, totalDone, total);
600 }
601 
602 // note:  SetUsername() expects an unescaped string
603 // do not pass in an escaped string
SetUsername(const char * name)604 void nsPop3Protocol::SetUsername(const char* name) {
605   NS_ASSERTION(name, "no name specified!");
606   if (name) m_username = name;
607 }
608 
RerunUrl()609 nsresult nsPop3Protocol::RerunUrl() {
610   ClearFlag(POP3_PASSWORD_FAILED);
611   m_pop3Server->SetRunningProtocol(nullptr);
612   Cleanup();
613   return LoadUrl(m_url, nullptr);
614 }
615 
GetNextPasswordObtainState()616 Pop3StatesEnum nsPop3Protocol::GetNextPasswordObtainState() {
617   switch (m_pop3ConData->next_state) {
618     case POP3_OBTAIN_PASSWORD_EARLY:
619       return POP3_FINISH_OBTAIN_PASSWORD_EARLY;
620     case POP3_SEND_USERNAME:
621     case POP3_OBTAIN_PASSWORD_BEFORE_USERNAME:
622       return POP3_FINISH_OBTAIN_PASSWORD_BEFORE_USERNAME;
623     case POP3_SEND_PASSWORD:
624     case POP3_OBTAIN_PASSWORD_BEFORE_PASSWORD:
625       return POP3_FINISH_OBTAIN_PASSWORD_BEFORE_PASSWORD;
626     default:
627       // Should never get here.
628       MOZ_ASSERT_UNREACHABLE(
629           "Invalid next_state in GetNextPasswordObtainState");
630   }
631   return POP3_ERROR_DONE;
632 }
633 
StartGetAsyncPassword(Pop3StatesEnum aNextState)634 nsresult nsPop3Protocol::StartGetAsyncPassword(Pop3StatesEnum aNextState) {
635   nsresult rv;
636 
637   // Try and avoid going async if possible - if we haven't got into a password
638   // failure state and the server has a password stored for this session, then
639   // use it.
640   if (!TestFlag(POP3_PASSWORD_FAILED)) {
641     nsCOMPtr<nsIMsgIncomingServer> server =
642         do_QueryInterface(m_pop3Server, &rv);
643     NS_ENSURE_SUCCESS(rv, rv);
644 
645     rv = server->GetPassword(m_passwordResult);
646     if (NS_SUCCEEDED(rv) && !m_passwordResult.IsEmpty()) {
647       m_pop3ConData->next_state = GetNextPasswordObtainState();
648       return NS_OK;
649     }
650   }
651 
652   // We're now going to need to do something that will end up with us either
653   // poking the login manager or prompting the user. We need to ensure we only
654   // do one prompt at a time (and login manager could cause a master password
655   // prompt), so we need to use the async prompter.
656   nsCOMPtr<nsIMsgAsyncPrompter> asyncPrompter =
657       do_GetService(NS_MSGASYNCPROMPTER_CONTRACTID, &rv);
658   NS_ENSURE_SUCCESS(rv, rv);
659 
660   m_pop3ConData->next_state = aNextState;
661 
662   // Although we're not actually pausing for a read, we'll do so anyway to let
663   // the async prompt run. Once it is our turn again we'll call back into
664   // ProcessProtocolState.
665   m_pop3ConData->pause_for_read = true;
666 
667   nsCString server("unknown");
668   m_url->GetPrePath(server);
669 
670   rv = asyncPrompter->QueueAsyncAuthPrompt(server, false, this);
671   // Explicit NS_ENSURE_SUCCESS for debug purposes as errors tend to get
672   // hidden.
673   NS_ENSURE_SUCCESS(rv, rv);
674   return rv;
675 }
676 
OnPromptStartAsync(nsIMsgAsyncPromptCallback * aCallback)677 NS_IMETHODIMP nsPop3Protocol::OnPromptStartAsync(
678     nsIMsgAsyncPromptCallback* aCallback) {
679   bool result = false;
680   OnPromptStart(&result);
681   return aCallback->OnAuthResult(result);
682 }
683 
OnPromptStart(bool * aResult)684 NS_IMETHODIMP nsPop3Protocol::OnPromptStart(bool* aResult) {
685   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("OnPromptStart()")));
686 
687   *aResult = false;
688 
689   nsresult rv;
690   nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server, &rv);
691   NS_ENSURE_SUCCESS(rv, rv);
692 
693   nsAutoString passwordResult;
694 
695   // pass the failed password into the password prompt so that
696   // it will be pre-filled, in case it failed because of a
697   // server problem and not because it was wrong.
698   if (!m_lastPasswordSent.IsEmpty()) passwordResult = m_lastPasswordSent;
699 
700   // Set up some items that we're going to need for the prompting.
701   nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
702   nsCOMPtr<nsIMsgWindow> msgWindow;
703   if (mailnewsUrl) mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
704 
705   nsCString userName;
706   server->GetRealUsername(userName);
707 
708   nsCString hostName;
709   server->GetRealHostName(hostName);
710 
711   nsString accountName;
712   server->GetPrettyName(accountName);
713 
714   nsString passwordPrompt;
715   AutoTArray<nsString, 2> passwordParams;
716   CopyUTF8toUTF16(userName, *passwordParams.AppendElement());
717   CopyUTF8toUTF16(hostName, *passwordParams.AppendElement());
718 
719   // if the last prompt got us a bad password then show a special dialog
720   if (TestFlag(POP3_PASSWORD_FAILED)) {
721     // Biff case (no msgWindow) shouldn't cause prompts or passwords to get
722     // forgotten at all
723     // TODO shouldn't we skip the new password prompt below as well for biff?
724     // Just exit here?
725     if (msgWindow) {
726       MOZ_LOG(POP3LOGMODULE, LogLevel::Warning,
727               (POP3LOG("POP: ask user what to do (after password failed): new "
728                        "password, retry or cancel")));
729 
730       int32_t buttonPressed = 0;
731       if (NS_SUCCEEDED(MsgPromptLoginFailed(msgWindow, hostName, userName,
732                                             accountName, &buttonPressed))) {
733         if (buttonPressed == 1)  // Cancel button
734         {
735           MOZ_LOG(POP3LOGMODULE, LogLevel::Warning,
736                   (POP3LOG("cancel button pressed")));
737           // Abort quickly and stop trying for now.
738 
739           // If we haven't actually connected yet (i.e. we're doing an early
740           // attempt to get the username/password but we've previously failed
741           // for some reason), then skip straight to POP3_FREE as it isn't an
742           // error in this connection, and just ends up with us closing the
743           // socket and saying we've aborted the bind. Otherwise, pretend this
744           // is an error and move on.
745           m_pop3ConData->next_state =
746               m_pop3ConData->next_state == POP3_OBTAIN_PASSWORD_EARLY
747                   ? POP3_FREE
748                   : POP3_ERROR_DONE;
749 
750           // Clear the password we're going to return to force failure in
751           // the get mail instance.
752           passwordResult.Truncate();
753 
754           // We also have to clear the password failed flag, otherwise we'll
755           // automatically try again.
756           ClearFlag(POP3_PASSWORD_FAILED);
757 
758           // As we're async, calling ProcessProtocolState gets things going
759           // again.
760           ProcessProtocolState(nullptr, nullptr, 0, 0);
761           return NS_OK;
762         } else if (buttonPressed == 2)  // "New password" button
763         {
764           MOZ_LOG(POP3LOGMODULE, LogLevel::Warning,
765                   (POP3LOG("new password button pressed")));
766           // Forget the stored password
767           // and we'll prompt for a new one next time around.
768           rv = server->ForgetPassword();
769           NS_ENSURE_SUCCESS(rv, rv);
770 
771           // try all methods again with new password
772           ResetAuthMethods();
773           // ... apart from GSSAPI, which doesn't care about passwords
774           MarkAuthMethodAsFailed(POP3_HAS_AUTH_GSSAPI);
775           if (m_needToRerunUrl) return RerunUrl();
776         } else if (buttonPressed == 0)  // "Retry" button
777         {
778           MOZ_LOG(POP3LOGMODULE, LogLevel::Warning,
779                   (POP3LOG("retry button pressed")));
780           // try all methods again, including GSSAPI
781           ResetAuthMethods();
782           ClearFlag(POP3_PASSWORD_FAILED | POP3_AUTH_FAILURE);
783 
784           if (m_needToRerunUrl) return RerunUrl();
785 
786           // It is a bit strange that we're going onto the next state that
787           // would essentially send the password. However in resetting the
788           // auth methods above, we're setting up SendUsername, SendPassword
789           // and friends to abort and return to the POP3_SEND_CAPA state.
790           // Hence we can do this safely.
791           m_pop3ConData->next_state = GetNextPasswordObtainState();
792           // As we're async, calling ProcessProtocolState gets things going
793           // again.
794           ProcessProtocolState(nullptr, nullptr, 0, 0);
795           return NS_OK;
796         }
797       }
798     }
799     mLocalBundle->FormatStringFromName(
800         "pop3PreviouslyEnteredPasswordIsInvalidPrompt", passwordParams,
801         passwordPrompt);
802   } else
803     // Otherwise this is the first time we've asked about the server's
804     // password so show a first time prompt.
805     mLocalBundle->FormatStringFromName("pop3EnterPasswordPrompt",
806                                        passwordParams, passwordPrompt);
807 
808   nsString passwordTitle;
809   mLocalBundle->FormatStringFromName("pop3EnterPasswordPromptTitleWithUsername",
810                                      passwordParams, passwordTitle);
811 
812   // Now go and get the password.
813   if (!passwordPrompt.IsEmpty() && !passwordTitle.IsEmpty())
814     rv = server->GetPasswordWithUI(passwordPrompt, passwordTitle, msgWindow,
815                                    passwordResult);
816   ClearFlag(POP3_PASSWORD_FAILED | POP3_AUTH_FAILURE);
817 
818   // If it failed or the user cancelled the prompt, just abort the
819   // connection.
820   if (NS_FAILED(rv) || rv == NS_MSG_PASSWORD_PROMPT_CANCELLED) {
821     m_pop3ConData->next_state = POP3_ERROR_DONE;
822     m_passwordResult.Truncate();
823     *aResult = false;
824   } else {
825     m_passwordResult = passwordResult;
826     m_pop3ConData->next_state = GetNextPasswordObtainState();
827     *aResult = true;
828   }
829   // Because this was done asynchronously, now call back into
830   // ProcessProtocolState to get the protocol going again.
831   ProcessProtocolState(nullptr, nullptr, 0, 0);
832   return NS_OK;
833 }
834 
OnPromptAuthAvailable()835 NS_IMETHODIMP nsPop3Protocol::OnPromptAuthAvailable() {
836   MOZ_ASSERT_UNREACHABLE(
837       "Did not expect to get POP3 protocol queuing up auth "
838       "connections for same server");
839   return NS_OK;
840 }
841 
OnPromptCanceled()842 NS_IMETHODIMP nsPop3Protocol::OnPromptCanceled() {
843   // A prompt was cancelled, so just abort out the connection
844   m_pop3ConData->next_state = POP3_ERROR_DONE;
845   // As we're async, calling ProcessProtocolState gets things going again.
846   ProcessProtocolState(nullptr, nullptr, 0, 0);
847   return NS_OK;
848 }
849 
OnTransportStatus(nsITransport * aTransport,nsresult aStatus,int64_t aProgress,int64_t aProgressMax)850 NS_IMETHODIMP nsPop3Protocol::OnTransportStatus(nsITransport* aTransport,
851                                                 nsresult aStatus,
852                                                 int64_t aProgress,
853                                                 int64_t aProgressMax) {
854   return nsMsgProtocol::OnTransportStatus(aTransport, aStatus, aProgress,
855                                           aProgressMax);
856 }
857 
858 // stop binding is a "notification" informing us that the stream associated with
859 // aURL is going away.
OnStopRequest(nsIRequest * aRequest,nsresult aStatus)860 NS_IMETHODIMP nsPop3Protocol::OnStopRequest(nsIRequest* aRequest,
861                                             nsresult aStatus) {
862   // If the server dropped the connection, m_socketIsOpen will be true, before
863   // we call nsMsgProtocol::OnStopRequest. The call will force a close socket,
864   // but we still want to go through the state machine one more time to cleanup
865   // the protocol object.
866   if (m_socketIsOpen) {
867     nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(m_url);
868 
869     if (NS_FAILED(aStatus)) {
870       // Stash the socket transport securityInfo on the url failedSecInfo
871       // attribute, so it'll be there for nsIUrlListener.OnStopRunningUrl().
872       nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport);
873       if (strans) {
874         nsCOMPtr<nsISupports> secInfo;
875         if (NS_SUCCEEDED(strans->GetSecurityInfo(getter_AddRefs(secInfo)))) {
876           if (nsCOMPtr<nsITransportSecurityInfo> transportSecInfo =
877                   do_QueryInterface(secInfo)) {
878             msgUrl->SetFailedSecInfo(transportSecInfo);
879           }
880         }
881       }
882     }
883 
884     // Check if the connection was dropped before getting back an auth error.
885     // If we got the auth error, the next state would be
886     // POP3_OBTAIN_PASSWORD_EARLY.
887     if ((m_pop3ConData->next_state_after_response == POP3_NEXT_AUTH_STEP ||
888          m_pop3ConData->next_state_after_response ==
889              POP3_AUTH_LOGIN_RESPONSE) &&
890         m_pop3ConData->next_state != POP3_OBTAIN_PASSWORD_EARLY) {
891       MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
892               (POP3LOG("dropped connection before auth error")));
893       SetFlag(POP3_AUTH_FAILURE);
894       m_pop3ConData->command_succeeded = false;
895       m_needToRerunUrl = true;
896       m_pop3ConData->next_state = POP3_NEXT_AUTH_STEP;
897       ProcessProtocolState(nullptr, nullptr, 0, 0);
898     }
899     // We can't call nsMsgProtocol::OnStopRequest because it calls SetUrlState,
900     // which notifies the URLListeners, but we need to do a bit of cleanup
901     // before running the url again.
902     CloseSocket();
903     if (m_loadGroup)
904       m_loadGroup->RemoveRequest(static_cast<nsIRequest*>(this), nullptr,
905                                  aStatus);
906     m_pop3ConData->next_state = POP3_ERROR_DONE;
907     if (NS_FAILED(aStatus)) {
908       m_pop3ConData->urlStatus = aStatus;
909     }
910     ProcessProtocolState(nullptr, nullptr, 0, 0);
911 
912     if (NS_FAILED(aStatus) && aStatus != NS_BINDING_ABORTED)
913       nsMsgProtocol::ShowAlertMessage(msgUrl, aStatus);
914 
915     return NS_OK;
916   }
917   nsresult rv = nsMsgProtocol::OnStopRequest(aRequest, aStatus);
918 
919   // turn off the server busy flag on stop request - we know we're done, right?
920   nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
921   if (server) {
922     MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
923             (POP3LOG("Clearing server busy in nsPop3Protocol::OnStopRequest")));
924     server->SetServerBusy(false);  // the server is not busy
925   }
926   if (m_pop3ConData->list_done) CommitState(true);
927   if (NS_FAILED(aStatus) && aStatus != NS_BINDING_ABORTED) Abort();
928   return rv;
929 }
930 
Abort()931 void nsPop3Protocol::Abort() {
932   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Abort")));
933 
934   if (m_pop3ConData->msg_closure) {
935     m_nsIPop3Sink->IncorporateAbort(m_pop3ConData->only_uidl != nullptr);
936     m_pop3ConData->msg_closure = nullptr;
937   }
938   // Need this to close the stream on the inbox. It's possible that
939   // we abort before the POP3 sink was set.
940   if (m_nsIPop3Sink) m_nsIPop3Sink->AbortMailDelivery(this);
941   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
942           (POP3LOG("Clearing running protocol in nsPop3Protocol::Abort()")));
943   if (m_pop3Server) m_pop3Server->SetRunningProtocol(nullptr);
944 }
945 
Cancel(nsresult status)946 NS_IMETHODIMP nsPop3Protocol::Cancel(nsresult status)  // handle stop button
947 {
948   if (m_proxyRequest) {
949     m_proxyRequest->Cancel(NS_BINDING_ABORTED);
950 
951     // Note that nsMsgProtocol::Cancel() also calls
952     // nsProtocolProxyService::Cancel(), so no need to call it twice
953     // (although it self-protects against multiple calls).
954     m_proxyRequest = nullptr;
955   }
956 
957   Abort();
958   return nsMsgProtocol::Cancel(NS_BINDING_ABORTED);
959 }
960 
LoadUrl(nsIURI * aURL,nsISupports *)961 nsresult nsPop3Protocol::LoadUrl(nsIURI* aURL, nsISupports* /* aConsumer */) {
962   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("LoadUrl()")));
963 
964   nsresult rv = Initialize(aURL);
965   NS_ENSURE_SUCCESS(rv, rv);
966 
967   if (aURL) {
968     rv = MsgExamineForProxyAsync(this, this, getter_AddRefs(m_proxyRequest));
969     if (NS_FAILED(rv)) {
970       rv = InitializeInternal(nullptr);
971       NS_ENSURE_SUCCESS(rv, rv);
972 
973       rv = LoadUrlInternal(m_url);
974     }
975   }
976 
977   return rv;
978 }
979 
LoadUrlInternal(nsIURI * aURL)980 nsresult nsPop3Protocol::LoadUrlInternal(nsIURI* aURL) {
981   nsresult rv;
982 
983   nsCOMPtr<nsIURL> url = do_QueryInterface(aURL, &rv);
984   if (NS_FAILED(rv)) return rv;
985 
986   int32_t port;
987   rv = url->GetPort(&port);
988   NS_ENSURE_SUCCESS(rv, rv);
989 
990   rv = NS_CheckPortSafety(port, "pop");
991   NS_ENSURE_SUCCESS(rv, rv);
992 
993   nsAutoCString queryPart;
994   rv = url->GetQuery(queryPart);
995   NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get the url spect");
996 
997   m_pop3ConData->only_check_for_new_mail =
998       (PL_strcasestr(queryPart.get(), "check") != nullptr);
999   m_pop3ConData->verify_logon =
1000       (PL_strcasestr(queryPart.get(), "verifyLogon") != nullptr);
1001   m_pop3ConData->get_url = (PL_strcasestr(queryPart.get(), "gurl") != nullptr);
1002 
1003   bool deleteByAgeFromServer = false;
1004   int32_t numDaysToLeaveOnServer = -1;
1005   if (!m_pop3ConData->verify_logon) {
1006     // Pick up pref setting regarding leave messages on server, message size
1007     // limit
1008 
1009     m_pop3Server->GetLeaveMessagesOnServer(&m_pop3ConData->leave_on_server);
1010     m_pop3Server->GetHeadersOnly(&m_pop3ConData->headers_only);
1011     bool limitMessageSize = false;
1012 
1013     nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
1014     if (server) {
1015       // size limits are superseded by headers_only mode
1016       if (!m_pop3ConData->headers_only) {
1017         server->GetLimitOfflineMessageSize(&limitMessageSize);
1018         if (limitMessageSize) {
1019           int32_t max_size = 0;  // default size
1020           server->GetMaxMessageSize(&max_size);
1021           m_pop3ConData->size_limit = (max_size) ? max_size * 1024 : 50 * 1024;
1022         }
1023       }
1024       m_pop3Server->GetDeleteByAgeFromServer(&deleteByAgeFromServer);
1025       if (deleteByAgeFromServer)
1026         m_pop3Server->GetNumDaysToLeaveOnServer(&numDaysToLeaveOnServer);
1027     }
1028   }
1029 
1030   // UIDL stuff
1031   nsCOMPtr<nsIPop3URL> pop3Url = do_QueryInterface(m_url);
1032   if (pop3Url) pop3Url->GetPop3Sink(getter_AddRefs(m_nsIPop3Sink));
1033 
1034   nsCOMPtr<nsIFile> mailDirectory;
1035 
1036   nsCString hostName;
1037   nsCString userName;
1038 
1039   nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
1040   if (server) {
1041     rv = server->GetLocalPath(getter_AddRefs(mailDirectory));
1042     NS_ENSURE_SUCCESS(rv, rv);
1043     server->SetServerBusy(true);  // the server is now busy
1044     server->GetHostName(hostName);
1045     server->GetUsername(userName);
1046     MOZ_LOG(POP3LOGMODULE, LogLevel::Info,
1047             (POP3LOG("Connecting to server %s:%d"), hostName.get(), port));
1048 
1049     MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
1050             (POP3LOG("Setting server busy in nsPop3Protocol::LoadUrl()")));
1051   }
1052 
1053   if (!m_pop3ConData->verify_logon)
1054     m_pop3ConData->uidlinfo =
1055         net_pop3_load_state(hostName.get(), userName.get(), mailDirectory);
1056 
1057   m_pop3ConData->biffstate = nsIMsgFolder::nsMsgBiffState_NoMail;
1058 
1059   if (m_pop3ConData->uidlinfo && numDaysToLeaveOnServer > 0) {
1060     uint32_t nowInSeconds = TimeInSecondsFromPRTime(PR_Now());
1061     uint32_t cutOffDay = nowInSeconds - (60 * 60 * 24 * numDaysToLeaveOnServer);
1062 
1063     PL_HashTableEnumerateEntries(m_pop3ConData->uidlinfo->hash,
1064                                  net_pop3_delete_old_msgs_mapper,
1065                                  (void*)(uintptr_t)cutOffDay);
1066   }
1067   const char* uidl = PL_strcasestr(queryPart.get(), "uidl=");
1068   PR_FREEIF(m_pop3ConData->only_uidl);
1069 
1070   if (uidl) {
1071     uidl += 5;
1072     nsCString unescapedData;
1073     MsgUnescapeString(nsDependentCString(uidl), 0, unescapedData);
1074     m_pop3ConData->only_uidl = PL_strdup(unescapedData.get());
1075 
1076     // Suppress on start and on stop because this url won't have any
1077     // content to display.
1078     mSuppressListenerNotifications = true;
1079   }
1080 
1081   m_pop3ConData->next_state = POP3_START_CONNECT;
1082   m_pop3ConData->next_state_after_response = POP3_FINISH_CONNECT;
1083   if (NS_SUCCEEDED(rv)) {
1084     m_pop3Server->SetRunningProtocol(this);
1085     return nsMsgProtocol::LoadUrl(aURL);
1086   } else
1087     return rv;
1088 }
1089 
FreeMsgInfo()1090 void nsPop3Protocol::FreeMsgInfo() {
1091   int i;
1092   if (m_pop3ConData->msg_info) {
1093     for (i = 0; i < m_pop3ConData->number_of_messages; i++) {
1094       if (m_pop3ConData->msg_info[i].uidl)
1095         PR_Free(m_pop3ConData->msg_info[i].uidl);
1096       m_pop3ConData->msg_info[i].uidl = nullptr;
1097     }
1098     PR_Free(m_pop3ConData->msg_info);
1099     m_pop3ConData->msg_info = nullptr;
1100   }
1101 }
1102 
WaitForStartOfConnectionResponse(nsIInputStream * aInputStream,uint32_t length)1103 int32_t nsPop3Protocol::WaitForStartOfConnectionResponse(
1104     nsIInputStream* aInputStream, uint32_t length) {
1105   char* line = nullptr;
1106   uint32_t line_length = 0;
1107   bool pauseForMoreData = false;
1108   nsresult rv;
1109   line = m_lineStreamBuffer->ReadNextLine(aInputStream, line_length,
1110                                           pauseForMoreData, &rv);
1111 
1112   MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
1113   if (NS_FAILED(rv)) return -1;
1114 
1115   if (pauseForMoreData || !line) {
1116     m_pop3ConData->pause_for_read = true; /* pause */
1117     PR_Free(line);
1118     return (line_length);
1119   }
1120 
1121   if (*line == '+') {
1122     m_pop3ConData->command_succeeded = true;
1123     if (PL_strlen(line) > 4)
1124       m_commandResponse = line + 4;
1125     else
1126       m_commandResponse = line;
1127 
1128     if (m_prefAuthMethods & POP3_HAS_AUTH_APOP) {
1129       if (NS_SUCCEEDED(GetApopTimestamp())) SetCapFlag(POP3_HAS_AUTH_APOP);
1130     } else
1131       ClearCapFlag(POP3_HAS_AUTH_APOP);
1132 
1133     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
1134 
1135     m_pop3ConData->next_state = POP3_PROCESS_AUTH;
1136     m_pop3ConData->pause_for_read = false; /* don't pause */
1137   }
1138 
1139   PR_Free(line);
1140   return (1); /* everything ok */
1141 }
1142 
WaitForResponse(nsIInputStream * inputStream,uint32_t length)1143 int32_t nsPop3Protocol::WaitForResponse(nsIInputStream* inputStream,
1144                                         uint32_t length) {
1145   char* line;
1146   uint32_t ln = 0;
1147   bool pauseForMoreData = false;
1148   nsresult rv;
1149   line =
1150       m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv);
1151   if (NS_FAILED(rv)) return -1;
1152 
1153   if (pauseForMoreData || !line) {
1154     m_pop3ConData->pause_for_read = true; /* pause */
1155 
1156     PR_Free(line);
1157     return (ln);
1158   }
1159 
1160   MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
1161   m_pop3ConData->command_temp_fail = false;
1162 
1163   if (*line == '+') {
1164     m_pop3ConData->command_succeeded = true;
1165     if (PL_strlen(line) > 4) {
1166       if (!PL_strncasecmp(line, "+OK", 3))
1167         m_commandResponse = line + 4;
1168       else  // challenge answer to AUTH CRAM-MD5 and LOGIN username/password
1169         m_commandResponse = line + 2;
1170     } else
1171       m_commandResponse = line;
1172   } else {
1173     m_pop3ConData->command_succeeded = false;
1174     if (PL_strlen(line) > 5)
1175       m_commandResponse = line + 5;
1176     else
1177       m_commandResponse = line;
1178 
1179     // search for the response codes (RFC 2449, chapter 8 and RFC 3206)
1180     if (TestCapFlag(POP3_HAS_RESP_CODES | POP3_HAS_AUTH_RESP_CODE)) {
1181       // code for authentication failure due to the user's credentials
1182       if (m_commandResponse.Find("[AUTH", true) >= 0) {
1183         MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
1184                 (POP3LOG("setting auth failure")));
1185         SetFlag(POP3_AUTH_FAILURE);
1186       }
1187 
1188       // codes for failures due to other reasons
1189       if (m_commandResponse.Find("[LOGIN-DELAY", true) >= 0 ||
1190           m_commandResponse.Find("[IN-USE", true) >= 0 ||
1191           m_commandResponse.Find("[SYS", true) >= 0)
1192         SetFlag(POP3_STOPLOGIN);
1193 
1194       if (m_commandResponse.Find("[SYS/TEMP", true) >= 0) {
1195         m_pop3ConData->command_temp_fail = true;
1196       }
1197 
1198       // remove the codes from the response string presented to the user
1199       int32_t i = m_commandResponse.FindChar(']');
1200       if (i >= 0) m_commandResponse.Cut(0, i + 2);
1201     }
1202   }
1203 
1204   m_pop3ConData->next_state = m_pop3ConData->next_state_after_response;
1205   m_pop3ConData->pause_for_read = false; /* don't pause */
1206 
1207   PR_Free(line);
1208   return (1); /* everything ok */
1209 }
1210 
Error(const char * err_code,const char16_t * param)1211 int32_t nsPop3Protocol::Error(const char* err_code, const char16_t* param) {
1212   MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("ERROR: %s"), err_code));
1213   nsresult rv = NS_OK;
1214 
1215   // the error code is just the resource name for the error string...
1216   // so print out that error message!
1217   nsAutoString message;
1218   // Format the alert string if parameter list isn't empty
1219   if (param) {
1220     AutoTArray<nsString, 1> params = {nsDependentString(param)};
1221     mLocalBundle->FormatStringFromName(err_code, params, message);
1222   } else {
1223     mLocalBundle->GetStringFromName(err_code, message);
1224   }
1225   if (!m_pop3ConData->command_succeeded) {
1226     // Server error message
1227     nsString serverSaidPrefix;
1228     nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
1229     nsCString hostName;
1230     // Format string with hostname.
1231     if (server) {
1232       rv = server->GetRealHostName(hostName);
1233     }
1234     if (NS_SUCCEEDED(rv)) {
1235       AutoTArray<nsString, 1> params;
1236       CopyASCIItoUTF16(hostName, *params.AppendElement());
1237       mLocalBundle->FormatStringFromName("pop3ServerSaid", params,
1238                                          serverSaidPrefix);
1239     }
1240 
1241     message.Append(' ');
1242     message.Append(serverSaidPrefix);
1243     message.Append(' ');
1244     message.Append(NS_ConvertASCIItoUTF16(m_commandResponse));
1245   }
1246 
1247   nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
1248   mailnewsUrl->SetErrorCode(nsDependentCString(err_code));
1249   mailnewsUrl->SetErrorMessage(message);
1250 
1251   nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
1252   nsString accountName;
1253   rv = server->GetPrettyName(accountName);
1254   NS_ENSURE_SUCCESS(rv, -1);
1255   AutoTArray<nsString, 1> titleParams = {accountName};
1256   nsString dialogTitle;
1257   mLocalBundle->FormatStringFromName("pop3ErrorDialogTitle", titleParams,
1258                                      dialogTitle);
1259   // we handle "pop3TmpDownloadError" earlier...
1260   if (strcmp(err_code, "pop3TmpDownloadError") && NS_SUCCEEDED(rv)) {
1261     nsCOMPtr<nsIMsgWindow> msgWindow;
1262     nsCOMPtr<nsIPrompt> dialog;
1263     rv = mailnewsUrl->GetMsgWindow(
1264         getter_AddRefs(msgWindow));  // it is ok to have null msgWindow, for
1265                                      // example when biffing
1266     if (NS_SUCCEEDED(rv) && msgWindow) {
1267       rv = msgWindow->GetPromptDialog(getter_AddRefs(dialog));
1268       if (NS_SUCCEEDED(rv)) {
1269         dialog->Alert(dialogTitle.get(), message.get());
1270       }
1271     }
1272   }
1273   m_pop3ConData->next_state = POP3_ERROR_DONE;
1274   m_pop3ConData->pause_for_read = false;
1275   return -1;
1276 }
1277 
Pop3SendData(const char * dataBuffer,bool aSuppressLogging)1278 int32_t nsPop3Protocol::Pop3SendData(const char* dataBuffer,
1279                                      bool aSuppressLogging) {
1280   // remove any leftover bytes in the line buffer
1281   // this can happen if the last message line doesn't end with a (CR)LF
1282   // or a server sent two reply lines
1283   m_lineStreamBuffer->ClearBuffer();
1284 
1285   nsresult result = nsMsgProtocol::SendData(dataBuffer);
1286 
1287   if (!aSuppressLogging)
1288     MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("SEND: %s"), dataBuffer));
1289   else
1290     MOZ_LOG(POP3LOGMODULE, LogLevel::Info,
1291             (POP3LOG("Logging suppressed for this command (it probably "
1292                      "contained authentication information)")));
1293 
1294   if (NS_SUCCEEDED(result)) {
1295     m_pop3ConData->pause_for_read = true;
1296     m_pop3ConData->next_state = POP3_WAIT_FOR_RESPONSE;
1297     return 0;
1298   }
1299 
1300   m_pop3ConData->next_state = POP3_ERROR_DONE;
1301   MOZ_LOG(POP3LOGMODULE, LogLevel::Info,
1302           (POP3LOG("Pop3SendData failed: %" PRIx32),
1303            static_cast<uint32_t>(result)));
1304   return -1;
1305 }
1306 
1307 /**
1308  * POP3 AUTH extension
1309  */
SendAuth()1310 int32_t nsPop3Protocol::SendAuth() {
1311   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendAuth()")));
1312 
1313   if (!m_pop3ConData->command_succeeded) return Error("pop3ServerError");
1314 
1315   nsAutoCString command("AUTH" CRLF);
1316 
1317   m_pop3ConData->next_state_after_response = POP3_AUTH_RESPONSE;
1318   return Pop3SendData(command.get());
1319 }
1320 
AuthResponse(nsIInputStream * inputStream,uint32_t length)1321 int32_t nsPop3Protocol::AuthResponse(nsIInputStream* inputStream,
1322                                      uint32_t length) {
1323   char* line;
1324   uint32_t ln = 0;
1325   nsresult rv;
1326 
1327   if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED)) {
1328     ClearCapFlag(POP3_AUTH_MECH_UNDEFINED);
1329     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
1330   }
1331 
1332   if (!m_pop3ConData->command_succeeded) {
1333     /* AUTH command not implemented
1334      * so no secure mechanisms available
1335      */
1336     m_pop3ConData->command_succeeded = true;
1337     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
1338     m_pop3ConData->next_state = POP3_SEND_CAPA;
1339     return 0;
1340   }
1341 
1342   bool pauseForMoreData = false;
1343   line =
1344       m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv);
1345   if (NS_FAILED(rv)) return -1;
1346 
1347   if (pauseForMoreData || !line) {
1348     m_pop3ConData->pause_for_read = true; /* pause */
1349     PR_Free(line);
1350     return (0);
1351   }
1352 
1353   MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
1354 
1355   if (!PL_strcmp(line, ".")) {
1356     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
1357 
1358     // now that we've read all the AUTH responses, go for it
1359     m_pop3ConData->next_state = POP3_SEND_CAPA;
1360     m_pop3ConData->pause_for_read = false; /* don't pause */
1361   } else if (!PL_strcasecmp(line, "CRAM-MD5"))
1362     SetCapFlag(POP3_HAS_AUTH_CRAM_MD5);
1363   else if (!PL_strcasecmp(line, "NTLM"))
1364     SetCapFlag(POP3_HAS_AUTH_NTLM);
1365   else if (!PL_strcasecmp(line, "MSN"))
1366     SetCapFlag(POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN);
1367   else if (!PL_strcasecmp(line, "GSSAPI"))
1368     SetCapFlag(POP3_HAS_AUTH_GSSAPI);
1369   else if (!PL_strcasecmp(line, "PLAIN"))
1370     SetCapFlag(POP3_HAS_AUTH_PLAIN);
1371   else if (!PL_strcasecmp(line, "LOGIN"))
1372     SetCapFlag(POP3_HAS_AUTH_LOGIN);
1373   else if (!PL_strcasecmp(line, "XOAUTH2"))
1374     SetCapFlag(POP3_HAS_AUTH_XOAUTH2);
1375 
1376   PR_Free(line);
1377   return 0;
1378 }
1379 
1380 /*
1381  * POP3 CAPA extension, see RFC 2449, chapter 5
1382  */
1383 
SendCapa()1384 int32_t nsPop3Protocol::SendCapa() {
1385   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendCapa()")));
1386   if (!m_pop3ConData->command_succeeded) return Error("pop3ServerError");
1387 
1388   nsAutoCString command("CAPA" CRLF);
1389 
1390   m_pop3ConData->next_state_after_response = POP3_CAPA_RESPONSE;
1391   return Pop3SendData(command.get());
1392 }
1393 
CapaResponse(nsIInputStream * inputStream,uint32_t length)1394 int32_t nsPop3Protocol::CapaResponse(nsIInputStream* inputStream,
1395                                      uint32_t length) {
1396   char* line;
1397   uint32_t ln = 0;
1398 
1399   if (!m_pop3ConData->command_succeeded) {
1400     /* CAPA command not implemented */
1401     m_pop3ConData->command_succeeded = true;
1402     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
1403     m_pop3ConData->next_state = POP3_PROCESS_AUTH;
1404     return 0;
1405   }
1406 
1407   bool pauseForMoreData = false;
1408   nsresult rv;
1409   line =
1410       m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv);
1411   if (NS_FAILED(rv)) return -1;
1412 
1413   if (pauseForMoreData || !line) {
1414     m_pop3ConData->pause_for_read = true; /* pause */
1415     PR_Free(line);
1416     return (0);
1417   }
1418 
1419   MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
1420 
1421   if (!PL_strcmp(line, ".")) {
1422     // now that we've read all the CAPA responses, go for it
1423     m_pop3ConData->next_state = POP3_PROCESS_AUTH;
1424     m_pop3ConData->pause_for_read = false; /* don't pause */
1425   } else if (!PL_strcasecmp(line, "XSENDER")) {
1426     SetCapFlag(POP3_HAS_XSENDER);
1427     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
1428   } else if (!PL_strcasecmp(line, "RESP-CODES") ||
1429              !PL_strcasecmp(line, "RESP_CODES")) {
1430     // see RFC 2449, chapter 6.4 -- some non-compliant servers use underscore
1431     // instead of hyphen so accept underscore too.
1432     SetCapFlag(POP3_HAS_RESP_CODES);
1433     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
1434   } else if (!PL_strcasecmp(line, "AUTH-RESP-CODE") ||
1435              !PL_strcasecmp(line, "AUTH_RESP_CODE")) {
1436     // see RFC 3206, chapter 6 -- some non-compliant servers use underscore
1437     // instead of hyphen so accept underscore too.
1438     SetCapFlag(POP3_HAS_AUTH_RESP_CODE);
1439     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
1440   } else if (!PL_strcasecmp(line, "STLS")) {
1441     // see RFC 2595, chapter 4
1442     SetCapFlag(POP3_HAS_STLS);
1443     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
1444   } else if (!PL_strncasecmp(line, "SASL", 4) && strlen(line) > 6) {
1445     // see RFC 2449, chapter 6.3
1446     nsAutoCString responseLine;
1447     responseLine.Assign(line + 5);
1448 
1449     if (responseLine.Find("PLAIN", /* ignoreCase = */ true) >= 0)
1450       SetCapFlag(POP3_HAS_AUTH_PLAIN);
1451 
1452     if (responseLine.Find("LOGIN", /* ignoreCase = */ true) >= 0)
1453       SetCapFlag(POP3_HAS_AUTH_LOGIN);
1454 
1455     if (responseLine.Find("GSSAPI", /* ignoreCase = */ true) >= 0)
1456       SetCapFlag(POP3_HAS_AUTH_GSSAPI);
1457 
1458     if (responseLine.Find("CRAM-MD5", /* ignoreCase = */ true) >= 0)
1459       SetCapFlag(POP3_HAS_AUTH_CRAM_MD5);
1460 
1461     if (responseLine.Find("NTLM", /* ignoreCase = */ true) >= 0)
1462       SetCapFlag(POP3_HAS_AUTH_NTLM);
1463 
1464     if (responseLine.Find("MSN", /* ignoreCase = */ true) >= 0)
1465       SetCapFlag(POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN);
1466 
1467     if (responseLine.Find("XOAUTH2", /* ignoreCase = */ true) >= 0)
1468       SetCapFlag(POP3_HAS_AUTH_XOAUTH2);
1469 
1470     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
1471   }
1472 
1473   PR_Free(line);
1474   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
1475           (POP3LOG("Capability entry processed")));
1476   return 0;
1477 }
1478 
SendTLSResponse()1479 int32_t nsPop3Protocol::SendTLSResponse() {
1480   // only tear down our existing connection and open a new one if we received
1481   // a +OK response from the pop server after we issued the STLS command
1482   nsresult rv = NS_OK;
1483   if (m_pop3ConData->command_succeeded) {
1484     nsCOMPtr<nsISupports> secInfo;
1485     nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport, &rv);
1486     if (NS_FAILED(rv)) return -1;
1487 
1488     rv = strans->GetSecurityInfo(getter_AddRefs(secInfo));
1489 
1490     if (NS_SUCCEEDED(rv) && secInfo) {
1491       nsCOMPtr<nsISSLSocketControl> sslControl =
1492           do_QueryInterface(secInfo, &rv);
1493 
1494       if (NS_SUCCEEDED(rv) && sslControl) rv = sslControl->StartTLS();
1495     }
1496 
1497     if (NS_SUCCEEDED(rv)) {
1498       m_pop3ConData->next_state = POP3_SEND_AUTH;
1499       m_tlsEnabled = true;
1500 
1501       // certain capabilities like POP3_HAS_AUTH_APOP should be
1502       // preserved across the connections.
1503       uint32_t preservedCapFlags =
1504           m_pop3ConData->capability_flags & POP3_HAS_AUTH_APOP;
1505       m_pop3ConData->capability_flags =  // resetting the flags
1506           POP3_AUTH_MECH_UNDEFINED |
1507           POP3_HAS_AUTH_USER |  // should be always there
1508           POP3_GURL_UNDEFINED | POP3_UIDL_UNDEFINED | POP3_TOP_UNDEFINED |
1509           POP3_XTND_XLST_UNDEFINED | preservedCapFlags;
1510       m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
1511       return 0;
1512     }
1513   }
1514 
1515   ClearFlag(POP3_HAS_STLS);
1516   m_pop3ConData->next_state = POP3_PROCESS_AUTH;
1517 
1518   return (NS_SUCCEEDED(rv) ? 0 : -1);
1519 }
1520 
InitPrefAuthMethods(int32_t authMethodPrefValue)1521 void nsPop3Protocol::InitPrefAuthMethods(int32_t authMethodPrefValue) {
1522   // for m_prefAuthMethods, using the same flags as server capabilities.
1523   switch (authMethodPrefValue) {
1524     case nsMsgAuthMethod::none:
1525       m_prefAuthMethods = POP3_HAS_AUTH_NONE;
1526       break;
1527     case nsMsgAuthMethod::old:
1528       m_prefAuthMethods = POP3_HAS_AUTH_USER;
1529       break;
1530     case nsMsgAuthMethod::passwordCleartext:
1531       m_prefAuthMethods =
1532           POP3_HAS_AUTH_USER | POP3_HAS_AUTH_LOGIN | POP3_HAS_AUTH_PLAIN;
1533       break;
1534     case nsMsgAuthMethod::passwordEncrypted:
1535       m_prefAuthMethods = POP3_HAS_AUTH_CRAM_MD5 | POP3_HAS_AUTH_APOP;
1536       break;
1537     case nsMsgAuthMethod::NTLM:
1538       m_prefAuthMethods = POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN;
1539       break;
1540     case nsMsgAuthMethod::GSSAPI:
1541       m_prefAuthMethods = POP3_HAS_AUTH_GSSAPI;
1542       break;
1543     case nsMsgAuthMethod::OAuth2:
1544       m_prefAuthMethods = POP3_HAS_AUTH_XOAUTH2;
1545       break;
1546     case nsMsgAuthMethod::secure:
1547       m_prefAuthMethods = POP3_HAS_AUTH_APOP | POP3_HAS_AUTH_CRAM_MD5 |
1548                           POP3_HAS_AUTH_GSSAPI | POP3_HAS_AUTH_NTLM |
1549                           POP3_HAS_AUTH_MSN;
1550       break;
1551     default:
1552       NS_ASSERTION(false, "POP: authMethod pref invalid");
1553       // TODO log to error console
1554       MOZ_LOG(
1555           POP3LOGMODULE, LogLevel::Error,
1556           (POP3LOG("POP: bad pref authMethod = %d\n"), authMethodPrefValue));
1557       // fall to any
1558       [[fallthrough]];
1559     case nsMsgAuthMethod::anything:
1560       m_prefAuthMethods =
1561           POP3_HAS_AUTH_USER | POP3_HAS_AUTH_LOGIN | POP3_HAS_AUTH_PLAIN |
1562           POP3_HAS_AUTH_CRAM_MD5 | POP3_HAS_AUTH_APOP | POP3_HAS_AUTH_GSSAPI |
1563           POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN | POP3_HAS_AUTH_XOAUTH2;
1564       // TODO needed?
1565       break;
1566   }
1567 
1568   // Only enable OAuth2 support if we can do the lookup.
1569   if ((m_prefAuthMethods & POP3_HAS_AUTH_XOAUTH2) && !mOAuth2Support) {
1570     m_prefAuthMethods &= ~POP3_HAS_AUTH_XOAUTH2;  // disable it
1571   }
1572 
1573   NS_ASSERTION(m_prefAuthMethods != POP3_AUTH_MECH_UNDEFINED,
1574                "POP: InitPrefAuthMethods() didn't work");
1575 }
1576 
1577 /**
1578  * Changes m_currentAuthMethod to pick the best one
1579  * which is allowed by server and prefs and not marked failed.
1580  * The order of preference and trying of auth methods is encoded here.
1581  */
ChooseAuthMethod()1582 nsresult nsPop3Protocol::ChooseAuthMethod() {
1583   int32_t availCaps = GetCapFlags() & m_prefAuthMethods & ~m_failedAuthMethods;
1584 
1585   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
1586           (POP3LOG("POP auth: server caps 0x%X, pref 0x%X, failed 0x%X, avail "
1587                    "caps 0x%X"),
1588            GetCapFlags(), m_prefAuthMethods, m_failedAuthMethods, availCaps));
1589   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
1590           (POP3LOG("(GSSAPI = 0x%X, CRAM = 0x%X, APOP = 0x%X, NTLM = 0x%X, "
1591                    "MSN = 0x%X, PLAIN = 0x%X, LOGIN = 0x%X, USER/PASS = 0x%X, "
1592                    "XOAUTH2 = 0x%X)"),
1593            POP3_HAS_AUTH_GSSAPI, POP3_HAS_AUTH_CRAM_MD5, POP3_HAS_AUTH_APOP,
1594            POP3_HAS_AUTH_NTLM, POP3_HAS_AUTH_MSN, POP3_HAS_AUTH_PLAIN,
1595            POP3_HAS_AUTH_LOGIN, POP3_HAS_AUTH_USER, POP3_HAS_AUTH_XOAUTH2));
1596 
1597   MOZ_LOG(
1598       POP3LOGMODULE, LogLevel::Debug,
1599       (POP3LOG("(Enabled - GSSAPI=%d, CRAM=%d, APOP=%d, NTLM=%d, "
1600                "MSN=%d, PLAIN=%d, LOGIN=%d, USER/PASS=%d, "
1601                "XOAUTH2=%d)"),
1602        !!(POP3_HAS_AUTH_GSSAPI & availCaps),
1603        !!(POP3_HAS_AUTH_CRAM_MD5 & availCaps),
1604        !!(POP3_HAS_AUTH_APOP & availCaps), !!(POP3_HAS_AUTH_NTLM & availCaps),
1605        !!(POP3_HAS_AUTH_MSN & availCaps), !!(POP3_HAS_AUTH_PLAIN & availCaps),
1606        !!(POP3_HAS_AUTH_LOGIN & availCaps), !!(POP3_HAS_AUTH_USER & availCaps),
1607        !!(POP3_HAS_AUTH_XOAUTH2 & availCaps)));
1608 
1609   if (POP3_HAS_AUTH_GSSAPI & availCaps)
1610     m_currentAuthMethod = POP3_HAS_AUTH_GSSAPI;
1611   else if (POP3_HAS_AUTH_CRAM_MD5 & availCaps)
1612     m_currentAuthMethod = POP3_HAS_AUTH_CRAM_MD5;
1613   else if (POP3_HAS_AUTH_APOP & availCaps)
1614     m_currentAuthMethod = POP3_HAS_AUTH_APOP;
1615   else if (POP3_HAS_AUTH_NTLM & availCaps)
1616     m_currentAuthMethod = POP3_HAS_AUTH_NTLM;
1617   else if (POP3_HAS_AUTH_MSN & availCaps)
1618     m_currentAuthMethod = POP3_HAS_AUTH_MSN;
1619   else if (POP3_HAS_AUTH_PLAIN & availCaps)
1620     m_currentAuthMethod = POP3_HAS_AUTH_PLAIN;
1621   else if (POP3_HAS_AUTH_LOGIN & availCaps)
1622     m_currentAuthMethod = POP3_HAS_AUTH_LOGIN;
1623   else if (POP3_HAS_AUTH_USER & availCaps)
1624     m_currentAuthMethod = POP3_HAS_AUTH_USER;
1625   else if (POP3_HAS_AUTH_XOAUTH2 & availCaps)
1626     m_currentAuthMethod = POP3_HAS_AUTH_XOAUTH2;
1627 
1628   else {
1629     // there are no matching login schemes at all, per server and prefs
1630     m_currentAuthMethod = POP3_AUTH_MECH_UNDEFINED;
1631     MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
1632             (POP3LOG("No auth method remaining")));
1633     return NS_ERROR_FAILURE;
1634   }
1635   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
1636           (POP3LOG("Trying auth method 0x%X"), m_currentAuthMethod));
1637   return NS_OK;
1638 }
1639 
MarkAuthMethodAsFailed(int32_t failedAuthMethod)1640 void nsPop3Protocol::MarkAuthMethodAsFailed(int32_t failedAuthMethod) {
1641   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
1642           (POP3LOG("Marking auth method 0x%X failed"), failedAuthMethod));
1643   m_failedAuthMethods |= failedAuthMethod;
1644 }
1645 
1646 /**
1647  * Start over, trying all auth methods again
1648  */
ResetAuthMethods()1649 void nsPop3Protocol::ResetAuthMethods() {
1650   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
1651           (POP3LOG("Resetting (failed) auth methods")));
1652   m_currentAuthMethod = POP3_AUTH_MECH_UNDEFINED;
1653   m_failedAuthMethods = 0;
1654 }
1655 
1656 /**
1657  * state POP3_PROCESS_AUTH
1658  * Called when we should try to authenticate to the server.
1659  * Also called when one auth method fails and we want to try and start
1660  * the next best auth method.
1661  */
ProcessAuth()1662 int32_t nsPop3Protocol::ProcessAuth() {
1663   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("ProcessAuth()")));
1664 
1665   // Try to upgrade to STARTTLS -- TODO move into its own function
1666   if (!m_tlsEnabled) {
1667     if (TestCapFlag(POP3_HAS_STLS)) {
1668       if (m_socketType == nsMsgSocketType::trySTARTTLS ||
1669           m_socketType == nsMsgSocketType::alwaysSTARTTLS) {
1670         nsAutoCString command("STLS" CRLF);
1671 
1672         m_pop3ConData->next_state_after_response = POP3_TLS_RESPONSE;
1673         return Pop3SendData(command.get());
1674       }
1675     } else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS) {
1676       m_pop3ConData->next_state = POP3_ERROR_DONE;
1677       return Error("nsErrorCouldNotConnectViaTls");
1678     }
1679   }
1680 
1681   m_password_already_sent = false;
1682 
1683   nsresult rv = ChooseAuthMethod();
1684   if (NS_FAILED(rv)) {
1685     // Pref doesn't match server. Now, find an appropriate error msg.
1686     MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
1687             (POP3LOG("ProcessAuth() early exit because no auth methods")));
1688 
1689     // AuthGSSAPI* falls in here in case of an auth failure.
1690     // If Kerberos was the only method, assume that
1691     // the user is just not logged in yet, and show an appropriate error.
1692     if (m_prefAuthMethods == POP3_HAS_AUTH_GSSAPI &&
1693         m_failedAuthMethods == POP3_HAS_AUTH_GSSAPI)
1694       return Error("pop3GssapiFailure");
1695 
1696     // pref has plaintext pw & server claims to support encrypted pw
1697     if (m_prefAuthMethods ==
1698             (POP3_HAS_AUTH_USER | POP3_HAS_AUTH_LOGIN | POP3_HAS_AUTH_PLAIN) &&
1699         GetCapFlags() & (POP3_HAS_AUTH_CRAM_MD5 | POP3_HAS_AUTH_APOP))
1700       // tell user to change to encrypted pw
1701       return Error("pop3AuthChangePlainToEncrypt");
1702     // pref has encrypted pw & server claims to support plaintext pw
1703     else if (m_prefAuthMethods ==
1704                  (POP3_HAS_AUTH_CRAM_MD5 | POP3_HAS_AUTH_APOP) &&
1705              GetCapFlags() & (POP3_HAS_AUTH_USER | POP3_HAS_AUTH_LOGIN |
1706                               POP3_HAS_AUTH_PLAIN)) {
1707       // have SSL
1708       if (m_socketType == nsMsgSocketType::SSL ||
1709           m_socketType == nsMsgSocketType::alwaysSTARTTLS)
1710         // tell user to change to plaintext pw
1711         return Error("pop3AuthChangeEncryptToPlainSSL");
1712       else
1713         // tell user to change to plaintext pw, with big warning
1714         return Error("pop3AuthChangeEncryptToPlainNoSSL");
1715     } else
1716       // just "change auth method"
1717       return Error("pop3AuthMechNotSupported");
1718   }
1719 
1720   switch (m_currentAuthMethod) {
1721     case POP3_HAS_AUTH_GSSAPI:
1722       MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP GSSAPI")));
1723       m_pop3ConData->next_state = POP3_AUTH_GSSAPI;
1724       break;
1725     case POP3_HAS_AUTH_APOP:
1726       MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP APOP")));
1727       m_pop3ConData->next_state = POP3_SEND_PASSWORD;
1728       break;
1729     case POP3_HAS_AUTH_CRAM_MD5:
1730       MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP CRAM")));
1731       [[fallthrough]];
1732     case POP3_HAS_AUTH_PLAIN:
1733     case POP3_HAS_AUTH_USER:
1734       MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP username")));
1735       m_pop3ConData->next_state = POP3_SEND_USERNAME;
1736       break;
1737     case POP3_HAS_AUTH_LOGIN:
1738       MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP AUTH=LOGIN")));
1739       m_pop3ConData->next_state = POP3_AUTH_LOGIN;
1740       break;
1741     case POP3_HAS_AUTH_NTLM:
1742       MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP NTLM")));
1743       m_pop3ConData->next_state = POP3_AUTH_NTLM;
1744       break;
1745     case POP3_HAS_AUTH_NONE:
1746       MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP no auth")));
1747       m_pop3ConData->command_succeeded = true;
1748       m_pop3ConData->next_state = POP3_NEXT_AUTH_STEP;
1749       break;
1750     case POP3_HAS_AUTH_XOAUTH2:
1751       MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP XOAUTH2")));
1752       m_pop3ConData->command_succeeded = true;
1753       m_pop3ConData->next_state = POP3_AUTH_OAUTH2_RESPONSE;
1754       break;
1755     default:
1756       MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
1757               (POP3LOG("POP: m_currentAuthMethod has unknown value")));
1758       return Error("pop3AuthMechNotSupported");
1759   }
1760 
1761   m_pop3ConData->pause_for_read = false;
1762   return 0;
1763 }
1764 
AuthOAuth2Response()1765 int32_t nsPop3Protocol::AuthOAuth2Response() {
1766   if (!mOAuth2Support) {
1767     return Error("pop3AuthMechNotSupported");
1768   }
1769   nsresult rv = mOAuth2Support->Connect(true, this);
1770   if (NS_FAILED(rv)) {
1771     MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
1772             (POP3LOG("OAuth2 authorizattion failed")));
1773     return -1;
1774   }
1775   m_pop3ConData->pause_for_read = true;
1776   return 0;
1777 }
1778 
OAuth2AuthStep()1779 int32_t nsPop3Protocol::OAuth2AuthStep() {
1780   MOZ_ASSERT(mOAuth2Support, "Can't do anything without OAuth2 support");
1781 
1782   if (!m_pop3ConData->command_succeeded) {
1783     m_OAuth2String.Truncate();
1784     m_pop3ConData->next_state = POP3_ERROR_DONE;
1785     return -1;
1786   }
1787 
1788   nsAutoCString cmdLine2;
1789   cmdLine2 += m_OAuth2String;
1790   cmdLine2 += CRLF;
1791 
1792   m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP;
1793   m_password_already_sent = true;
1794   m_OAuth2String.Truncate();
1795   if (Pop3SendData(cmdLine2.get(), true)) {
1796     MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
1797             (POP3LOG("POP: XOAUTH2 authentication (second step) failed")));
1798     m_pop3ConData->next_state = POP3_ERROR_DONE;
1799   }
1800   return 0;
1801 }
1802 
1803 /** msgIOAuth2ModuleListener implementation */
OnSuccess(const nsACString & aOAuth2String)1804 nsresult nsPop3Protocol::OnSuccess(const nsACString& aOAuth2String) {
1805   MOZ_ASSERT(mOAuth2Support, "Can't do anything without OAuth2 support");
1806   // Send the AUTH XOAUTH2 command, and then siphon us back to the regular
1807   // authentication login stream.
1808   nsAutoCString cmd;
1809   cmd.AppendLiteral("AUTH XOAUTH2 ");
1810   cmd += aOAuth2String;
1811   cmd += CRLF;
1812   // RFC 2449 limits command length to 255 octets. If our command is over that,
1813   // send AUTH XOAUTH2 on one line, and the actual data on the next.
1814   // The second line is treated as a blob allowing arbitrary size content.
1815   if (cmd.Length() <= 255) {
1816     m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP;
1817     m_password_already_sent = true;
1818     if (Pop3SendData(cmd.get(), true)) {
1819       MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
1820               (POP3LOG("POP: XOAUTH2 authentication failed")));
1821       m_pop3ConData->next_state = POP3_ERROR_DONE;
1822     }
1823   } else {
1824     // Stash the string away, in POP3_AUTH_OAUTH2_AUTH_STEP we'll send this too.
1825     m_OAuth2String.Assign(aOAuth2String);
1826 
1827     nsAutoCString cmdLine1;
1828     cmdLine1.AppendLiteral("AUTH XOAUTH2");
1829     cmdLine1 += CRLF;
1830     m_pop3ConData->next_state = POP3_WAIT_FOR_RESPONSE;
1831     m_pop3ConData->pause_for_read = true;
1832     m_pop3ConData->next_state_after_response = POP3_AUTH_OAUTH2_AUTH_STEP;
1833     if (Pop3SendData(cmdLine1.get(), false)) {
1834       MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
1835               (POP3LOG("POP: XOAUTH2 authentication (line1) failed")));
1836       m_pop3ConData->next_state = POP3_ERROR_DONE;
1837       ProcessProtocolState(nullptr, nullptr, 0, 0);
1838       return NS_ERROR_FAILURE;
1839     }
1840   }
1841   ProcessProtocolState(nullptr, nullptr, 0, 0);
1842   return NS_OK;
1843 }
1844 
1845 /** msgIOAuth2ModuleListener implementation */
OnFailure(nsresult aError)1846 nsresult nsPop3Protocol::OnFailure(nsresult aError) {
1847   MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
1848           ("OAuth2 login error %08x", (uint32_t)aError));
1849   m_pop3ConData->next_state = POP3_ERROR_DONE;
1850   return ProcessProtocolState(nullptr, nullptr, 0, 0);
1851 }
1852 
1853 /**
1854  * state POP3_NEXT_AUTH_STEP
1855  * This is called when we finished one auth step (e.g. sending username
1856  * or password are separate steps, similarly for AUTH LOGIN, NTLM etc.)
1857  * and want to proceed to the next one.
1858  */
NextAuthStep()1859 int32_t nsPop3Protocol::NextAuthStep() {
1860   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("NextAuthStep()")));
1861   if (m_pop3ConData->command_succeeded) {
1862     if (m_password_already_sent ||  // (also true for GSSAPI)
1863         m_currentAuthMethod == POP3_HAS_AUTH_NONE) {
1864       MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Login succeeded")));
1865       m_nsIPop3Sink->SetUserAuthenticated(true);
1866       ClearFlag(POP3_PASSWORD_FAILED);
1867       if (m_pop3ConData->verify_logon)
1868         m_pop3ConData->next_state = POP3_SEND_QUIT;
1869       else
1870         m_pop3ConData->next_state =
1871             (m_pop3ConData->get_url) ? POP3_SEND_GURL : POP3_SEND_STAT;
1872     } else
1873       m_pop3ConData->next_state = POP3_SEND_PASSWORD;
1874   } else {
1875     MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
1876             (POP3LOG("command did not succeed")));
1877     // response code received shows that login failed not because of
1878     // wrong credential -> stop login without retry or pw dialog, only alert
1879     // parameter list -> user
1880     nsCString userName;
1881     nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
1882     nsresult rv = server->GetRealUsername(userName);
1883     NS_ENSURE_SUCCESS(rv, -1);
1884     NS_ConvertUTF8toUTF16 userNameUTF16(userName);
1885     if (TestFlag(POP3_STOPLOGIN)) {
1886       if (m_password_already_sent)
1887         return Error("pop3PasswordFailed", userNameUTF16.get());
1888 
1889       return Error("pop3UsernameFailure");
1890     }
1891     // response code received shows that server is certain about the
1892     // credential was wrong -> no fallback, show alert and pw dialog
1893     if (TestFlag(POP3_AUTH_FAILURE)) {
1894       MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
1895               (POP3LOG("auth failure, setting password failed")));
1896       if (m_password_already_sent)
1897         Error("pop3PasswordFailed", userNameUTF16.get());
1898       else
1899         Error("pop3UsernameFailure");
1900       SetFlag(POP3_PASSWORD_FAILED);
1901       ClearFlag(POP3_AUTH_FAILURE);
1902       return 0;
1903     }
1904 
1905     // We have no certain response code -> fallback and try again.
1906     // Mark the auth method failed, to use a different method next round.
1907     MarkAuthMethodAsFailed(m_currentAuthMethod);
1908 
1909     if (m_currentAuthMethod == POP3_HAS_AUTH_USER && !m_password_already_sent) {
1910       MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
1911               (POP3LOG("USER username failed")));
1912       // if USER auth method failed before sending the password,
1913       // the username was wrong.
1914       // no fallback but return error
1915       return Error("pop3UsernameFailure");
1916     }
1917 
1918     // If we have no auth method left, ask user to try with new password
1919     rv = ChooseAuthMethod();
1920     if (NS_FAILED(rv)) {
1921       MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
1922               (POP3LOG(
1923                   "POP: no auth methods remaining, setting password failure")));
1924       /* Sever the connection and go back to the `read password' state,
1925          which, upon success, will re-open the connection.  Set a flag
1926          which causes the prompt to be different that time (to indicate
1927          that the old password was bogus.)
1928 
1929          But if we're just checking for new mail (biff) then don't bother
1930          prompting the user for a password: just fail silently.
1931       */
1932       SetFlag(POP3_PASSWORD_FAILED);
1933       Error("pop3PasswordFailed", userNameUTF16.get());
1934       return 0;
1935     }
1936     MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
1937             (POP3LOG("still have some auth methods to try")));
1938 
1939     // TODO needed?
1940     // m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
1941 
1942     m_pop3ConData->command_succeeded = true;
1943 
1944     m_pop3ConData->next_state = POP3_PROCESS_AUTH;
1945   }
1946 
1947   if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED)) {
1948     ClearCapFlag(POP3_AUTH_MECH_UNDEFINED);
1949     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
1950   }
1951 
1952   m_pop3ConData->pause_for_read = false;
1953 
1954   return 0;
1955 }
1956 
1957 // LOGIN consists of three steps not two as USER/PASS or CRAM-MD5,
1958 // so we've to start here and continue in SendUsername if the server
1959 // responds + to "AUTH LOGIN"
AuthLogin()1960 int32_t nsPop3Protocol::AuthLogin() {
1961   nsAutoCString command("AUTH LOGIN" CRLF);
1962   m_pop3ConData->next_state_after_response = POP3_AUTH_LOGIN_RESPONSE;
1963   m_pop3ConData->pause_for_read = true;
1964 
1965   return Pop3SendData(command.get());
1966 }
1967 
AuthLoginResponse()1968 int32_t nsPop3Protocol::AuthLoginResponse() {
1969   // need the test to be here instead in NextAuthStep() to
1970   // differentiate between command AUTH LOGIN failed and
1971   // sending username using LOGIN mechanism failed.
1972   if (!m_pop3ConData->command_succeeded) {
1973     // we failed with LOGIN, remove it
1974     MarkAuthMethodAsFailed(POP3_HAS_AUTH_LOGIN);
1975     m_pop3ConData->next_state = POP3_PROCESS_AUTH;
1976   } else
1977     m_pop3ConData->next_state = POP3_SEND_USERNAME;
1978 
1979   m_pop3ConData->pause_for_read = false;
1980 
1981   return 0;
1982 }
1983 
1984 // NTLM, like LOGIN consists of three steps not two as USER/PASS or CRAM-MD5,
1985 // so we've to start here and continue in SendUsername if the server
1986 // responds + to "AUTH NTLM"
AuthNtlm()1987 int32_t nsPop3Protocol::AuthNtlm() {
1988   nsAutoCString command(m_currentAuthMethod == POP3_HAS_AUTH_MSN
1989                             ? "AUTH MSN" CRLF
1990                             : "AUTH NTLM" CRLF);
1991   m_pop3ConData->next_state_after_response = POP3_AUTH_NTLM_RESPONSE;
1992   m_pop3ConData->pause_for_read = true;
1993 
1994   return Pop3SendData(command.get());
1995 }
1996 
AuthNtlmResponse()1997 int32_t nsPop3Protocol::AuthNtlmResponse() {
1998   // need the test to be here instead in NextAuthStep() to
1999   // differentiate between command AUTH NTLM failed and
2000   // sending username using NTLM mechanism failed.
2001   if (!m_pop3ConData->command_succeeded) {
2002     MarkAuthMethodAsFailed(POP3_HAS_AUTH_NTLM);
2003     MarkAuthMethodAsFailed(POP3_HAS_AUTH_MSN);
2004     m_pop3ConData->next_state = POP3_PROCESS_AUTH;
2005   } else
2006     m_pop3ConData->next_state = POP3_SEND_USERNAME;
2007 
2008   m_pop3ConData->pause_for_read = false;
2009 
2010   return 0;
2011 }
2012 
AuthGSSAPI()2013 int32_t nsPop3Protocol::AuthGSSAPI() {
2014   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("AuthGSSAPI()")));
2015   nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
2016   if (server) {
2017     nsAutoCString cmd;
2018     nsAutoCString service("pop@");
2019     nsCString hostName;
2020     nsresult rv;
2021     server->GetRealHostName(hostName);
2022     service.Append(hostName);
2023     rv = DoGSSAPIStep1(service.get(), m_username.get(), cmd);
2024     if (NS_SUCCEEDED(rv)) {
2025       m_GSSAPICache.Assign(cmd);
2026       m_pop3ConData->next_state_after_response = POP3_AUTH_GSSAPI_FIRST;
2027       m_pop3ConData->pause_for_read = true;
2028       return Pop3SendData("AUTH GSSAPI" CRLF);
2029     }
2030   }
2031 
2032   MarkAuthMethodAsFailed(POP3_HAS_AUTH_GSSAPI);
2033   m_pop3ConData->next_state = POP3_PROCESS_AUTH;
2034   m_pop3ConData->pause_for_read = false;
2035   return 0;
2036 }
2037 
AuthGSSAPIResponse(bool first)2038 int32_t nsPop3Protocol::AuthGSSAPIResponse(bool first) {
2039   if (!m_pop3ConData->command_succeeded) {
2040     if (first) m_GSSAPICache.Truncate();
2041     MarkAuthMethodAsFailed(POP3_HAS_AUTH_GSSAPI);
2042     m_pop3ConData->next_state = POP3_PROCESS_AUTH;
2043     m_pop3ConData->pause_for_read = false;
2044     return 0;
2045   }
2046 
2047   int32_t result;
2048 
2049   m_pop3ConData->next_state_after_response = POP3_AUTH_GSSAPI_STEP;
2050   m_pop3ConData->pause_for_read = true;
2051 
2052   if (first) {
2053     m_GSSAPICache += CRLF;
2054     result = Pop3SendData(m_GSSAPICache.get());
2055     m_GSSAPICache.Truncate();
2056   } else {
2057     nsAutoCString cmd;
2058     MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("GSSAPI step 2")));
2059     nsresult rv = DoGSSAPIStep2(m_commandResponse, cmd);
2060     if (NS_FAILED(rv)) cmd = "*";
2061     if (rv == NS_SUCCESS_AUTH_FINISHED) {
2062       m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP;
2063       m_password_already_sent = true;
2064     }
2065     cmd += CRLF;
2066     result = Pop3SendData(cmd.get());
2067   }
2068 
2069   return result;
2070 }
2071 
SendUsername()2072 int32_t nsPop3Protocol::SendUsername() {
2073   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendUsername()")));
2074   if (m_username.IsEmpty()) return Error("pop3UsernameUndefined");
2075 
2076   // <copied from="SendPassword()">
2077   // Needed for NTLM
2078 
2079   // The POP3_SEND_PASSWORD/POP3_WAIT_SEND_PASSWORD states have already
2080   // got the password - they will have cancelled if necessary.
2081   // If the password is still empty here, don't try to go on.
2082   if (m_passwordResult.IsEmpty()) {
2083     m_pop3ConData->next_state = POP3_ERROR_DONE;
2084     return Error("pop3PasswordUndefined");
2085   }
2086   // </copied>
2087 
2088   nsAutoCString cmd;
2089 
2090   if (m_currentAuthMethod == POP3_HAS_AUTH_NTLM)
2091     (void)DoNtlmStep1(m_username, m_passwordResult, cmd);
2092   else if (m_currentAuthMethod == POP3_HAS_AUTH_CRAM_MD5)
2093     cmd = "AUTH CRAM-MD5";
2094   else if (m_currentAuthMethod == POP3_HAS_AUTH_PLAIN)
2095     cmd = "AUTH PLAIN";
2096   else if (m_currentAuthMethod == POP3_HAS_AUTH_LOGIN) {
2097     char* base64Str =
2098         PL_Base64Encode(m_username.get(), m_username.Length(), nullptr);
2099     cmd = base64Str;
2100     PR_Free(base64Str);
2101   } else if (m_currentAuthMethod == POP3_HAS_AUTH_USER) {
2102     MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("USER login")));
2103     cmd = "USER ";
2104     cmd += m_username;
2105   } else {
2106     MOZ_LOG(
2107         POP3LOGMODULE, LogLevel::Error,
2108         (POP3LOG(
2109              "In nsPop3Protocol::SendUsername(), m_currentAuthMethod is 0x%X, "
2110              "but that is unexpected"),
2111          m_currentAuthMethod));
2112     return Error("pop3AuthInternalError");
2113   }
2114 
2115   cmd += CRLF;
2116 
2117   m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP;
2118 
2119   m_pop3ConData->pause_for_read = true;
2120 
2121   return Pop3SendData(cmd.get());
2122 }
2123 
SendPassword()2124 int32_t nsPop3Protocol::SendPassword() {
2125   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendPassword()")));
2126   if (m_username.IsEmpty()) return Error("pop3UsernameUndefined");
2127 
2128   // <copied to="SendUsername()">
2129   // Needed here, too, because APOP skips SendUsername()
2130   // The POP3_SEND_PASSWORD/POP3_WAIT_SEND_PASSWORD states have already
2131   // got the password - they will have cancelled if necessary.
2132   // If the password is still empty here, don't try to go on.
2133   if (m_passwordResult.IsEmpty()) {
2134     m_pop3ConData->next_state = POP3_ERROR_DONE;
2135     return Error("pop3PasswordUndefined");
2136   }
2137   // </copied>
2138 
2139   nsAutoCString cmd;
2140   nsresult rv;
2141   NS_ConvertUTF16toUTF8 passwordUTF8(m_passwordResult);
2142 
2143   if (m_currentAuthMethod == POP3_HAS_AUTH_NTLM)
2144     rv = DoNtlmStep2(m_commandResponse, cmd);
2145   else if (m_currentAuthMethod == POP3_HAS_AUTH_CRAM_MD5) {
2146     MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("CRAM login")));
2147     char buffer[255 + 1 + 2 * DIGEST_LENGTH + 1];
2148     unsigned char digest[DIGEST_LENGTH];
2149 
2150     char* decodedChallenge = PL_Base64Decode(
2151         m_commandResponse.get(), m_commandResponse.Length(), nullptr);
2152 
2153     if (decodedChallenge)
2154       rv = MSGCramMD5(decodedChallenge, strlen(decodedChallenge),
2155                       passwordUTF8.get(), passwordUTF8.Length(), digest);
2156     else
2157       rv = NS_ERROR_NULL_POINTER;
2158 
2159     if (NS_SUCCEEDED(rv)) {
2160       // The encoded digest is the hexadecimal representation of
2161       // DIGEST_LENGTH characters, so it will be twice that length.
2162       nsAutoCStringN<2 * DIGEST_LENGTH> encodedDigest;
2163 
2164       for (uint32_t j = 0; j < DIGEST_LENGTH; j++) {
2165         char hexVal[3];
2166         PR_snprintf(hexVal, 3, "%.2x", 0x0ff & (unsigned short)digest[j]);
2167         encodedDigest.Append(hexVal);
2168       }
2169 
2170       PR_snprintf(buffer, sizeof(buffer), "%.255s %s", m_username.get(),
2171                   encodedDigest.get());
2172       char* base64Str = PL_Base64Encode(buffer, strlen(buffer), nullptr);
2173       cmd.Adopt(base64Str);
2174     }
2175 
2176     if (NS_FAILED(rv)) cmd = "*";
2177   } else if (m_currentAuthMethod == POP3_HAS_AUTH_APOP) {
2178     MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("APOP login")));
2179     char buffer[5 + 255 + 1 + 2 * DIGEST_LENGTH + 1];
2180     unsigned char digest[DIGEST_LENGTH];
2181 
2182     rv = MSGApopMD5(m_ApopTimestamp.get(), m_ApopTimestamp.Length(),
2183                     passwordUTF8.get(), passwordUTF8.Length(), digest);
2184 
2185     if (NS_SUCCEEDED(rv)) {
2186       // The encoded digest is the hexadecimal representation of
2187       // DIGEST_LENGTH characters, so it will be twice that length.
2188       nsAutoCStringN<2 * DIGEST_LENGTH> encodedDigest;
2189 
2190       for (uint32_t j = 0; j < DIGEST_LENGTH; j++) {
2191         char hexVal[3];
2192         PR_snprintf(hexVal, 3, "%.2x", 0x0ff & (unsigned short)digest[j]);
2193         encodedDigest.Append(hexVal);
2194       }
2195 
2196       PR_snprintf(buffer, sizeof(buffer), "APOP %.255s %s", m_username.get(),
2197                   encodedDigest.get());
2198       cmd = buffer;
2199     }
2200 
2201     if (NS_FAILED(rv)) cmd = "*";
2202   } else if (m_currentAuthMethod == POP3_HAS_AUTH_PLAIN) {
2203     MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("PLAIN login")));
2204     // workaround for IPswitch's IMail server software
2205     // this server goes into LOGIN mode even if we send "AUTH PLAIN"
2206     // "VXNlc" is the beginning of the base64 encoded prompt ("Username:") for
2207     // LOGIN
2208     if (StringBeginsWith(m_commandResponse, "VXNlc"_ns)) {
2209       // disable PLAIN and enable LOGIN (in case it's not already enabled)
2210       ClearCapFlag(POP3_HAS_AUTH_PLAIN);
2211       SetCapFlag(POP3_HAS_AUTH_LOGIN);
2212       m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
2213 
2214       // reenter authentication again at LOGIN response handler
2215       m_pop3ConData->next_state = POP3_AUTH_LOGIN_RESPONSE;
2216       m_pop3ConData->pause_for_read = false;
2217       return 0;
2218     }
2219 
2220     char plain_string[513];
2221     memset(plain_string, 0, 513);
2222     PR_snprintf(&plain_string[1], 256, "%.255s", m_username.get());
2223     uint32_t len = std::min<uint32_t>(m_username.Length(), 255u) +
2224                    2;                 // We include two <NUL> characters.
2225     if (passwordUTF8.Length() > 255)  // RFC 4616: passwd; up to 255 octets
2226       passwordUTF8.Truncate(255);
2227     PR_snprintf(&plain_string[len], 256, "%s", passwordUTF8.get());
2228     len += passwordUTF8.Length();
2229 
2230     char* base64Str = PL_Base64Encode(plain_string, len, nullptr);
2231     cmd.Adopt(base64Str);
2232   } else if (m_currentAuthMethod == POP3_HAS_AUTH_LOGIN) {
2233     MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("LOGIN password")));
2234     char* base64Str =
2235         PL_Base64Encode(passwordUTF8.get(), passwordUTF8.Length(), nullptr);
2236     cmd.Adopt(base64Str);
2237   } else if (m_currentAuthMethod == POP3_HAS_AUTH_USER) {
2238     MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("PASS password")));
2239     cmd = "PASS ";
2240     bool useLatin1 = mozilla::Preferences::GetBool(
2241         "mail.smtp_login_pop3_user_pass_auth_is_latin1", true);
2242     if (useLatin1)
2243       cmd += NS_LossyConvertUTF16toASCII(m_passwordResult);
2244     else
2245       cmd += passwordUTF8;
2246   } else {
2247     MOZ_LOG(
2248         POP3LOGMODULE, LogLevel::Error,
2249         (POP3LOG(
2250              "In nsPop3Protocol::SendPassword(), m_currentAuthMethod is %X, "
2251              "but that is unexpected"),
2252          m_currentAuthMethod));
2253     return Error("pop3AuthInternalError");
2254   }
2255 
2256   cmd += CRLF;
2257 
2258   // TODO needed?
2259   // m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
2260 
2261   m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP;
2262 
2263   m_pop3ConData->pause_for_read = true;
2264 
2265   m_password_already_sent = true;
2266   m_lastPasswordSent = m_passwordResult;
2267   return Pop3SendData(cmd.get(), true);
2268 }
2269 
SendStatOrGurl(bool sendStat)2270 int32_t nsPop3Protocol::SendStatOrGurl(bool sendStat) {
2271   nsAutoCString cmd;
2272   if (sendStat) {
2273     cmd = "STAT" CRLF;
2274     m_pop3ConData->next_state_after_response = POP3_GET_STAT;
2275   } else {
2276     cmd = "GURL" CRLF;
2277     m_pop3ConData->next_state_after_response = POP3_GURL_RESPONSE;
2278   }
2279   return Pop3SendData(cmd.get());
2280 }
2281 
SendStat()2282 int32_t nsPop3Protocol::SendStat() { return SendStatOrGurl(true); }
2283 
GetStat()2284 int32_t nsPop3Protocol::GetStat() {
2285   // check stat response
2286   if (!m_pop3ConData->command_succeeded) return Error("pop3StatFail");
2287 
2288   /* stat response looks like:  %d %d
2289    * The first number is the number of articles
2290    * The second number is the number of bytes
2291    *
2292    *  grab the first and second arg of stat response
2293    */
2294   nsCString oldStr(m_commandResponse);
2295   char* newStr = oldStr.BeginWriting();
2296   char* num = NS_strtok(" ", &newStr);  // msg num
2297   if (num) {
2298     m_pop3ConData->number_of_messages = atol(num);  // bytes
2299     num = NS_strtok(" ", &newStr);
2300     m_commandResponse = newStr;
2301     if (num)
2302       m_totalFolderSize =
2303           nsCRT::atoll(num);  // we always initialize m_totalFolderSize to 0
2304   } else
2305     m_pop3ConData->number_of_messages = 0;
2306 
2307   m_pop3ConData->really_new_messages = 0;
2308   m_pop3ConData->real_new_counter = 1;
2309 
2310   m_totalDownloadSize = -1;  // Means we need to calculate it, later.
2311 
2312   if (m_pop3ConData->number_of_messages <= 0) {
2313     // We're all done. We know we have no mail.
2314     m_pop3ConData->next_state = POP3_SEND_QUIT;
2315     PL_HashTableEnumerateEntries(m_pop3ConData->uidlinfo->hash,
2316                                  hash_clear_mapper, nullptr);
2317     // Hack - use nsPop3Sink to wipe out any stale Partial messages
2318     m_nsIPop3Sink->BeginMailDelivery(false, nullptr, nullptr);
2319     m_nsIPop3Sink->AbortMailDelivery(this);
2320     return (0);
2321   }
2322 
2323   /* We're just checking for new mail, and we're not playing any games that
2324      involve keeping messages on the server.  Therefore, we now know enough
2325      to finish up.  If we had no messages, that would have been handled
2326      above; therefore, we know we have some new messages.
2327   */
2328   if (m_pop3ConData->only_check_for_new_mail &&
2329       !m_pop3ConData->leave_on_server) {
2330     m_nsIPop3Sink->SetBiffStateAndUpdateFE(nsIMsgFolder::nsMsgBiffState_NewMail,
2331                                            m_pop3ConData->number_of_messages,
2332                                            true);
2333     m_pop3ConData->next_state = POP3_SEND_QUIT;
2334     return (0);
2335   }
2336 
2337   if (!m_pop3ConData->only_check_for_new_mail) {
2338     /* The following was added to prevent the loss of Data when we try and
2339        write to somewhere we don't have write access error to (See bug 62480)
2340        (Note: This is only a temp hack until the underlying XPCOM is fixed
2341        to return errors) */
2342 
2343     nsresult rv;
2344     nsCOMPtr<nsIMsgWindow> msgWindow;
2345     nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
2346     if (mailnewsUrl) rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
2347     // NS_ASSERTION(NS_SUCCEEDED(rv) && msgWindow, "no msg window");
2348 
2349     rv = m_nsIPop3Sink->BeginMailDelivery(m_pop3ConData->only_uidl != nullptr,
2350                                           msgWindow,
2351                                           &m_pop3ConData->msg_del_started);
2352     if (NS_FAILED(rv)) {
2353       m_nsIPop3Sink->AbortMailDelivery(this);
2354       if (rv == NS_MSG_FOLDER_BUSY) {
2355         nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
2356         nsString accountName;
2357         rv = server->GetPrettyName(accountName);
2358         NS_ENSURE_SUCCESS(rv, -1);
2359 
2360         return Error("pop3ServerBusy", accountName.get());
2361       }
2362 
2363       return Error("pop3MessageWriteError");
2364     }
2365 
2366     if (!m_pop3ConData->msg_del_started) return Error("pop3MessageWriteError");
2367   }
2368 
2369   m_pop3ConData->next_state = POP3_SEND_LIST;
2370   return 0;
2371 }
2372 
SendGurl()2373 int32_t nsPop3Protocol::SendGurl() {
2374   if (m_pop3ConData->capability_flags == POP3_CAPABILITY_UNDEFINED ||
2375       TestCapFlag(POP3_GURL_UNDEFINED | POP3_HAS_GURL))
2376     return SendStatOrGurl(false);
2377   else
2378     return -1;
2379 }
2380 
GurlResponse()2381 int32_t nsPop3Protocol::GurlResponse() {
2382   ClearCapFlag(POP3_GURL_UNDEFINED);
2383 
2384   if (m_pop3ConData->command_succeeded) {
2385     SetCapFlag(POP3_HAS_GURL);
2386     if (m_nsIPop3Sink) m_nsIPop3Sink->SetMailAccountURL(m_commandResponse);
2387   } else {
2388     ClearCapFlag(POP3_HAS_GURL);
2389   }
2390   m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
2391   m_pop3ConData->next_state = POP3_SEND_QUIT;
2392 
2393   return 0;
2394 }
2395 
SendList()2396 int32_t nsPop3Protocol::SendList() {
2397   // check for server returning number of messages that will cause the
2398   // calculation of the size of the block for msg_info to overflow a 32 bit int,
2399   // in turn causing us to allocate a block of memory much smaller than we think
2400   // we're allocating, and potentially allowing the server to make us overwrite
2401   // memory outside our heap block.
2402 
2403   if (m_pop3ConData->number_of_messages >
2404       (int)(0xFFFFF000 / sizeof(Pop3MsgInfo)))
2405     return MK_OUT_OF_MEMORY;
2406 
2407   m_pop3ConData->msg_info = (Pop3MsgInfo*)PR_CALLOC(
2408       sizeof(Pop3MsgInfo) * m_pop3ConData->number_of_messages);
2409   if (!m_pop3ConData->msg_info) return (MK_OUT_OF_MEMORY);
2410   m_pop3ConData->next_state_after_response = POP3_GET_LIST;
2411   m_listpos = 0;
2412   return Pop3SendData("LIST" CRLF);
2413 }
2414 
GetList(nsIInputStream * inputStream,uint32_t length)2415 int32_t nsPop3Protocol::GetList(nsIInputStream* inputStream, uint32_t length) {
2416   /* check list response
2417    * This will get called multiple times
2418    * but it's alright since command_succeeded
2419    * will remain constant
2420    */
2421   if (!m_pop3ConData->command_succeeded) return Error("pop3ListFailure");
2422 
2423   uint32_t ln = 0;
2424   bool pauseForMoreData = false;
2425   nsresult rv;
2426   char* line =
2427       m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv);
2428   if (NS_FAILED(rv)) return -1;
2429 
2430   if (pauseForMoreData || !line) {
2431     m_pop3ConData->pause_for_read = true;
2432     PR_Free(line);
2433     return (ln);
2434   }
2435 
2436   MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
2437 
2438   /* parse the line returned from the list command
2439    * it looks like
2440    * #msg_number #bytes
2441    *
2442    * list data is terminated by a ".CRLF" line
2443    */
2444   if (!PL_strcmp(line, ".")) {
2445     // limit the list if fewer entries than given in STAT response
2446     if (m_listpos < m_pop3ConData->number_of_messages)
2447       m_pop3ConData->number_of_messages = m_listpos;
2448     m_pop3ConData->next_state = POP3_SEND_UIDL_LIST;
2449     m_pop3ConData->pause_for_read = false;
2450     PR_Free(line);
2451     return (0);
2452   }
2453 
2454   char* newStr = line;
2455   char* token = NS_strtok(" ", &newStr);
2456   if (token) {
2457     int32_t msg_num = atol(token);
2458 
2459     if (++m_listpos <= m_pop3ConData->number_of_messages) {
2460       token = NS_strtok(" ", &newStr);
2461       if (token) {
2462         m_pop3ConData->msg_info[m_listpos - 1].size = atol(token);
2463         m_pop3ConData->msg_info[m_listpos - 1].msgnum = msg_num;
2464       }
2465     }
2466   }
2467 
2468   PR_Free(line);
2469   return (0);
2470 }
2471 
2472 /* UIDL and XTND are both unsupported for this mail server.
2473    If not enabled any advanced features, we're able to live
2474    without them. We're simply downloading and deleting everything
2475    on the server.
2476 
2477    Advanced features are:
2478    *'Keep Mail on Server' with aging or deletion support
2479    *'Fetch Headers Only'
2480    *'Limit Message Size'
2481    *only download a specific UID
2482 
2483    These require knowledge of of all messages UID's on the server at
2484    least when it comes to deleting deleting messages on server that
2485    have been deleted on client or vice versa. TOP doesn't help here
2486    without generating huge traffic and is mostly not supported at all
2487    if the server lacks UIDL and XTND XLST.
2488 
2489    In other cases the user has to join the 20th century.
2490    Tell the user this, and refuse to download any messages until
2491    they've gone into preferences and turned off any of the above
2492    prefs.
2493 */
HandleNoUidListAvailable()2494 int32_t nsPop3Protocol::HandleNoUidListAvailable() {
2495   m_pop3ConData->pause_for_read = false;
2496 
2497   if (!m_pop3ConData->leave_on_server && !m_pop3ConData->headers_only &&
2498       m_pop3ConData->size_limit <= 0 && !m_pop3ConData->only_uidl) {
2499     m_pop3ConData->next_state = POP3_GET_MSG;
2500     return 0;
2501   }
2502   m_pop3ConData->next_state = POP3_SEND_QUIT;
2503   nsCString hostName;
2504   nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
2505   nsresult rv = server->GetRealHostName(hostName);
2506   NS_ENSURE_SUCCESS(rv, -1);
2507   NS_ConvertASCIItoUTF16 hostNameUnicode(hostName);
2508   return Error("pop3ServerDoesNotSupportUidlEtc", hostNameUnicode.get());
2509 }
2510 
2511 /* km
2512  *
2513  *  net_pop3_send_xtnd_xlst_msgid
2514  *
2515  *  Process state: POP3_SEND_XTND_XLST_MSGID
2516  *
2517  *  If we get here then UIDL is not supported by the mail server.
2518  *  Some mail servers support a similar command:
2519  *
2520  *    XTND XLST Message-Id
2521  *
2522  *  Here is a sample transaction from a QUALCOMM server
2523 
2524  >>XTND XLST Message-Id
2525  <<+OK xlst command accepted; headers coming.
2526  <<1 Message-ID: <3117E4DC.2699@netscape.invalid>
2527  <<2 Message-Id: <199602062335.PAA19215@lemon.example.com>
2528 
2529  * This function will send the xtnd command and put us into the
2530  * POP3_GET_XTND_XLST_MSGID state
2531  *
2532 */
SendXtndXlstMsgid()2533 int32_t nsPop3Protocol::SendXtndXlstMsgid() {
2534   if (TestCapFlag(POP3_HAS_XTND_XLST | POP3_XTND_XLST_UNDEFINED)) {
2535     m_pop3ConData->next_state_after_response = POP3_GET_XTND_XLST_MSGID;
2536     m_pop3ConData->pause_for_read = true;
2537     m_listpos = 0;
2538     return Pop3SendData("XTND XLST Message-Id" CRLF);
2539   } else
2540     return HandleNoUidListAvailable();
2541 }
2542 
2543 /* km
2544  *
2545  *  net_pop3_get_xtnd_xlst_msgid
2546  *
2547  *  This code was created from the net_pop3_get_uidl_list boiler plate.
2548  *  The difference is that the XTND reply strings have one more token per
2549  *  string than the UIDL reply strings do.
2550  *
2551  */
2552 
GetXtndXlstMsgid(nsIInputStream * inputStream,uint32_t length)2553 int32_t nsPop3Protocol::GetXtndXlstMsgid(nsIInputStream* inputStream,
2554                                          uint32_t length) {
2555   /* check list response
2556    * This will get called multiple times
2557    * but it's alright since command_succeeded
2558    * will remain constant
2559    */
2560   ClearCapFlag(POP3_XTND_XLST_UNDEFINED);
2561 
2562   if (!m_pop3ConData->command_succeeded) {
2563     ClearCapFlag(POP3_HAS_XTND_XLST);
2564     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
2565     HandleNoUidListAvailable();
2566     return (0);
2567   } else {
2568     SetCapFlag(POP3_HAS_XTND_XLST);
2569     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
2570   }
2571 
2572   uint32_t ln = 0;
2573   bool pauseForMoreData = false;
2574   nsresult rv;
2575   char* line =
2576       m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv);
2577   if (NS_FAILED(rv)) return -1;
2578 
2579   if (pauseForMoreData || !line) {
2580     m_pop3ConData->pause_for_read = true;
2581     PR_Free(line);
2582     return ln;
2583   }
2584 
2585   MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
2586 
2587   /* parse the line returned from the list command
2588    * it looks like
2589    * 1 Message-ID: <3117E4DC.2699@example.com>
2590    *
2591    * list data is terminated by a ".CRLF" line
2592    */
2593   if (!PL_strcmp(line, ".")) {
2594     // limit the list if fewer entries than given in STAT response
2595     if (m_listpos < m_pop3ConData->number_of_messages)
2596       m_pop3ConData->number_of_messages = m_listpos;
2597     m_pop3ConData->list_done = true;
2598     m_pop3ConData->next_state = POP3_GET_MSG;
2599     m_pop3ConData->pause_for_read = false;
2600     PR_Free(line);
2601     return (0);
2602   }
2603 
2604   char* newStr = line;
2605   char* token = NS_strtok(" ", &newStr);  // msg num
2606   if (token) {
2607     int32_t msg_num = atol(token);
2608     if (++m_listpos <= m_pop3ConData->number_of_messages) {
2609       NS_strtok(" ", &newStr);  // eat message ID token
2610       const char* uid =
2611           NS_strtok(" ", &newStr);  // not really a UID but a unique token -km
2612       if (!uid)
2613         /* This is bad.  The server didn't give us a UIDL for this message.
2614         I've seen this happen when somehow the mail spool has a message
2615         that contains a header that reads "X-UIDL: \n".  But how that got
2616         there, I have no idea; must be a server bug.  Or something. */
2617         uid = "";
2618 
2619       // seeking right entry, but try the one that should it be first
2620       int32_t i;
2621       if (m_pop3ConData->msg_info[m_listpos - 1].msgnum == msg_num)
2622         i = m_listpos - 1;
2623       else
2624         for (i = 0; i < m_pop3ConData->number_of_messages &&
2625                     m_pop3ConData->msg_info[i].msgnum != msg_num;
2626              i++)
2627           ;
2628 
2629       // only if found a matching slot
2630       if (i < m_pop3ConData->number_of_messages) {
2631         // to protect us from memory leak in case of getting a msg num twice
2632         m_pop3ConData->msg_info[i].uidl = PL_strdup(uid);
2633         if (!m_pop3ConData->msg_info[i].uidl) {
2634           PR_Free(line);
2635           return MK_OUT_OF_MEMORY;
2636         }
2637       }
2638     }
2639   }
2640 
2641   PR_Free(line);
2642   return (0);
2643 }
2644 
SendUidlList()2645 int32_t nsPop3Protocol::SendUidlList() {
2646   if (TestCapFlag(POP3_HAS_UIDL | POP3_UIDL_UNDEFINED)) {
2647     m_pop3ConData->next_state_after_response = POP3_GET_UIDL_LIST;
2648     m_pop3ConData->pause_for_read = true;
2649     m_listpos = 0;
2650     return Pop3SendData("UIDL" CRLF);
2651   } else
2652     return SendXtndXlstMsgid();
2653 }
2654 
GetUidlList(nsIInputStream * inputStream,uint32_t length)2655 int32_t nsPop3Protocol::GetUidlList(nsIInputStream* inputStream,
2656                                     uint32_t length) {
2657   if (m_pop3ConData->command_temp_fail) {
2658     nsCString hostName;
2659     nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
2660     nsresult rv = server->GetRealHostName(hostName);
2661     NS_ENSURE_SUCCESS(rv, -1);
2662     NS_ConvertASCIItoUTF16 hostNameUnicode(hostName);
2663     return Error("pop3TempServerError", hostNameUnicode.get());
2664   }
2665 
2666   /* check list response
2667    * This will get called multiple times
2668    * but it's alright since command_succeeded
2669    * will remain constant
2670    */
2671   ClearCapFlag(POP3_UIDL_UNDEFINED);
2672 
2673   if (!m_pop3ConData->command_succeeded) {
2674     m_pop3ConData->next_state = POP3_SEND_XTND_XLST_MSGID;
2675     m_pop3ConData->pause_for_read = false;
2676     ClearCapFlag(POP3_HAS_UIDL);
2677     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
2678     return (0);
2679   } else {
2680     SetCapFlag(POP3_HAS_UIDL);
2681     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
2682   }
2683 
2684   uint32_t ln = 0;
2685   bool pauseForMoreData = false;
2686   nsresult rv;
2687   char* line =
2688       m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv);
2689   if (NS_FAILED(rv)) return -1;
2690 
2691   if (pauseForMoreData || !line) {
2692     PR_Free(line);
2693     m_pop3ConData->pause_for_read = true;
2694     return ln;
2695   }
2696 
2697   MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
2698 
2699   /* parse the line returned from the list command
2700    * it looks like
2701    * #msg_number uidl
2702    *
2703    * list data is terminated by a ".CRLF" line
2704    */
2705   if (!PL_strcmp(line, ".")) {
2706     // limit the list if fewer entries than given in STAT response
2707     if (m_listpos < m_pop3ConData->number_of_messages)
2708       m_pop3ConData->number_of_messages = m_listpos;
2709     m_pop3ConData->list_done = true;
2710     m_pop3ConData->next_state = POP3_GET_MSG;
2711     m_pop3ConData->pause_for_read = false;
2712     PR_Free(line);
2713     return (0);
2714   }
2715 
2716   char* newStr = line;
2717   char* token = NS_strtok(" ", &newStr);  // msg num
2718   if (token) {
2719     int32_t msg_num = atol(token);
2720     if (++m_listpos <= m_pop3ConData->number_of_messages) {
2721       const char* uid = NS_strtok(" ", &newStr);  // UID
2722       if (!uid)
2723         /* This is bad.  The server didn't give us a UIDL for this message.
2724            I've seen this happen when somehow the mail spool has a message
2725            that contains a header that reads "X-UIDL: \n".  But how that got
2726            there, I have no idea; must be a server bug.  Or something. */
2727         uid = "";
2728 
2729       // seeking right entry, but try the one that should it be first
2730       int32_t i;
2731       if (m_pop3ConData->msg_info[m_listpos - 1].msgnum == msg_num)
2732         i = m_listpos - 1;
2733       else
2734         for (i = 0; i < m_pop3ConData->number_of_messages &&
2735                     m_pop3ConData->msg_info[i].msgnum != msg_num;
2736              i++)
2737           ;
2738 
2739       // only if found a matching slot
2740       if (i < m_pop3ConData->number_of_messages) {
2741         // to protect us from memory leak in case of getting a msg num twice
2742         m_pop3ConData->msg_info[i].uidl = PL_strdup(uid);
2743         if (!m_pop3ConData->msg_info[i].uidl) {
2744           PR_Free(line);
2745           return MK_OUT_OF_MEMORY;
2746         }
2747       }
2748     }
2749   }
2750   PR_Free(line);
2751   return (0);
2752 }
2753 
2754 /* this function decides if we are going to do a
2755  * normal RETR or a TOP.  The first time, it also decides the total number
2756  * of bytes we're probably going to get.
2757  */
GetMsg()2758 int32_t nsPop3Protocol::GetMsg() {
2759   int32_t popstateTimestamp = TimeInSecondsFromPRTime(PR_Now());
2760 
2761   if (m_pop3ConData->last_accessed_msg >= m_pop3ConData->number_of_messages) {
2762     /* Oh, gee, we're all done. */
2763     if (m_pop3ConData->msg_del_started) {
2764       if (!m_pop3ConData->only_uidl) {
2765         if (m_pop3ConData->only_check_for_new_mail)
2766           m_nsIPop3Sink->SetBiffStateAndUpdateFE(
2767               m_pop3ConData->biffstate, m_pop3ConData->really_new_messages,
2768               true);
2769         /* update old style biff */
2770         else
2771           m_nsIPop3Sink->SetBiffStateAndUpdateFE(
2772               nsIMsgFolder::nsMsgBiffState_NewMail,
2773               m_pop3ConData->really_new_messages, false);
2774       }
2775       m_nsIPop3Sink->EndMailDelivery(this);
2776     }
2777 
2778     m_pop3ConData->next_state = POP3_SEND_QUIT;
2779     return 0;
2780   }
2781 
2782   if (m_totalDownloadSize < 0) {
2783     /* First time.  Figure out how many bytes we're about to get.
2784     If we didn't get any message info, then we are going to get
2785     everything, and it's easy.  Otherwise, if we only want one
2786     uidl, than that's the only one we'll get.  Otherwise, go
2787     through each message info, decide if we're going to get that
2788     message, and add the number of bytes for it. When a message is too
2789     large (per user's preferences) only add the size we are supposed
2790     to get. */
2791     m_pop3ConData->really_new_messages = 0;
2792     m_pop3ConData->real_new_counter = 1;
2793     if (m_pop3ConData->msg_info) {
2794       m_totalDownloadSize = 0;
2795       for (int32_t i = 0; i < m_pop3ConData->number_of_messages; i++) {
2796         if (m_pop3ConData->only_uidl) {
2797           if (m_pop3ConData->msg_info[i].uidl &&
2798               !PL_strcmp(m_pop3ConData->msg_info[i].uidl,
2799                          m_pop3ConData->only_uidl)) {
2800             m_totalDownloadSize = m_pop3ConData->msg_info[i].size;
2801             m_pop3ConData->really_new_messages = 1;
2802             // we are only getting one message
2803             m_pop3ConData->real_new_counter = 1;
2804             break;
2805           }
2806           continue;
2807         }
2808 
2809         char c = 0;
2810         popstateTimestamp = TimeInSecondsFromPRTime(PR_Now());
2811         if (m_pop3ConData->msg_info[i].uidl) {
2812           Pop3UidlEntry* uidlEntry = (Pop3UidlEntry*)PL_HashTableLookup(
2813               m_pop3ConData->uidlinfo->hash, m_pop3ConData->msg_info[i].uidl);
2814           if (uidlEntry) {
2815             c = uidlEntry->status;
2816             popstateTimestamp = uidlEntry->dateReceived;
2817           }
2818         }
2819         if ((c == KEEP) &&
2820             !m_pop3ConData
2821                  ->leave_on_server) { /* This message has been downloaded but
2822                                        * kept on server, we no longer want to
2823                                        * keep it there */
2824           if (!m_pop3ConData->newuidl) {
2825             m_pop3ConData->newuidl =
2826                 PL_NewHashTable(20, PL_HashString, PL_CompareStrings,
2827                                 PL_CompareValues, &gHashAllocOps, nullptr);
2828             if (!m_pop3ConData->newuidl) return MK_OUT_OF_MEMORY;
2829           }
2830           c = DELETE_CHAR;
2831           // Mark message to be deleted in new table
2832           put_hash(m_pop3ConData->newuidl, m_pop3ConData->msg_info[i].uidl,
2833                    DELETE_CHAR, popstateTimestamp);
2834           // and old one too
2835           put_hash(m_pop3ConData->uidlinfo->hash,
2836                    m_pop3ConData->msg_info[i].uidl, DELETE_CHAR,
2837                    popstateTimestamp);
2838         }
2839         if ((c != KEEP) && (c != DELETE_CHAR) &&
2840             (c != TOO_BIG)) {  // message left on server
2841           m_totalDownloadSize += m_pop3ConData->msg_info[i].size;
2842           m_pop3ConData->really_new_messages++;
2843           // a message we will really download
2844         }
2845       }
2846     } else {
2847       m_totalDownloadSize = m_totalFolderSize;
2848     }
2849     if (m_pop3ConData->only_check_for_new_mail) {
2850       if (m_totalDownloadSize > 0) {
2851         m_pop3ConData->biffstate = nsIMsgFolder::nsMsgBiffState_NewMail;
2852         m_nsIPop3Sink->SetBiffStateAndUpdateFE(
2853             nsIMsgFolder::nsMsgBiffState_NewMail,
2854             m_pop3ConData->really_new_messages, true);
2855       }
2856       m_pop3ConData->next_state = POP3_SEND_QUIT;
2857       return (0);
2858     }
2859     /* get the amount of available space on the drive
2860      * and make sure there is enough
2861      */
2862     if (m_totalDownloadSize > 0)  // skip all this if there aren't any messages
2863     {
2864       nsCOMPtr<nsIMsgFolder> folder;
2865 
2866       // Get the current mailbox folder
2867       NS_ENSURE_TRUE(m_nsIPop3Sink, -1);
2868       nsresult rv = m_nsIPop3Sink->GetFolder(getter_AddRefs(folder));
2869       if (NS_FAILED(rv)) return -1;
2870 
2871       nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
2872       if (NS_FAILED(rv) || !mailnewsUrl) return -1;
2873 
2874       nsCOMPtr<nsIMsgWindow> msgWindow;
2875       (void)mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
2876 
2877       bool spaceNotAvailable = true;
2878       nsCOMPtr<nsIMsgLocalMailFolder> localFolder(
2879           do_QueryInterface(folder, &rv));
2880       if (NS_FAILED(rv) || !localFolder) return -1;
2881 
2882       // check if we have a reasonable amount of space left
2883       rv = localFolder->WarnIfLocalFileTooBig(msgWindow, m_totalDownloadSize,
2884                                               &spaceNotAvailable);
2885       if (NS_FAILED(rv) || spaceNotAvailable) {
2886 #ifdef DEBUG
2887         printf("Not enough disk space! Raising error!\n");
2888 #endif
2889         return -1;
2890       }
2891 
2892       // Here we know how many messages we're going to download, so let
2893       // the pop3 sink know.
2894       rv = m_nsIPop3Sink->SetMsgsToDownload(m_pop3ConData->really_new_messages);
2895     }
2896   }
2897 
2898   /* Look at this message, and decide whether to ignore it, get it, just get
2899   the TOP of it, or delete it. */
2900 
2901   // if this is a message we've seen for the first time, we won't find it in
2902   // m_pop3ConData-uidlinfo->hash.  By default, we retrieve messages, unless
2903   // they have a status, or are too big, in which case we figure out what to do.
2904   if (m_prefAuthMethods != POP3_HAS_AUTH_USER && TestCapFlag(POP3_HAS_XSENDER))
2905     m_pop3ConData->next_state = POP3_SEND_XSENDER;
2906   else
2907     m_pop3ConData->next_state = POP3_SEND_RETR;
2908   m_pop3ConData->truncating_cur_msg = false;
2909   m_pop3ConData->pause_for_read = false;
2910   if (m_pop3ConData->msg_info) {
2911     Pop3MsgInfo* info =
2912         m_pop3ConData->msg_info + m_pop3ConData->last_accessed_msg;
2913     if (m_pop3ConData->only_uidl) {
2914       if (info->uidl == NULL || PL_strcmp(info->uidl, m_pop3ConData->only_uidl))
2915         m_pop3ConData->next_state = POP3_GET_MSG;
2916       else
2917         m_pop3ConData->next_state = POP3_SEND_RETR;
2918     } else {
2919       char c = 0;
2920       if (!m_pop3ConData->newuidl) {
2921         m_pop3ConData->newuidl =
2922             PL_NewHashTable(20, PL_HashString, PL_CompareStrings,
2923                             PL_CompareValues, &gHashAllocOps, nullptr);
2924         if (!m_pop3ConData->newuidl) return MK_OUT_OF_MEMORY;
2925       }
2926       if (info->uidl) {
2927         Pop3UidlEntry* uidlEntry = (Pop3UidlEntry*)PL_HashTableLookup(
2928             m_pop3ConData->uidlinfo->hash, info->uidl);
2929         if (uidlEntry) {
2930           c = uidlEntry->status;
2931           popstateTimestamp = uidlEntry->dateReceived;
2932         }
2933       }
2934       if (c == DELETE_CHAR) {
2935         m_pop3ConData->next_state = POP3_SEND_DELE;
2936       } else if (c == KEEP) {
2937         // this is a message we've already downloaded and left on server;
2938         // Advance to next message.
2939         m_pop3ConData->next_state = POP3_GET_MSG;
2940       } else if (c == FETCH_BODY) {
2941         m_pop3ConData->next_state = POP3_SEND_RETR;
2942         PL_HashTableRemove(m_pop3ConData->uidlinfo->hash, (void*)info->uidl);
2943       } else if ((c != TOO_BIG) &&
2944                  (TestCapFlag(POP3_TOP_UNDEFINED | POP3_HAS_TOP)) &&
2945                  (m_pop3ConData->headers_only ||
2946                   ((m_pop3ConData->size_limit > 0) &&
2947                    (info->size > m_pop3ConData->size_limit) &&
2948                    !m_pop3ConData->only_uidl)) &&
2949                  info->uidl && *info->uidl) {
2950         // message is too big
2951         m_pop3ConData->truncating_cur_msg = true;
2952         m_pop3ConData->next_state = POP3_SEND_TOP;
2953         put_hash(m_pop3ConData->newuidl, info->uidl, TOO_BIG,
2954                  popstateTimestamp);
2955       } else if (c == TOO_BIG) {
2956         /* message previously left on server, see if the max download size
2957         has changed, because we may want to download the message this time
2958         around. Otherwise ignore the message, we have the header. */
2959         if ((m_pop3ConData->size_limit > 0) &&
2960             (info->size <= m_pop3ConData->size_limit))
2961           PL_HashTableRemove(m_pop3ConData->uidlinfo->hash, (void*)info->uidl);
2962         // remove from our table, and download
2963         else {
2964           m_pop3ConData->truncating_cur_msg = true;
2965           m_pop3ConData->next_state = POP3_GET_MSG;
2966           // ignore this message and get next one
2967           put_hash(m_pop3ConData->newuidl, info->uidl, TOO_BIG,
2968                    popstateTimestamp);
2969         }
2970       }
2971 
2972       if (m_pop3ConData->next_state != POP3_SEND_DELE && info->uidl) {
2973         /* This is a message we have decided to keep on the server. Notate
2974             that now for the future. (Don't change the popstate file at all
2975             if only_uidl is set; in that case, there might be brand new messages
2976             on the server that we *don't* want to mark KEEP; we just want to
2977             leave them around until the user next does a GetNewMail.) */
2978 
2979         /* If this is a message we already know about (i.e., it was
2980             in popstate.dat already), we need to maintain the original
2981             date the message was downloaded. */
2982         if (m_pop3ConData->truncating_cur_msg)
2983           put_hash(m_pop3ConData->newuidl, info->uidl, TOO_BIG,
2984                    popstateTimestamp);
2985         else
2986           put_hash(m_pop3ConData->newuidl, info->uidl, KEEP, popstateTimestamp);
2987       }
2988     }
2989     if (m_pop3ConData->next_state == POP3_GET_MSG)
2990       m_pop3ConData->last_accessed_msg++;
2991     // Make sure we check the next message next time!
2992   }
2993   return 0;
2994 }
2995 
2996 /* start retrieving just the first 20 lines
2997  */
SendTop()2998 int32_t nsPop3Protocol::SendTop() {
2999   char* cmd = PR_smprintf(
3000       "TOP %ld %d" CRLF,
3001       m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum,
3002       m_pop3ConData->headers_only ? 0 : 20);
3003   int32_t status = -1;
3004   if (cmd) {
3005     m_pop3ConData->next_state_after_response = POP3_TOP_RESPONSE;
3006     m_pop3ConData->cur_msg_size = -1;
3007 
3008     /* zero the bytes received in message in preparation for
3009      * the next
3010      */
3011     m_bytesInMsgReceived = 0;
3012     status = Pop3SendData(cmd);
3013   }
3014   PR_Free(cmd);
3015   return status;
3016 }
3017 
3018 /* send the xsender command
3019  */
SendXsender()3020 int32_t nsPop3Protocol::SendXsender() {
3021   char* cmd = PR_smprintf(
3022       "XSENDER %ld" CRLF,
3023       m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum);
3024   int32_t status = -1;
3025   if (cmd) {
3026     m_pop3ConData->next_state_after_response = POP3_XSENDER_RESPONSE;
3027     status = Pop3SendData(cmd);
3028     PR_Free(cmd);
3029   }
3030   return status;
3031 }
3032 
XsenderResponse()3033 int32_t nsPop3Protocol::XsenderResponse() {
3034   m_pop3ConData->seenFromHeader = false;
3035   m_senderInfo = "";
3036 
3037   if (m_pop3ConData->command_succeeded) {
3038     if (m_commandResponse.Length() > 4) m_senderInfo = m_commandResponse;
3039   } else {
3040     ClearCapFlag(POP3_HAS_XSENDER);
3041     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
3042   }
3043 
3044   if (m_pop3ConData->truncating_cur_msg)
3045     m_pop3ConData->next_state = POP3_SEND_TOP;
3046   else
3047     m_pop3ConData->next_state = POP3_SEND_RETR;
3048   return 0;
3049 }
3050 
3051 /* retrieve the whole message
3052  */
SendRetr()3053 int32_t nsPop3Protocol::SendRetr() {
3054   char* cmd = PR_smprintf(
3055       "RETR %ld" CRLF,
3056       m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum);
3057   int32_t status = -1;
3058   if (cmd) {
3059     m_pop3ConData->next_state_after_response = POP3_RETR_RESPONSE;
3060     m_pop3ConData->cur_msg_size = -1;
3061 
3062     /* zero the bytes received in message in preparation for
3063      * the next
3064      */
3065     m_bytesInMsgReceived = 0;
3066 
3067     if (m_pop3ConData->only_uidl) {
3068       /* Display bytes if we're only downloading one message. */
3069       PR_ASSERT(!m_pop3ConData->graph_progress_bytes_p);
3070       UpdateProgressPercent(0, m_totalDownloadSize);
3071       m_pop3ConData->graph_progress_bytes_p = true;
3072     } else {
3073       nsString finalString;
3074       mozilla::DebugOnly<nsresult> rv = FormatCounterString(
3075           u"receivingMessages"_ns, m_pop3ConData->real_new_counter,
3076           m_pop3ConData->really_new_messages, finalString);
3077       NS_ASSERTION(NS_SUCCEEDED(rv), "couldn't format string");
3078       if (mProgressEventSink) {
3079         rv = mProgressEventSink->OnStatus(this, NS_OK, finalString.get());
3080         NS_ASSERTION(NS_SUCCEEDED(rv), "dropping error result");
3081       }
3082     }
3083 
3084     status = Pop3SendData(cmd);
3085   }  // if cmd
3086   PR_Free(cmd);
3087   return status;
3088 }
3089 
3090 /* digest the message
3091  */
RetrResponse(nsIInputStream * inputStream,uint32_t length)3092 int32_t nsPop3Protocol::RetrResponse(nsIInputStream* inputStream,
3093                                      uint32_t length) {
3094   uint32_t buffer_size;
3095   int32_t flags = 0;
3096   char* uidl = NULL;
3097   nsresult rv;
3098   uint32_t status = 0;
3099 
3100   if (m_pop3ConData->cur_msg_size == -1) {
3101     /* this is the beginning of a message
3102      * get the response code and byte size
3103      */
3104     if (!m_pop3ConData->command_succeeded) return Error("pop3RetrFailure");
3105 
3106     /* a successful RETR response looks like: #num_bytes Junk
3107        from TOP we only get the +OK and data
3108        */
3109     if (m_pop3ConData->truncating_cur_msg) { /* TOP, truncated message */
3110       flags |= nsMsgMessageFlags::Partial;
3111     } else {
3112       nsCString cmdResp(m_commandResponse);
3113       char* newStr = cmdResp.BeginWriting();
3114       char* num = NS_strtok(" ", &newStr);
3115       if (num) m_pop3ConData->cur_msg_size = atol(num);
3116       m_commandResponse = newStr;
3117     }
3118 
3119     /* RETR complete message */
3120     if (!m_senderInfo.IsEmpty()) flags |= nsMsgMessageFlags::SenderAuthed;
3121 
3122     if (m_pop3ConData->cur_msg_size <= 0) {
3123       if (m_pop3ConData->msg_info)
3124         m_pop3ConData->cur_msg_size =
3125             m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].size;
3126       else
3127         m_pop3ConData->cur_msg_size = 0;
3128     }
3129 
3130     if (m_pop3ConData->msg_info &&
3131         m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].uidl)
3132       uidl = m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].uidl;
3133 
3134     m_pop3ConData->parsed_bytes = 0;
3135     m_pop3ConData->pop3_size = m_pop3ConData->cur_msg_size;
3136     m_pop3ConData->assumed_end = false;
3137 
3138     m_pop3Server->GetDotFix(&m_pop3ConData->dot_fix);
3139 
3140     MOZ_LOG(POP3LOGMODULE, LogLevel::Info,
3141             (POP3LOG("Opening message stream: MSG_IncorporateBegin")));
3142 
3143     /* open the message stream so we have someplace
3144      * to put the data
3145      */
3146     m_pop3ConData->real_new_counter++;
3147     /* (rb) count only real messages being downloaded */
3148     rv = m_nsIPop3Sink->IncorporateBegin(uidl, m_url, flags,
3149                                          &m_pop3ConData->msg_closure);
3150 
3151     MOZ_LOG(POP3LOGMODULE, LogLevel::Info,
3152             (POP3LOG("Done opening message stream!")));
3153 
3154     if (!m_pop3ConData->msg_closure || NS_FAILED(rv))
3155       return Error("pop3MessageWriteError");
3156   }
3157 
3158   m_pop3ConData->pause_for_read = true;
3159 
3160   bool pauseForMoreData = false;
3161   char* line = m_lineStreamBuffer->ReadNextLine(inputStream, status,
3162                                                 pauseForMoreData, &rv, true);
3163   MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
3164   if (NS_FAILED(rv)) return -1;
3165 
3166   buffer_size = status;
3167 
3168   if (status == 0 && !line)  // no bytes read in...
3169     return (0);
3170 
3171   if (m_pop3ConData->msg_closure) /* not done yet */
3172   {
3173     // buffer the line we just read in, and buffer all remaining lines in the
3174     // stream
3175     status = buffer_size;
3176     do {
3177       if (m_pop3ConData->msg_closure) {
3178         rv = HandleLine(line, buffer_size);
3179         if (NS_FAILED(rv)) return Error("pop3MessageWriteError");
3180 
3181         // buffer_size already includes MSG_LINEBREAK_LEN so
3182         // subtract and add CRLF
3183         // but not really sure we always had CRLF in input since
3184         // we also treat a single LF as line ending!
3185         m_pop3ConData->parsed_bytes += buffer_size - MSG_LINEBREAK_LEN + 2;
3186       }
3187 
3188       // now read in the next line
3189       PR_Free(line);
3190       line = m_lineStreamBuffer->ReadNextLine(inputStream, buffer_size,
3191                                               pauseForMoreData, &rv, true);
3192       if (NS_FAILED(rv)) return -1;
3193 
3194       MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
3195       // buffer_size already includes MSG_LINEBREAK_LEN so
3196       // subtract and add CRLF
3197       // but not really sure we always had CRLF in input since
3198       // we also treat a single LF as line ending!
3199       status += buffer_size - MSG_LINEBREAK_LEN + 2;
3200     } while (line);
3201   }
3202 
3203   buffer_size =
3204       status;  // status holds # bytes we've actually buffered so far...
3205 
3206   /* normal read. Yay! */
3207   if ((int32_t)(m_bytesInMsgReceived + buffer_size) >
3208       m_pop3ConData->cur_msg_size)
3209     buffer_size = m_pop3ConData->cur_msg_size - m_bytesInMsgReceived;
3210 
3211   m_bytesInMsgReceived += buffer_size;
3212   m_totalBytesReceived += buffer_size;
3213 
3214   // *** jefft in case of the message size that server tells us is different
3215   // from the actual message size
3216   if (pauseForMoreData && m_pop3ConData->dot_fix &&
3217       m_pop3ConData->assumed_end && m_pop3ConData->msg_closure) {
3218     nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
3219     nsCOMPtr<nsIMsgWindow> msgWindow;
3220     if (NS_SUCCEEDED(rv))
3221       rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
3222     rv = m_nsIPop3Sink->IncorporateComplete(
3223         msgWindow,
3224         m_pop3ConData->truncating_cur_msg ? m_pop3ConData->cur_msg_size : 0);
3225 
3226     // The following was added to prevent the loss of Data when we try
3227     // and write to somewhere we don't have write access error to (See
3228     // bug 62480)
3229     // (Note: This is only a temp hack until the underlying XPCOM is
3230     // fixed to return errors)
3231 
3232     if (NS_FAILED(rv))
3233       return Error((rv == NS_MSG_ERROR_COPYING_FROM_TMP_DOWNLOAD)
3234                        ? "pop3TmpDownloadError"
3235                        : "pop3MessageWriteError");
3236 
3237     m_pop3ConData->msg_closure = nullptr;
3238   }
3239 
3240   if (!m_pop3ConData->msg_closure)
3241   /* meaning _handle_line read ".\r\n" at end-of-msg */
3242   {
3243     m_pop3ConData->pause_for_read = false;
3244 
3245     if (m_pop3ConData->truncating_cur_msg || m_pop3ConData->leave_on_server) {
3246       Pop3UidlEntry* uidlEntry = NULL;
3247       Pop3MsgInfo* info =
3248           m_pop3ConData->msg_info + m_pop3ConData->last_accessed_msg;
3249 
3250       /* Check for filter actions - FETCH or DELETE */
3251       if ((m_pop3ConData->newuidl) && (info->uidl))
3252         uidlEntry = (Pop3UidlEntry*)PL_HashTableLookup(m_pop3ConData->newuidl,
3253                                                        info->uidl);
3254 
3255       if (uidlEntry && uidlEntry->status == FETCH_BODY &&
3256           m_pop3ConData->truncating_cur_msg) {
3257         /* A filter decided to retrieve this full msg.
3258            Use GetMsg() so the popstate will update correctly,
3259            but don't let this msg get counted twice. */
3260         m_pop3ConData->next_state = POP3_GET_MSG;
3261         m_pop3ConData->real_new_counter--;
3262         /* Make sure we don't try to come through here again. */
3263         PL_HashTableRemove(m_pop3ConData->newuidl, (void*)info->uidl);
3264         put_hash(m_pop3ConData->uidlinfo->hash, info->uidl, FETCH_BODY,
3265                  uidlEntry->dateReceived);
3266 
3267       } else if (uidlEntry && uidlEntry->status == DELETE_CHAR) {
3268         // A filter decided to delete this msg from the server
3269         m_pop3ConData->next_state = POP3_SEND_DELE;
3270       } else {
3271         /* We've retrieved all or part of this message, but we want to
3272            keep it on the server.  Go on to the next message. */
3273         m_pop3ConData->last_accessed_msg++;
3274         m_pop3ConData->next_state = POP3_GET_MSG;
3275       }
3276       if (m_pop3ConData->only_uidl) {
3277         /* GetMsg didn't update this field. Do it now */
3278         uidlEntry = (Pop3UidlEntry*)PL_HashTableLookup(
3279             m_pop3ConData->uidlinfo->hash, m_pop3ConData->only_uidl);
3280         NS_ASSERTION(uidlEntry, "uidl not found in uidlinfo");
3281         if (uidlEntry)
3282           put_hash(m_pop3ConData->uidlinfo->hash, m_pop3ConData->only_uidl,
3283                    KEEP, uidlEntry->dateReceived);
3284       }
3285     } else {
3286       m_pop3ConData->next_state = POP3_SEND_DELE;
3287     }
3288 
3289     /* if we didn't get the whole message add the bytes that we didn't get
3290        to the bytes received part so that the progress percent stays sane.
3291        */
3292     if (m_bytesInMsgReceived < m_pop3ConData->cur_msg_size)
3293       m_totalBytesReceived +=
3294           (m_pop3ConData->cur_msg_size - m_bytesInMsgReceived);
3295   }
3296 
3297   /* set percent done to portion of total bytes of all messages
3298      that we're going to download. */
3299   if (m_totalDownloadSize)
3300     UpdateProgressPercent(m_totalBytesReceived, m_totalDownloadSize);
3301 
3302   PR_Free(line);
3303   return (0);
3304 }
3305 
TopResponse(nsIInputStream * inputStream,uint32_t length)3306 int32_t nsPop3Protocol::TopResponse(nsIInputStream* inputStream,
3307                                     uint32_t length) {
3308   if (TestCapFlag(POP3_TOP_UNDEFINED)) {
3309     ClearCapFlag(POP3_TOP_UNDEFINED);
3310     if (m_pop3ConData->command_succeeded)
3311       SetCapFlag(POP3_HAS_TOP);
3312     else
3313       ClearCapFlag(POP3_HAS_TOP);
3314     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
3315   }
3316 
3317   if (m_pop3ConData->cur_msg_size ==
3318           -1 &&                          /* first line after TOP command sent */
3319       !m_pop3ConData->command_succeeded) /* and TOP command failed */
3320   {
3321     /* TOP doesn't work so we can't retrieve the first part of this msg.
3322     So just go download the whole thing, and warn the user.
3323 
3324       Note that the progress bar will not be accurate in this case.
3325       Oops. #### */
3326     m_pop3ConData->truncating_cur_msg = false;
3327 
3328     nsString statusTemplate;
3329     mLocalBundle->GetStringFromName("pop3ServerDoesNotSupportTopCommand",
3330                                     statusTemplate);
3331     if (!statusTemplate.IsEmpty()) {
3332       nsAutoCString hostName;
3333       nsString statusString;
3334       m_url->GetHost(hostName);
3335 
3336       nsTextFormatter::ssprintf(statusString, statusTemplate.get(),
3337                                 NS_ConvertUTF8toUTF16(hostName).get());
3338       UpdateStatusWithString(statusString.get());
3339     }
3340 
3341     if (m_prefAuthMethods != POP3_HAS_AUTH_USER &&
3342         TestCapFlag(POP3_HAS_XSENDER))
3343       m_pop3ConData->next_state = POP3_SEND_XSENDER;
3344     else
3345       m_pop3ConData->next_state = POP3_SEND_RETR;
3346     return (0);
3347   }
3348 
3349   /* If TOP works, we handle it in the same way as RETR. */
3350   return RetrResponse(inputStream, length);
3351 }
3352 
3353 /* line is handed over as null-terminated string with MSG_LINEBREAK */
HandleLine(char * line,uint32_t line_length)3354 nsresult nsPop3Protocol::HandleLine(char* line, uint32_t line_length) {
3355   nsresult rv = NS_OK;
3356 
3357   NS_ASSERTION(
3358       m_pop3ConData->msg_closure,
3359       "m_pop3ConData->msg_closure is null in nsPop3Protocol::HandleLine()");
3360   if (!m_pop3ConData->msg_closure) return NS_ERROR_NULL_POINTER;
3361 
3362   if (!m_senderInfo.IsEmpty() && !m_pop3ConData->seenFromHeader) {
3363     if (line_length > 6 && !PL_strncasecmp("From: ", line, 6)) {
3364       m_pop3ConData->seenFromHeader = true;
3365       if (PL_strstr(line, m_senderInfo.get()) == NULL)
3366         m_nsIPop3Sink->SetSenderAuthedFlag(m_pop3ConData->msg_closure, false);
3367     }
3368   }
3369 
3370   // line contains only a single dot and linebreak -> message end
3371   if (line_length == 1 + MSG_LINEBREAK_LEN && line[0] == '.') {
3372     m_pop3ConData->assumed_end = true; /* in case byte count from server is */
3373                                        /* wrong, mark we may have had the end */
3374     if (!m_pop3ConData->dot_fix || m_pop3ConData->truncating_cur_msg ||
3375         (m_pop3ConData->parsed_bytes >= (m_pop3ConData->pop3_size - 3))) {
3376       nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
3377       nsCOMPtr<nsIMsgWindow> msgWindow;
3378       if (NS_SUCCEEDED(rv))
3379         rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
3380       rv = m_nsIPop3Sink->IncorporateComplete(
3381           msgWindow,
3382           m_pop3ConData->truncating_cur_msg ? m_pop3ConData->cur_msg_size : 0);
3383 
3384       // The following was added to prevent the loss of Data when we try
3385       // and write to somewhere we don't have write access error to (See
3386       // bug 62480)
3387       // (Note: This is only a temp hack until the underlying XPCOM is
3388       // fixed to return errors)
3389 
3390       if (NS_FAILED(rv)) {
3391         Error((rv == NS_MSG_ERROR_COPYING_FROM_TMP_DOWNLOAD)
3392                   ? "pop3TmpDownloadError"
3393                   : "pop3MessageWriteError");
3394         return rv;
3395       }
3396 
3397       m_pop3ConData->msg_closure = nullptr;
3398       return rv;
3399     }
3400   }
3401   /* Check if the line begins with the termination octet. If so
3402      and if another termination octet follows, we step over the
3403      first occurrence of it. */
3404   else if (line_length > 1 && line[0] == '.' && line[1] == '.') {
3405     line++;
3406     line_length--;
3407   }
3408 
3409   return m_nsIPop3Sink->IncorporateWrite(line, line_length);
3410 }
3411 
SendDele()3412 int32_t nsPop3Protocol::SendDele() {
3413   /* increment the last accessed message since we have now read it
3414    */
3415   char* cmd = PR_smprintf(
3416       "DELE %ld" CRLF,
3417       m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum);
3418   m_pop3ConData->last_accessed_msg++;
3419   int32_t status = -1;
3420   if (cmd) {
3421     m_pop3ConData->next_state_after_response = POP3_DELE_RESPONSE;
3422     status = Pop3SendData(cmd);
3423   }
3424   PR_Free(cmd);
3425   return status;
3426 }
3427 
DeleResponse()3428 int32_t nsPop3Protocol::DeleResponse() {
3429   Pop3UidlHost* host = NULL;
3430 
3431   host = m_pop3ConData->uidlinfo;
3432 
3433   /* the return from the delete will come here
3434    */
3435   if (!m_pop3ConData->command_succeeded) return Error("pop3DeleFailure");
3436 
3437   /*  ###chrisf
3438   the delete succeeded.  Write out state so that we
3439   keep track of all the deletes which have not yet been
3440   committed on the server.  Flush this state upon successful
3441   QUIT.
3442 
3443   We will do this by adding each successfully deleted message id
3444   to a list which we will write out to popstate.dat in
3445   net_pop3_write_state().
3446   */
3447   if (host) {
3448     if (m_pop3ConData->msg_info &&
3449         m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg - 1].uidl) {
3450       if (m_pop3ConData->newuidl)
3451         if (m_pop3ConData->leave_on_server) {
3452           PL_HashTableRemove(
3453               m_pop3ConData->newuidl,
3454               (void*)m_pop3ConData
3455                   ->msg_info[m_pop3ConData->last_accessed_msg - 1]
3456                   .uidl);
3457         } else {
3458           put_hash(m_pop3ConData->newuidl,
3459                    m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg - 1]
3460                        .uidl,
3461                    DELETE_CHAR, 0);
3462           /* kill message in new hash table */
3463         }
3464       else
3465         PL_HashTableRemove(
3466             host->hash,
3467             (void*)m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg - 1]
3468                 .uidl);
3469     }
3470   }
3471 
3472   m_pop3ConData->next_state = POP3_GET_MSG;
3473   m_pop3ConData->pause_for_read = false;
3474 
3475   return (0);
3476 }
3477 
CommitState(bool remove_last_entry)3478 int32_t nsPop3Protocol::CommitState(bool remove_last_entry) {
3479   // only use newuidl if we successfully finished looping through all the
3480   // messages in the inbox.
3481   if (m_pop3ConData->newuidl) {
3482     if (m_pop3ConData->last_accessed_msg >= m_pop3ConData->number_of_messages) {
3483       PL_HashTableDestroy(m_pop3ConData->uidlinfo->hash);
3484       m_pop3ConData->uidlinfo->hash = m_pop3ConData->newuidl;
3485       m_pop3ConData->newuidl = nullptr;
3486     } else {
3487       /* If we are leaving messages on the server, pull out the last
3488         uidl from the hash, because it might have been put in there before
3489         we got it into the database.
3490       */
3491       if (remove_last_entry && m_pop3ConData->msg_info &&
3492           !m_pop3ConData->only_uidl && m_pop3ConData->newuidl->nentries > 0) {
3493         Pop3MsgInfo* info =
3494             m_pop3ConData->msg_info + m_pop3ConData->last_accessed_msg;
3495         if (info && info->uidl) {
3496           mozilla::DebugOnly<bool> val =
3497               PL_HashTableRemove(m_pop3ConData->newuidl, info->uidl);
3498           NS_ASSERTION(val, "uidl not in hash table");
3499         }
3500       }
3501 
3502       // Add the entries in newuidl to m_pop3ConData->uidlinfo->hash to keep
3503       // track of the messages we *did* download in this session.
3504       PL_HashTableEnumerateEntries(m_pop3ConData->newuidl,
3505                                    net_pop3_copy_hash_entries,
3506                                    (void*)m_pop3ConData->uidlinfo->hash);
3507     }
3508   }
3509 
3510   if (!m_pop3ConData->only_check_for_new_mail) {
3511     nsresult rv;
3512     nsCOMPtr<nsIFile> mailDirectory;
3513 
3514     // get the mail directory
3515     nsCOMPtr<nsIMsgIncomingServer> server =
3516         do_QueryInterface(m_pop3Server, &rv);
3517     if (NS_FAILED(rv)) return -1;
3518 
3519     rv = server->GetLocalPath(getter_AddRefs(mailDirectory));
3520     if (NS_FAILED(rv)) return -1;
3521 
3522     // write the state in the mail directory
3523     net_pop3_write_state(m_pop3ConData->uidlinfo, mailDirectory.get());
3524   }
3525   return 0;
3526 }
3527 
3528 /* NET_process_Pop3  will control the state machine that
3529  * loads messages from a pop3 server
3530  *
3531  * returns negative if the transfer is finished or error'd out
3532  *
3533  * returns zero or more if the transfer needs to be continued.
3534  */
ProcessProtocolState(nsIURI * url,nsIInputStream * aInputStream,uint64_t sourceOffset,uint32_t aLength)3535 nsresult nsPop3Protocol::ProcessProtocolState(nsIURI* url,
3536                                               nsIInputStream* aInputStream,
3537                                               uint64_t sourceOffset,
3538                                               uint32_t aLength) {
3539   int32_t status = 0;
3540   bool urlStatusSet = false;
3541   nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_url);
3542 
3543   MOZ_LOG(POP3LOGMODULE, LogLevel::Info,
3544           (POP3LOG("Entering NET_ProcessPop3 %d"), aLength));
3545 
3546   m_pop3ConData->pause_for_read = false; /* already paused; reset */
3547 
3548   if (m_username.IsEmpty()) {
3549     // net_pop3_block = false;
3550     Error("pop3UsernameUndefined");
3551     return NS_MSG_SERVER_USERNAME_MISSING;
3552   }
3553 
3554   while (!m_pop3ConData->pause_for_read) {
3555     MOZ_LOG(POP3LOGMODULE, LogLevel::Info,
3556             (POP3LOG("Entering state: %d"), m_pop3ConData->next_state));
3557 
3558     switch (m_pop3ConData->next_state) {
3559       case POP3_READ_PASSWORD:
3560         // This is a separate state so that we're waiting for the user to type
3561         // in a password while we don't actually have a connection to the pop
3562         // server open; this saves us from having to worry about the server
3563         // timing out on us while we wait for user input.
3564         if (NS_FAILED(StartGetAsyncPassword(POP3_OBTAIN_PASSWORD_EARLY)))
3565           status = -1;
3566         break;
3567       case POP3_FINISH_OBTAIN_PASSWORD_EARLY: {
3568         if (m_passwordResult.IsEmpty() || m_username.IsEmpty()) {
3569           status = MK_POP3_PASSWORD_UNDEFINED;
3570           m_pop3ConData->biffstate = nsIMsgFolder::nsMsgBiffState_Unknown;
3571           m_nsIPop3Sink->SetBiffStateAndUpdateFE(m_pop3ConData->biffstate, 0,
3572                                                  false);
3573 
3574           /* update old style biff */
3575           m_pop3ConData->next_state = POP3_FREE;
3576           m_pop3ConData->pause_for_read = false;
3577           break;
3578         }
3579 
3580         m_pop3ConData->pause_for_read = false;
3581         // we are already connected so just go on and send the username
3582         if (m_prefAuthMethods == POP3_HAS_AUTH_USER) {
3583           m_currentAuthMethod = POP3_HAS_AUTH_USER;
3584           m_pop3ConData->next_state = POP3_SEND_USERNAME;
3585         } else {
3586           if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED))
3587             m_pop3ConData->next_state = POP3_SEND_AUTH;
3588           else
3589             m_pop3ConData->next_state = POP3_SEND_CAPA;
3590         }
3591         break;
3592       }
3593 
3594       case POP3_START_CONNECT: {
3595         m_pop3ConData->next_state = POP3_FINISH_CONNECT;
3596         m_pop3ConData->pause_for_read = false;
3597         break;
3598       }
3599 
3600       case POP3_FINISH_CONNECT: {
3601         m_pop3ConData->pause_for_read = false;
3602         m_pop3ConData->next_state = POP3_WAIT_FOR_START_OF_CONNECTION_RESPONSE;
3603         break;
3604       }
3605 
3606       case POP3_WAIT_FOR_RESPONSE:
3607         status = WaitForResponse(aInputStream, aLength);
3608         break;
3609 
3610       case POP3_WAIT_FOR_START_OF_CONNECTION_RESPONSE: {
3611         status = WaitForStartOfConnectionResponse(aInputStream, aLength);
3612 
3613         if (status) {
3614           if (m_prefAuthMethods == POP3_HAS_AUTH_USER) {
3615             m_currentAuthMethod = POP3_HAS_AUTH_USER;
3616             m_pop3ConData->next_state = POP3_SEND_USERNAME;
3617           } else {
3618             if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED))
3619               m_pop3ConData->next_state = POP3_SEND_AUTH;
3620             else
3621               m_pop3ConData->next_state = POP3_SEND_CAPA;
3622           }
3623         }
3624 
3625         break;
3626       }
3627 
3628       case POP3_SEND_AUTH:
3629         status = SendAuth();
3630         break;
3631 
3632       case POP3_AUTH_RESPONSE:
3633         status = AuthResponse(aInputStream, aLength);
3634         break;
3635 
3636       case POP3_SEND_CAPA:
3637         status = SendCapa();
3638         break;
3639 
3640       case POP3_CAPA_RESPONSE:
3641         status = CapaResponse(aInputStream, aLength);
3642         break;
3643 
3644       case POP3_TLS_RESPONSE:
3645         status = SendTLSResponse();
3646         break;
3647 
3648       case POP3_PROCESS_AUTH:
3649         status = ProcessAuth();
3650         break;
3651 
3652       case POP3_NEXT_AUTH_STEP:
3653         status = NextAuthStep();
3654         break;
3655 
3656       case POP3_AUTH_LOGIN:
3657         status = AuthLogin();
3658         break;
3659 
3660       case POP3_AUTH_LOGIN_RESPONSE:
3661         status = AuthLoginResponse();
3662         break;
3663 
3664       case POP3_AUTH_NTLM:
3665         status = AuthNtlm();
3666         break;
3667 
3668       case POP3_AUTH_NTLM_RESPONSE:
3669         status = AuthNtlmResponse();
3670         break;
3671 
3672       case POP3_AUTH_GSSAPI:
3673         status = AuthGSSAPI();
3674         break;
3675 
3676       case POP3_AUTH_GSSAPI_FIRST:
3677         UpdateStatus("hostContact");
3678         status = AuthGSSAPIResponse(true);
3679         break;
3680 
3681       case POP3_AUTH_GSSAPI_STEP:
3682         status = AuthGSSAPIResponse(false);
3683         break;
3684 
3685       case POP3_AUTH_OAUTH2_AUTH_STEP:
3686         status = OAuth2AuthStep();
3687         break;
3688 
3689       case POP3_AUTH_OAUTH2_RESPONSE:
3690         status = AuthOAuth2Response();
3691         break;
3692 
3693       case POP3_SEND_USERNAME:
3694         if (NS_FAILED(
3695                 StartGetAsyncPassword(POP3_OBTAIN_PASSWORD_BEFORE_USERNAME)))
3696           status = -1;
3697         break;
3698 
3699       case POP3_OBTAIN_PASSWORD_BEFORE_USERNAME:
3700         status = -1;
3701         break;
3702 
3703       case POP3_FINISH_OBTAIN_PASSWORD_BEFORE_USERNAME:
3704         UpdateStatus("hostContact");
3705         status = SendUsername();
3706         break;
3707 
3708       case POP3_SEND_PASSWORD:
3709         if (NS_FAILED(
3710                 StartGetAsyncPassword(POP3_OBTAIN_PASSWORD_BEFORE_PASSWORD)))
3711           status = -1;
3712         break;
3713 
3714       case POP3_FINISH_OBTAIN_PASSWORD_BEFORE_PASSWORD:
3715         status = SendPassword();
3716         break;
3717 
3718       case POP3_SEND_GURL:
3719         status = SendGurl();
3720         break;
3721 
3722       case POP3_GURL_RESPONSE:
3723         status = GurlResponse();
3724         break;
3725 
3726       case POP3_SEND_STAT:
3727         status = SendStat();
3728         break;
3729 
3730       case POP3_GET_STAT:
3731         status = GetStat();
3732         break;
3733 
3734       case POP3_SEND_LIST:
3735         status = SendList();
3736         break;
3737 
3738       case POP3_GET_LIST:
3739         status = GetList(aInputStream, aLength);
3740         break;
3741 
3742       case POP3_SEND_UIDL_LIST:
3743         status = SendUidlList();
3744         break;
3745 
3746       case POP3_GET_UIDL_LIST:
3747         status = GetUidlList(aInputStream, aLength);
3748         break;
3749 
3750       case POP3_SEND_XTND_XLST_MSGID:
3751         status = SendXtndXlstMsgid();
3752         break;
3753 
3754       case POP3_GET_XTND_XLST_MSGID:
3755         status = GetXtndXlstMsgid(aInputStream, aLength);
3756         break;
3757 
3758       case POP3_GET_MSG:
3759         status = GetMsg();
3760         break;
3761 
3762       case POP3_SEND_TOP:
3763         status = SendTop();
3764         break;
3765 
3766       case POP3_TOP_RESPONSE:
3767         status = TopResponse(aInputStream, aLength);
3768         break;
3769 
3770       case POP3_SEND_XSENDER:
3771         status = SendXsender();
3772         break;
3773 
3774       case POP3_XSENDER_RESPONSE:
3775         status = XsenderResponse();
3776         break;
3777 
3778       case POP3_SEND_RETR:
3779         status = SendRetr();
3780         break;
3781 
3782       case POP3_RETR_RESPONSE:
3783         status = RetrResponse(aInputStream, aLength);
3784         break;
3785 
3786       case POP3_SEND_DELE:
3787         status = SendDele();
3788         break;
3789 
3790       case POP3_DELE_RESPONSE:
3791         status = DeleResponse();
3792         break;
3793 
3794       case POP3_SEND_QUIT:
3795         /* attempt to send a server quit command.  Since this means
3796         everything went well, this is a good time to update the
3797         status file and the FE's biff state.
3798           */
3799         if (!m_pop3ConData->only_uidl) {
3800           /* update old style biff */
3801           if (!m_pop3ConData->only_check_for_new_mail) {
3802             /* We don't want to pop up a warning message any more (see
3803               bug 54116), so instead we put the "no new messages" or
3804               "retrieved x new messages"
3805               in the status line.  Unfortunately, this tends to be running
3806               in a progress pane, so we try to get the real pane and
3807               show the message there. */
3808 
3809             if (m_totalDownloadSize <= 0) {
3810               UpdateStatus("noNewMessages");
3811               /* There are no new messages.  */
3812             } else {
3813               nsString statusString;
3814               nsresult rv = FormatCounterString(
3815                   u"receivedMsgs"_ns, m_pop3ConData->real_new_counter - 1,
3816                   m_pop3ConData->really_new_messages, statusString);
3817               if (NS_SUCCEEDED(rv)) UpdateStatusWithString(statusString.get());
3818             }
3819           }
3820         }
3821 
3822         status = Pop3SendData("QUIT" CRLF);
3823         m_pop3ConData->next_state = POP3_WAIT_FOR_RESPONSE;
3824         m_pop3ConData->next_state_after_response = POP3_QUIT_RESPONSE;
3825         break;
3826 
3827       case POP3_QUIT_RESPONSE:
3828         if (m_pop3ConData->command_succeeded) {
3829           /*  the QUIT succeeded.  We can now flush the state in popstate.dat
3830             which keeps track of any uncommitted DELE's */
3831 
3832           /* clear the hash of all our uncommitted deletes */
3833           if (!m_pop3ConData->leave_on_server && m_pop3ConData->newuidl) {
3834             PL_HashTableEnumerateEntries(m_pop3ConData->newuidl,
3835                                          net_pop3_remove_messages_marked_delete,
3836                                          (void*)m_pop3ConData);
3837           }
3838           m_pop3ConData->next_state = POP3_DONE;
3839         } else {
3840           m_pop3ConData->next_state = POP3_ERROR_DONE;
3841         }
3842         break;
3843 
3844       case POP3_DONE:
3845         CommitState(false);
3846         m_pop3ConData->urlStatus = NS_OK;
3847         urlStatusSet = true;
3848         m_pop3ConData->next_state = POP3_FREE;
3849         break;
3850 
3851       case POP3_ERROR_DONE:
3852         /*  write out the state */
3853         if (m_pop3ConData->list_done) CommitState(true);
3854 
3855         if (m_pop3ConData->msg_closure) {
3856           m_nsIPop3Sink->IncorporateAbort(m_pop3ConData->only_uidl != nullptr);
3857           m_pop3ConData->msg_closure = NULL;
3858           m_nsIPop3Sink->AbortMailDelivery(this);
3859         }
3860 
3861         if (m_pop3ConData->msg_del_started) {
3862           nsString statusString;
3863           nsresult rv = FormatCounterString(
3864               u"receivedMsgs"_ns, m_pop3ConData->real_new_counter - 1,
3865               m_pop3ConData->really_new_messages, statusString);
3866           if (NS_SUCCEEDED(rv)) UpdateStatusWithString(statusString.get());
3867 
3868           NS_ASSERTION(!TestFlag(POP3_PASSWORD_FAILED),
3869                        "POP3_PASSWORD_FAILED set when del_started");
3870           m_nsIPop3Sink->AbortMailDelivery(this);
3871         }
3872         {  // this brace is to avoid compiler error about vars in switch case.
3873           nsCOMPtr<nsIMsgWindow> msgWindow;
3874 
3875           if (mailnewsurl) mailnewsurl->GetMsgWindow(getter_AddRefs(msgWindow));
3876           // no msgWindow means no re-prompt, so treat as error.
3877           if (TestFlag(POP3_PASSWORD_FAILED) && msgWindow) {
3878             // We get here because the password was wrong.
3879             if (!m_socketIsOpen && mailnewsurl) {
3880               // The server dropped the connection, so we're going
3881               // to re-run the url.
3882               MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
3883                       (POP3LOG("need to rerun url because connection dropped "
3884                                "during auth")));
3885               m_needToRerunUrl = true;
3886               return NS_OK;
3887             }
3888             m_pop3ConData->next_state = POP3_READ_PASSWORD;
3889             m_pop3ConData->command_succeeded = true;
3890             status = 0;
3891           } else {
3892             /* Else we got a "real" error, so finish up. */
3893             m_pop3ConData->next_state = POP3_FREE;
3894             // If we don't have a specific error already, set a generic one.
3895             if (NS_SUCCEEDED(m_pop3ConData->urlStatus)) {
3896               m_pop3ConData->urlStatus = NS_ERROR_FAILURE;
3897             }
3898             urlStatusSet = true;
3899             m_pop3ConData->pause_for_read = false;
3900           }
3901         }
3902         break;
3903 
3904       case POP3_FREE: {
3905         UpdateProgressPercent(0, 0);  // clear out the progress meter
3906         nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
3907         if (server) {
3908           MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
3909                   (POP3LOG("Clearing server busy in POP3_FREE")));
3910           server->SetServerBusy(false);  // the server is now not busy
3911         }
3912         MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
3913                 (POP3LOG("Clearing running protocol in POP3_FREE")));
3914         CloseSocket();
3915         m_pop3Server->SetRunningProtocol(nullptr);
3916         if (mailnewsurl && urlStatusSet)
3917           mailnewsurl->SetUrlState(false, m_pop3ConData->urlStatus);
3918 
3919         m_url = nullptr;
3920         return NS_OK;
3921       }
3922       default:
3923         NS_ERROR(
3924             "Got to unexpected state in nsPop3Protocol::ProcessProtocolState");
3925         status = -1;
3926     } /* end switch */
3927 
3928     if ((status < 0) && m_pop3ConData->next_state != POP3_FREE) {
3929       m_pop3ConData->pause_for_read = false;
3930       m_pop3ConData->next_state = POP3_ERROR_DONE;
3931     }
3932 
3933   } /* end while */
3934 
3935   return NS_OK;
3936 }
3937 
MarkMessages(nsTArray<Pop3UidlEntry * > * aUIDLArray)3938 NS_IMETHODIMP nsPop3Protocol::MarkMessages(
3939     nsTArray<Pop3UidlEntry*>* aUIDLArray) {
3940   NS_ENSURE_ARG_POINTER(aUIDLArray);
3941   uint32_t count = aUIDLArray->Length();
3942 
3943   for (uint32_t i = 0; i < count; i++) {
3944     bool changed;
3945     if (m_pop3ConData->newuidl)
3946       MarkMsgInHashTable(m_pop3ConData->newuidl, aUIDLArray->ElementAt(i),
3947                          &changed);
3948     if (m_pop3ConData->uidlinfo)
3949       MarkMsgInHashTable(m_pop3ConData->uidlinfo->hash,
3950                          aUIDLArray->ElementAt(i), &changed);
3951   }
3952   return NS_OK;
3953 }
3954 
CheckMessage(const char * aUidl,bool * aBool)3955 NS_IMETHODIMP nsPop3Protocol::CheckMessage(const char* aUidl, bool* aBool) {
3956   Pop3UidlEntry* uidlEntry = nullptr;
3957 
3958   if (aUidl) {
3959     if (m_pop3ConData->newuidl)
3960       uidlEntry =
3961           (Pop3UidlEntry*)PL_HashTableLookup(m_pop3ConData->newuidl, aUidl);
3962     else if (m_pop3ConData->uidlinfo)
3963       uidlEntry = (Pop3UidlEntry*)PL_HashTableLookup(
3964           m_pop3ConData->uidlinfo->hash, aUidl);
3965   }
3966 
3967   *aBool = uidlEntry ? true : false;
3968   return NS_OK;
3969 }
3970 
3971 /* Function for finding an APOP Timestamp and simple check
3972    it for its validity. If returning NS_OK m_ApopTimestamp
3973    contains the validated substring of m_commandResponse. */
GetApopTimestamp()3974 nsresult nsPop3Protocol::GetApopTimestamp() {
3975   int32_t startMark = m_commandResponse.Length(), endMark = -1;
3976 
3977   while (true) {
3978     // search for previous <
3979     if ((startMark = m_commandResponse.RFindChar('<', startMark - 1)) < 0)
3980       return NS_ERROR_FAILURE;
3981 
3982     // search for next >
3983     if ((endMark = m_commandResponse.FindChar('>', startMark)) < 0) continue;
3984 
3985     // look for an @ between start and end as a raw test
3986     int32_t at = m_commandResponse.FindChar('@', startMark);
3987     if (at < 0 || at >= endMark) continue;
3988 
3989     // now test if sub only consists of chars in ASCII range
3990     nsCString sub(
3991         Substring(m_commandResponse, startMark, endMark - startMark + 1));
3992     if (NS_IsAscii(sub.get())) {
3993       // set m_ApopTimestamp to the validated substring
3994       m_ApopTimestamp.Assign(sub);
3995       break;
3996     }
3997   }
3998 
3999   return NS_OK;
4000 }
4001