diff options
Diffstat (limited to 'mailnews/compose/src/nsSmtpProtocol.cpp')
-rw-r--r-- | mailnews/compose/src/nsSmtpProtocol.cpp | 2249 |
1 files changed, 2249 insertions, 0 deletions
diff --git a/mailnews/compose/src/nsSmtpProtocol.cpp b/mailnews/compose/src/nsSmtpProtocol.cpp new file mode 100644 index 000000000..d525d3f7f --- /dev/null +++ b/mailnews/compose/src/nsSmtpProtocol.cpp @@ -0,0 +1,2249 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" +#include "nsSmtpProtocol.h" +#include "nscore.h" +#include "nsIStreamListener.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsISocketTransport.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsMsgBaseCID.h" +#include "nsMsgCompCID.h" +#include "nsIPrompt.h" +#include "nsIAuthPrompt.h" +#include "nsStringGlue.h" +#include "nsTextFormatter.h" +#include "nsIMsgIdentity.h" +#include "nsISmtpServer.h" +#include "prtime.h" +#include "mozilla/Logging.h" +#include "prerror.h" +#include "prprf.h" +#include "prmem.h" +#include "plbase64.h" +#include "prnetdb.h" +#include "prsystem.h" +#include "nsMsgUtils.h" +#include "nsIPipe.h" +#include "nsNetUtil.h" +#include "nsIPrefService.h" +#include "nsISSLSocketControl.h" +#include "nsComposeStrings.h" +#include "nsIStringBundle.h" +#include "nsMsgCompUtils.h" +#include "nsIMsgWindow.h" +#include "MailNewsTypes2.h" // for nsMsgSocketType and nsMsgAuthMethod +#include "nsIIDNService.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "mozilla/Services.h" +#include "mozilla/Attributes.h" +#include "nsINetAddr.h" +#include "nsIProxyInfo.h" + +#ifndef XP_UNIX +#include <stdarg.h> +#endif /* !XP_UNIX */ + +#undef PostMessage // avoid to collision with WinUser.h + +static PRLogModuleInfo *SMTPLogModule = nullptr; + +using namespace mozilla::mailnews; + +/* the output_buffer_size must be larger than the largest possible line + * 2000 seems good for news + * + * jwz: I increased this to 4k since it must be big enough to hold the + * entire button-bar HTML, and with the new "mailto" format, that can + * contain arbitrarily long header fields like "references". + * + * fortezza: proxy auth is huge, buffer increased to 8k (sigh). + */ +#define OUTPUT_BUFFER_SIZE (4096*2) + +//////////////////////////////////////////////////////////////////////////////////////////// +// TEMPORARY HARD CODED FUNCTIONS +/////////////////////////////////////////////////////////////////////////////////////////// + +/* based on in NET_ExplainErrorDetails in mkmessag.c */ +nsresult nsExplainErrorDetails(nsISmtpUrl * aSmtpUrl, nsresult aCode, ...) +{ + NS_ENSURE_ARG(aSmtpUrl); + + va_list args; + + nsCOMPtr<nsIPrompt> dialog; + aSmtpUrl->GetPrompt(getter_AddRefs(dialog)); + NS_ENSURE_TRUE(dialog, NS_ERROR_FAILURE); + + char16_t * msg; + nsString eMsg; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + nsresult rv = bundleService->CreateBundle( + "chrome://messenger/locale/messengercompose/composeMsgs.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + va_start (args, aCode); + + const char16_t* exitString; +#ifdef __GNUC__ +// Temporary workaroung until bug 783526 is fixed. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" +#endif + switch (aCode) + { + case NS_ERROR_ILLEGAL_LOCALPART: + bundle->GetStringFromName(u"errorIllegalLocalPart", + getter_Copies(eMsg)); + msg = nsTextFormatter::vsmprintf(eMsg.get(), args); + break; + case NS_ERROR_SMTP_SERVER_ERROR: + case NS_ERROR_TCP_READ_ERROR: + case NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED: + case NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_1: + case NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2: + case NS_ERROR_SENDING_FROM_COMMAND: + case NS_ERROR_SENDING_RCPT_COMMAND: + case NS_ERROR_SENDING_DATA_COMMAND: + case NS_ERROR_SENDING_MESSAGE: + case NS_ERROR_SMTP_GREETING: + exitString = errorStringNameForErrorCode(aCode); + bundle->GetStringFromName(exitString, getter_Copies(eMsg)); + msg = nsTextFormatter::vsmprintf(eMsg.get(), args); + break; + default: + NS_WARNING("falling to default error code"); + bundle->GetStringFromName(u"communicationsError", getter_Copies(eMsg)); + msg = nsTextFormatter::smprintf(eMsg.get(), aCode); + break; + } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + if (msg) + { + rv = dialog->Alert(nullptr, msg); + nsTextFormatter::smprintf_free(msg); + } + + va_end (args); + + return rv; +} + +/* RFC 1891 -- extended smtp value encoding scheme + + 5. Additional parameters for RCPT and MAIL commands + + The extended RCPT and MAIL commands are issued by a client when it wishes to request a DSN from the + server, under certain conditions, for a particular recipient. The extended RCPT and MAIL commands are + identical to the RCPT and MAIL commands defined in [1], except that one or more of the following parameters + appear after the sender or recipient address, respectively. The general syntax for extended SMTP commands is + defined in [4]. + + NOTE: Although RFC 822 ABNF is used to describe the syntax of these parameters, they are not, in the + language of that document, "structured field bodies". Therefore, while parentheses MAY appear within an + emstp-value, they are not recognized as comment delimiters. + + The syntax for "esmtp-value" in [4] does not allow SP, "=", control characters, or characters outside the + traditional ASCII range of 1- 127 decimal to be transmitted in an esmtp-value. Because the ENVID and + ORCPT parameters may need to convey values outside this range, the esmtp-values for these parameters are + encoded as "xtext". "xtext" is formally defined as follows: + + xtext = *( xchar / hexchar ) + + xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive, except for "+" and "=". + + ; "hexchar"s are intended to encode octets that cannot appear + ; as ASCII characters within an esmtp-value. + + hexchar = ASCII "+" immediately followed by two upper case hexadecimal digits + + When encoding an octet sequence as xtext: + + + Any ASCII CHAR between "!" and "~" inclusive, except for "+" and "=", + MAY be encoded as itself. (A CHAR in this range MAY instead be encoded as a "hexchar", at the + implementor's discretion.) + + + ASCII CHARs that fall outside the range above must be encoded as + "hexchar". + + */ +/* caller must free the return buffer */ +static char * +esmtp_value_encode(const char *addr) +{ + char *buffer = (char *) PR_Malloc(512); /* esmtp ORCPT allow up to 500 chars encoded addresses */ + char *bp = buffer, *bpEnd = buffer+500; + int len, i; + + if (!buffer) return NULL; + + *bp=0; + if (! addr || *addr == 0) /* this will never happen */ + return buffer; + + for (i=0, len=PL_strlen(addr); i < len && bp < bpEnd; i++) + { + if (*addr >= 0x21 && + *addr <= 0x7E && + *addr != '+' && + *addr != '=') + { + *bp++ = *addr++; + } + else + { + PR_snprintf(bp, bpEnd-bp, "+%.2X", ((int)*addr++)); + bp += PL_strlen(bp); + } + } + *bp=0; + return buffer; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// END OF TEMPORARY HARD CODED FUNCTIONS +/////////////////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS_INHERITED(nsSmtpProtocol, nsMsgAsyncWriteProtocol, + msgIOAuth2ModuleListener) + +nsSmtpProtocol::nsSmtpProtocol(nsIURI * aURL) + : nsMsgAsyncWriteProtocol(aURL) +{ +} + +nsSmtpProtocol::~nsSmtpProtocol() +{ + // free our local state + PR_Free(m_dataBuf); + delete m_lineStreamBuffer; +} + +void nsSmtpProtocol::Initialize(nsIURI * aURL) +{ + NS_PRECONDITION(aURL, "invalid URL passed into Smtp Protocol"); + nsresult rv = NS_OK; + + m_flags = 0; + m_prefAuthMethods = 0; + m_failedAuthMethods = 0; + m_currentAuthMethod = 0; + m_usernamePrompted = false; + m_prefSocketType = nsMsgSocketType::trySTARTTLS; + m_tlsInitiated = false; + + m_urlErrorState = NS_ERROR_FAILURE; + + if (!SMTPLogModule) + SMTPLogModule = PR_NewLogModule("SMTP"); + + if (aURL) + m_runningURL = do_QueryInterface(aURL); + + // extract out message feedback if there is any. + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aURL); + if (mailnewsUrl) + mailnewsUrl->GetStatusFeedback(getter_AddRefs(m_statusFeedback)); + + m_dataBuf = (char *) PR_Malloc(sizeof(char) * OUTPUT_BUFFER_SIZE); + m_dataBufSize = OUTPUT_BUFFER_SIZE; + + m_nextState = SMTP_START_CONNECT; + m_nextStateAfterResponse = SMTP_START_CONNECT; + m_responseCode = 0; + m_previousResponseCode = 0; + m_continuationResponse = -1; + m_tlsEnabled = false; + m_addressesLeft = 0; + + m_sendDone = false; + + m_sizelimit = 0; + m_totalMessageSize = 0; + nsCOMPtr<nsIFile> file; + m_runningURL->GetPostMessageFile(getter_AddRefs(file)); + if (file) + file->GetFileSize(&m_totalMessageSize); + + m_originalContentLength = 0; + m_totalAmountRead = 0; + + m_lineStreamBuffer = new nsMsgLineStreamBuffer(OUTPUT_BUFFER_SIZE, true); + // ** may want to consider caching the server capability to save lots of + // round trip communication between the client and server + int32_t authMethod = 0; + nsCOMPtr<nsISmtpServer> smtpServer; + m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer)); + if (smtpServer) { + smtpServer->GetAuthMethod(&authMethod); + smtpServer->GetSocketType(&m_prefSocketType); + smtpServer->GetHelloArgument(getter_Copies(m_helloArgument)); + + // Query for OAuth2 support. If the SMTP server preferences don't allow + // for OAuth2, then don't carry around the OAuth2 module any longer + // since we won't need it. + mOAuth2Support = do_CreateInstance(MSGIOAUTH2MODULE_CONTRACTID); + if (mOAuth2Support) + { + bool supportsOAuth = false; + mOAuth2Support->InitFromSmtp(smtpServer, &supportsOAuth); + if (!supportsOAuth) + mOAuth2Support = nullptr; + } + } + InitPrefAuthMethods(authMethod); + + nsAutoCString hostName; + int32_t port = 0; + + aURL->GetPort(&port); + aURL->GetAsciiHost(hostName); + + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, ("SMTP Connecting to: %s", hostName.get())); + + // When we are making a secure connection, we need to make sure that we + // pass an interface requestor down to the socket transport so that PSM can + // retrieve a nsIPrompt instance if needed. + nsCOMPtr<nsIInterfaceRequestor> callbacks; + nsCOMPtr<nsISmtpUrl> smtpUrl(do_QueryInterface(aURL)); + if (smtpUrl) + smtpUrl->GetNotificationCallbacks(getter_AddRefs(callbacks)); + + nsCOMPtr<nsIProxyInfo> proxyInfo; + rv = MsgExamineForProxy(this, getter_AddRefs(proxyInfo)); + if (NS_FAILED(rv)) proxyInfo = nullptr; + + if (m_prefSocketType == nsMsgSocketType::SSL) + rv = OpenNetworkSocketWithInfo(hostName.get(), port, "ssl", proxyInfo, + callbacks); + else if (m_prefSocketType != nsMsgSocketType::plain) + { + rv = OpenNetworkSocketWithInfo(hostName.get(), port, "starttls", + proxyInfo, callbacks); + if (NS_FAILED(rv) && m_prefSocketType == nsMsgSocketType::trySTARTTLS) + { + m_prefSocketType = nsMsgSocketType::plain; + rv = OpenNetworkSocketWithInfo(hostName.get(), port, nullptr, + proxyInfo, callbacks); + } + } + else + rv = OpenNetworkSocketWithInfo(hostName.get(), port, nullptr, proxyInfo, + callbacks); +} + +void nsSmtpProtocol::AppendHelloArgument(nsACString& aResult) +{ + nsresult rv; + + // is a custom EHLO/HELO argument configured for the transport to be used? + if (!m_helloArgument.IsEmpty()) + { + aResult += m_helloArgument; + } + else + { + // is a FQDN known for this system? + char hostName[256]; + PR_GetSystemInfo(PR_SI_HOSTNAME_UNTRUNCATED, hostName, sizeof hostName); + if ((hostName[0] != '\0') && (strchr(hostName, '.') != NULL)) + { + nsDependentCString cleanedHostName(hostName); + // avoid problems with hostnames containing newlines/whitespace + cleanedHostName.StripWhitespace(); + aResult += cleanedHostName; + } + else + { + nsCOMPtr<nsINetAddr> iaddr; // IP address for this connection + // our transport is always a nsISocketTransport + nsCOMPtr<nsISocketTransport> socketTransport = do_QueryInterface(m_transport); + // should return the interface ip of the SMTP connection + // minimum case - see bug 68877 and RFC 2821, chapter 4.1.1.1 + rv = socketTransport->GetScriptableSelfAddr(getter_AddRefs(iaddr)); + + if (NS_SUCCEEDED(rv)) + { + // turn it into a string + nsCString ipAddressString; + rv = iaddr->GetAddress(ipAddressString); + if (NS_SUCCEEDED(rv)) + { +#ifdef DEBUG + bool v4mapped = false; + iaddr->GetIsV4Mapped(&v4mapped); + NS_ASSERTION(!v4mapped, + "unexpected IPv4-mapped IPv6 address"); +#endif + + uint16_t family = nsINetAddr::FAMILY_INET; + iaddr->GetFamily(&family); + + if (family == nsINetAddr::FAMILY_INET6) // IPv6 style address? + aResult.AppendLiteral("[IPv6:"); + else + aResult.AppendLiteral("["); + + aResult.Append(ipAddressString); + aResult.Append(']'); + } + } + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////////// +// we suppport the nsIStreamListener interface +//////////////////////////////////////////////////////////////////////////////////////////// + +// stop binding is a "notification" informing us that the stream +// associated with aURL is going away. +NS_IMETHODIMP nsSmtpProtocol::OnStopRequest(nsIRequest *request, nsISupports *ctxt, + nsresult aStatus) +{ + bool connDroppedDuringAuth = NS_SUCCEEDED(aStatus) && !m_sendDone && + (m_nextStateAfterResponse == SMTP_AUTH_LOGIN_STEP0_RESPONSE || + m_nextStateAfterResponse == SMTP_AUTH_LOGIN_RESPONSE); + // ignore errors handling the QUIT command so fcc can continue. + if (m_sendDone && NS_FAILED(aStatus)) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, + ("SMTP connection error quitting %lx, ignoring ", aStatus)); + aStatus = NS_OK; + } + if (NS_SUCCEEDED(aStatus) && !m_sendDone) { + // if we are getting OnStopRequest() with NS_OK, + // but we haven't finished clean, that's spells trouble. + // it means that the server has dropped us before we could send the whole mail + // for example, see bug #200647 + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, + ("SMTP connection dropped after %ld total bytes read", m_totalAmountRead)); + if (!connDroppedDuringAuth) + nsMsgAsyncWriteProtocol::OnStopRequest(nullptr, ctxt, NS_ERROR_NET_INTERRUPT); + } + else + nsMsgAsyncWriteProtocol::OnStopRequest(nullptr, ctxt, aStatus); + + // okay, we've been told that the send is done and the connection is going away. So + // we need to release all of our state + nsresult rv = nsMsgAsyncWriteProtocol::CloseSocket(); + // If the server dropped the connection when we were expecting + // a login response, reprompt for password, and if the user asks, + // retry the url. + if (connDroppedDuringAuth) + { + nsCOMPtr<nsIURI> runningURI = do_QueryInterface(m_runningURL); + nsresult rv = AuthLoginResponse(nullptr, 0); + if (NS_FAILED(rv)) + return rv; + return LoadUrl(runningURI, ctxt); + } + + return rv; +} + +///////////////////////////////////////////////////////////////////////////////////////////// +// End of nsIStreamListenerSupport +////////////////////////////////////////////////////////////////////////////////////////////// + +void nsSmtpProtocol::UpdateStatus(const char16_t* aStatusName) +{ + if (m_statusFeedback) + { + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + if (!bundleService) return; + nsCOMPtr<nsIStringBundle> bundle; + nsresult rv = bundleService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(bundle)); + if (NS_FAILED(rv)) return; + nsString msg; + bundle->GetStringFromName(aStatusName, getter_Copies(msg)); + UpdateStatusWithString(msg.get()); + } +} + +void nsSmtpProtocol::UpdateStatusWithString(const char16_t * aStatusString) +{ + if (m_statusFeedback && aStatusString) + m_statusFeedback->ShowStatusString(nsDependentString(aStatusString)); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +// Begin protocol state machine functions... +////////////////////////////////////////////////////////////////////////////////////////////// + +/* + * gets the response code from the SMTP server and the + * response line + */ +nsresult nsSmtpProtocol::SmtpResponse(nsIInputStream * inputStream, uint32_t length) +{ + char * line = nullptr; + char cont_char; + uint32_t ln = 0; + bool pauseForMoreData = false; + + if (!m_lineStreamBuffer) + // this will force an error and at least we won't crash + return NS_ERROR_NULL_POINTER; + + line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData); + + if (pauseForMoreData || !line) + { + SetFlag(SMTP_PAUSE_FOR_READ); /* pause */ + PR_Free(line); + return NS_OK; + } + + m_totalAmountRead += ln; + + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, ("SMTP Response: %s", line)); + cont_char = ' '; /* default */ + // sscanf() doesn't update m_responseCode if line doesn't start + // with a number. That can be dangerous. So be sure to set + // m_responseCode to 0 if no items read. + if (PR_sscanf(line, "%d%c", &m_responseCode, &cont_char) <= 0) + m_responseCode = 0; + + if (m_continuationResponse == -1) + { + if (cont_char == '-') /* begin continuation */ + m_continuationResponse = m_responseCode; + + // display the whole message if no valid response code or + // message shorter than 4 chars + m_responseText = (m_responseCode >= 100 && PL_strlen(line) > 3) ? line + 4 : line; + } + else + { /* have to continue */ + if (m_continuationResponse == m_responseCode && cont_char == ' ') + m_continuationResponse = -1; /* ended */ + + if (m_responseText.IsEmpty() || m_responseText.Last() != '\n') + m_responseText += "\n"; + + m_responseText += (PL_strlen(line) > 3) ? line + 4 : line; + } + + if (m_responseCode == 220 && m_responseText.Length() && !m_tlsInitiated && + !m_sendDone) + m_nextStateAfterResponse = SMTP_EXTN_LOGIN_RESPONSE; + + if (m_continuationResponse == -1) /* all done with this response? */ + { + m_nextState = m_nextStateAfterResponse; + ClearFlag(SMTP_PAUSE_FOR_READ); /* don't pause */ + } + + PR_Free(line); + return NS_OK; +} + +nsresult nsSmtpProtocol::ExtensionLoginResponse(nsIInputStream * inputStream, uint32_t length) +{ + nsresult status = NS_OK; + + if (m_responseCode != 220) + { +#ifdef DEBUG + nsresult rv = +#endif + nsExplainErrorDetails(m_runningURL, NS_ERROR_SMTP_GREETING, + m_responseText.get()); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return NS_ERROR_SMTP_AUTH_FAILURE; + } + + nsAutoCString buffer("EHLO "); + AppendHelloArgument(buffer); + buffer += CRLF; + + status = SendData(buffer.get()); + + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_SEND_EHLO_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + + return(status); +} + +nsresult nsSmtpProtocol::SendHeloResponse(nsIInputStream * inputStream, uint32_t length) +{ + nsresult status = NS_OK; + nsAutoCString buffer; + nsresult rv; + + if (m_responseCode != 250) + { +#ifdef DEBUG + rv = +#endif + nsExplainErrorDetails(m_runningURL, NS_ERROR_SMTP_SERVER_ERROR, + m_responseText.get()); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return NS_ERROR_SMTP_AUTH_FAILURE; + } + + // check if we're just verifying the ability to logon + nsCOMPtr<nsISmtpUrl> smtpUrl = do_QueryInterface(m_runningURL, &rv); + NS_ENSURE_SUCCESS(rv, rv); + bool verifyingLogon = false; + smtpUrl->GetVerifyLogon(&verifyingLogon); + if (verifyingLogon) + return SendQuit(); + + // extract the email address from the identity + nsCString emailAddress; + nsCOMPtr <nsIMsgIdentity> senderIdentity; + rv = m_runningURL->GetSenderIdentity(getter_AddRefs(senderIdentity)); + if (NS_FAILED(rv) || !senderIdentity) + { + m_urlErrorState = NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS; + return(NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS); + } + senderIdentity->GetEmail(emailAddress); + + if (emailAddress.IsEmpty()) + { + m_urlErrorState = NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS; + return(NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS); + } + + nsCString fullAddress; + // Quote the email address before passing it to the SMTP server. + MakeMimeAddress(EmptyCString(), emailAddress, fullAddress); + + buffer = "MAIL FROM:<"; + buffer += fullAddress; + buffer += ">"; + + nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv, rv); + + if (TestFlag(SMTP_EHLO_DSN_ENABLED)) + { + bool requestDSN = false; + rv = m_runningURL->GetRequestDSN(&requestDSN); + + if (requestDSN) + { + bool requestRetFull = false; + rv = prefBranch->GetBoolPref("mail.dsn.ret_full_on", &requestRetFull); + + buffer += requestRetFull ? " RET=FULL" : " RET=HDRS"; + + nsCString dsnEnvid; + + // get the envid from the smtpUrl + rv = m_runningURL->GetDsnEnvid(dsnEnvid); + + if (dsnEnvid.IsEmpty()) + dsnEnvid.Adopt(msg_generate_message_id(senderIdentity)); + + buffer += " ENVID="; + buffer += dsnEnvid; + } + } + + if (TestFlag(SMTP_EHLO_8BIT_ENABLED)) + { + bool strictlyMime = false; + rv = prefBranch->GetBoolPref("mail.strictly_mime", &strictlyMime); + + if (!strictlyMime) + buffer.Append(" BODY=8BITMIME"); + } + + if (TestFlag(SMTP_EHLO_SIZE_ENABLED)) + { + buffer.Append(" SIZE="); + buffer.AppendInt(m_totalMessageSize); + } + buffer += CRLF; + + status = SendData(buffer.get()); + + m_nextState = SMTP_RESPONSE; + + + m_nextStateAfterResponse = SMTP_SEND_MAIL_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + + return(status); +} + +nsresult nsSmtpProtocol::SendEhloResponse(nsIInputStream * inputStream, uint32_t length) +{ + nsresult status = NS_OK; + + if (m_responseCode != 250) + { + /* EHLO must not be implemented by the server, so fall back to the HELO case + * if command is unrecognized or unimplemented. + */ + if (m_responseCode == 500 || m_responseCode == 502) + { + /* If STARTTLS is requested by the user, EHLO is required to advertise it. + * But only if TLS handshake is not already accomplished. + */ + if (m_prefSocketType == nsMsgSocketType::alwaysSTARTTLS && + !m_tlsEnabled) + { + m_nextState = SMTP_ERROR_DONE; + m_urlErrorState = NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS; + return(NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS); + } + + nsAutoCString buffer("HELO "); + AppendHelloArgument(buffer); + buffer += CRLF; + + status = SendData(buffer.get()); + + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_SEND_HELO_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + return (status); + } + /* e.g. getting 421 "Server says unauthorized, bye" or + * 501 "Syntax error in EHLOs parameters or arguments" + */ + else + { +#ifdef DEBUG + nsresult rv = +#endif + nsExplainErrorDetails(m_runningURL, NS_ERROR_SMTP_SERVER_ERROR, + m_responseText.get()); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return NS_ERROR_SMTP_AUTH_FAILURE; + } + } + + int32_t responseLength = m_responseText.Length(); + int32_t startPos = 0; + int32_t endPos; + do + { + endPos = m_responseText.FindChar('\n', startPos + 1); + nsAutoCString responseLine; + responseLine.Assign(Substring(m_responseText, startPos, + (endPos >= 0 ? endPos : responseLength) - startPos)); + + MsgCompressWhitespace(responseLine); + if (responseLine.LowerCaseEqualsLiteral("starttls")) + { + SetFlag(SMTP_EHLO_STARTTLS_ENABLED); + } + else if (responseLine.LowerCaseEqualsLiteral("dsn")) + { + SetFlag(SMTP_EHLO_DSN_ENABLED); + } + else if (StringBeginsWith(responseLine, NS_LITERAL_CSTRING("AUTH"), nsCaseInsensitiveCStringComparator())) + { + SetFlag(SMTP_AUTH); + + if (responseLine.Find(NS_LITERAL_CSTRING("GSSAPI"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_GSSAPI_ENABLED); + + if (responseLine.Find(NS_LITERAL_CSTRING("CRAM-MD5"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_CRAM_MD5_ENABLED); + + if (responseLine.Find(NS_LITERAL_CSTRING("NTLM"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_NTLM_ENABLED); + + if (responseLine.Find(NS_LITERAL_CSTRING("MSN"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_MSN_ENABLED); + + if (responseLine.Find(NS_LITERAL_CSTRING("PLAIN"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_PLAIN_ENABLED); + + if (responseLine.Find(NS_LITERAL_CSTRING("LOGIN"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_LOGIN_ENABLED); + + if (responseLine.Find(NS_LITERAL_CSTRING("EXTERNAL"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_EXTERNAL_ENABLED); + + if (responseLine.Find(NS_LITERAL_CSTRING("XOAUTH2"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_OAUTH2_ENABLED); + } + else if (StringBeginsWith(responseLine, NS_LITERAL_CSTRING("SIZE"), nsCaseInsensitiveCStringComparator())) + { + SetFlag(SMTP_EHLO_SIZE_ENABLED); + + m_sizelimit = atol((responseLine.get()) + 4); + } + else if (StringBeginsWith(responseLine, NS_LITERAL_CSTRING("8BITMIME"), nsCaseInsensitiveCStringComparator())) + { + SetFlag(SMTP_EHLO_8BIT_ENABLED); + } + + startPos = endPos + 1; + } while (endPos >= 0); + + if (TestFlag(SMTP_EHLO_SIZE_ENABLED) && + m_sizelimit > 0 && (int32_t)m_totalMessageSize > m_sizelimit) + { +#ifdef DEBUG + nsresult rv = +#endif + nsExplainErrorDetails(m_runningURL, + NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_1, m_sizelimit); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return(NS_ERROR_SENDING_FROM_COMMAND); + } + + m_nextState = SMTP_AUTH_PROCESS_STATE; + return status; +} + + +nsresult nsSmtpProtocol::SendTLSResponse() +{ + // only tear down our existing connection and open a new one if we received a 220 response + // from the smtp server after we issued the STARTTLS + nsresult rv = NS_OK; + if (m_responseCode == 220) + { + nsCOMPtr<nsISupports> secInfo; + nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport, &rv); + if (NS_FAILED(rv)) return rv; + + rv = strans->GetSecurityInfo(getter_AddRefs(secInfo)); + + if (NS_SUCCEEDED(rv) && secInfo) { + nsCOMPtr<nsISSLSocketControl> sslControl = do_QueryInterface(secInfo, &rv); + + if (NS_SUCCEEDED(rv) && sslControl) + rv = sslControl->StartTLS(); + } + + if (NS_SUCCEEDED(rv)) + { + m_nextState = SMTP_EXTN_LOGIN_RESPONSE; + m_nextStateAfterResponse = SMTP_EXTN_LOGIN_RESPONSE; + m_tlsEnabled = true; + m_flags = 0; // resetting the flags + return rv; + } + } + + ClearFlag(SMTP_EHLO_STARTTLS_ENABLED); + m_tlsInitiated = false; + m_nextState = SMTP_AUTH_PROCESS_STATE; + + return rv; +} + +void nsSmtpProtocol::InitPrefAuthMethods(int32_t authMethodPrefValue) +{ + // for m_prefAuthMethods, using the same flags as server capablities. + switch (authMethodPrefValue) + { + case nsMsgAuthMethod::none: + m_prefAuthMethods = SMTP_AUTH_NONE_ENABLED; + break; + //case nsMsgAuthMethod::old -- no such thing for SMTP + case nsMsgAuthMethod::passwordCleartext: + m_prefAuthMethods = SMTP_AUTH_LOGIN_ENABLED | + SMTP_AUTH_PLAIN_ENABLED; + break; + case nsMsgAuthMethod::passwordEncrypted: + m_prefAuthMethods = SMTP_AUTH_CRAM_MD5_ENABLED; + break; + case nsMsgAuthMethod::NTLM: + m_prefAuthMethods = SMTP_AUTH_NTLM_ENABLED | + SMTP_AUTH_MSN_ENABLED; + break; + case nsMsgAuthMethod::GSSAPI: + m_prefAuthMethods = SMTP_AUTH_GSSAPI_ENABLED; + break; + case nsMsgAuthMethod::OAuth2: + m_prefAuthMethods = SMTP_AUTH_OAUTH2_ENABLED; + break; + case nsMsgAuthMethod::secure: + m_prefAuthMethods = SMTP_AUTH_CRAM_MD5_ENABLED | + SMTP_AUTH_GSSAPI_ENABLED | + SMTP_AUTH_NTLM_ENABLED | SMTP_AUTH_MSN_ENABLED | + SMTP_AUTH_EXTERNAL_ENABLED; // TODO: Expose EXTERNAL? How? + break; + default: + NS_ASSERTION(false, "SMTP: authMethod pref invalid"); + // TODO log to error console + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, + ("SMTP: bad pref authMethod = %d\n", authMethodPrefValue)); + // fall to any + MOZ_FALLTHROUGH; + case nsMsgAuthMethod::anything: + m_prefAuthMethods = + SMTP_AUTH_LOGIN_ENABLED | SMTP_AUTH_PLAIN_ENABLED | + SMTP_AUTH_CRAM_MD5_ENABLED | SMTP_AUTH_GSSAPI_ENABLED | + SMTP_AUTH_NTLM_ENABLED | SMTP_AUTH_MSN_ENABLED | + SMTP_AUTH_OAUTH2_ENABLED | + SMTP_AUTH_EXTERNAL_ENABLED; + break; + } + + // Only enable OAuth2 support if we can do the lookup. + if ((m_prefAuthMethods & SMTP_AUTH_OAUTH2_ENABLED) && !mOAuth2Support) + m_prefAuthMethods &= ~SMTP_AUTH_OAUTH2_ENABLED; + + NS_ASSERTION(m_prefAuthMethods != 0, "SMTP:InitPrefAuthMethods() failed"); +} + +/** + * Changes m_currentAuthMethod to pick the next-best one + * which is allowed by server and prefs and not marked failed. + * The order of preference and trying of auth methods is encoded here. + */ +nsresult nsSmtpProtocol::ChooseAuthMethod() +{ + int32_t serverCaps = m_flags; // from nsMsgProtocol::TestFlag() + int32_t availCaps = serverCaps & m_prefAuthMethods & ~m_failedAuthMethods; + + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, + ("SMTP auth: server caps 0x%X, pref 0x%X, failed 0x%X, avail caps 0x%X", + serverCaps, m_prefAuthMethods, m_failedAuthMethods, availCaps)); + MOZ_LOG(SMTPLogModule, mozilla::LogLevel:: Debug, + ("(GSSAPI = 0x%X, CRAM = 0x%X, NTLM = 0x%X, " + "MSN = 0x%X, PLAIN = 0x%X, LOGIN = 0x%X, EXTERNAL = 0x%X)", + SMTP_AUTH_GSSAPI_ENABLED, SMTP_AUTH_CRAM_MD5_ENABLED, + SMTP_AUTH_NTLM_ENABLED, SMTP_AUTH_MSN_ENABLED, SMTP_AUTH_PLAIN_ENABLED, + SMTP_AUTH_LOGIN_ENABLED, SMTP_AUTH_EXTERNAL_ENABLED)); + + if (SMTP_AUTH_GSSAPI_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_GSSAPI_ENABLED; + else if (SMTP_AUTH_CRAM_MD5_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_CRAM_MD5_ENABLED; + else if (SMTP_AUTH_NTLM_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_NTLM_ENABLED; + else if (SMTP_AUTH_MSN_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_MSN_ENABLED; + else if (SMTP_AUTH_OAUTH2_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_OAUTH2_ENABLED; + else if (SMTP_AUTH_PLAIN_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_PLAIN_ENABLED; + else if (SMTP_AUTH_LOGIN_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_LOGIN_ENABLED; + else if (SMTP_AUTH_EXTERNAL_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_EXTERNAL_ENABLED; + else + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("no auth method remaining")); + m_currentAuthMethod = 0; + return NS_ERROR_SMTP_AUTH_FAILURE; + } + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("trying auth method 0x%X", m_currentAuthMethod)); + return NS_OK; +} + +void nsSmtpProtocol::MarkAuthMethodAsFailed(int32_t failedAuthMethod) +{ + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, + ("marking auth method 0x%X failed", failedAuthMethod)); + m_failedAuthMethods |= failedAuthMethod; +} + +/** + * Start over, trying all auth methods again + */ +void nsSmtpProtocol::ResetAuthMethods() +{ + m_currentAuthMethod = 0; + m_failedAuthMethods = 0; +} + +nsresult nsSmtpProtocol::ProcessAuth() +{ + nsresult status = NS_OK; + nsAutoCString buffer; + + if (!m_tlsEnabled) + { + if (TestFlag(SMTP_EHLO_STARTTLS_ENABLED)) + { + // Do not try to combine SMTPS with STARTTLS. + // If nsMsgSocketType::SSL is set, + // we are alrady using a secure connection. + // Do not attempt to do STARTTLS, + // even if server offers it. + if (m_prefSocketType == nsMsgSocketType::trySTARTTLS || + m_prefSocketType == nsMsgSocketType::alwaysSTARTTLS) + { + buffer = "STARTTLS"; + buffer += CRLF; + + status = SendData(buffer.get()); + + m_tlsInitiated = true; + + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_TLS_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + return status; + } + } + else if (m_prefSocketType == nsMsgSocketType::alwaysSTARTTLS) + { + m_nextState = SMTP_ERROR_DONE; + m_urlErrorState = NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS; + return NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS; + } + } + // (wrong indention until here) + + (void) ChooseAuthMethod(); // advance m_currentAuthMethod + + // We don't need to auth, per pref, or the server doesn't advertise AUTH, + // so skip auth and try to send message. + if (m_prefAuthMethods == SMTP_AUTH_NONE_ENABLED || !TestFlag(SMTP_AUTH)) + { + m_nextState = SMTP_SEND_HELO_RESPONSE; + // fake to 250 because SendHeloResponse() tests for this + m_responseCode = 250; + } + else if (m_currentAuthMethod == SMTP_AUTH_EXTERNAL_ENABLED) + { + buffer = "AUTH EXTERNAL ="; + buffer += CRLF; + SendData(buffer.get()); + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_AUTH_EXTERNAL_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + return NS_OK; + } + else if (m_currentAuthMethod == SMTP_AUTH_GSSAPI_ENABLED) + { + m_nextState = SMTP_SEND_AUTH_GSSAPI_FIRST; + } + else if (m_currentAuthMethod == SMTP_AUTH_CRAM_MD5_ENABLED || + m_currentAuthMethod == SMTP_AUTH_PLAIN_ENABLED || + m_currentAuthMethod == SMTP_AUTH_NTLM_ENABLED) + { + m_nextState = SMTP_SEND_AUTH_LOGIN_STEP1; + } + else if (m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED || + m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED) + { + m_nextState = SMTP_SEND_AUTH_LOGIN_STEP0; + } + else if (m_currentAuthMethod == SMTP_AUTH_OAUTH2_ENABLED) + { + m_nextState = SMTP_AUTH_OAUTH2_STEP; + } + else // All auth methods failed + { + // show an appropriate error msg + if (m_failedAuthMethods == 0) + { + // we didn't even try anything, so we had a non-working config: + // pref doesn't match server + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, + ("no working auth mech - pref doesn't match server capas")); + + // pref has encrypted pw & server claims to support plaintext pw + if (m_prefAuthMethods == SMTP_AUTH_CRAM_MD5_ENABLED && + m_flags & (SMTP_AUTH_LOGIN_ENABLED | SMTP_AUTH_PLAIN_ENABLED)) + { + // have SSL + if (m_prefSocketType == nsMsgSocketType::SSL || + m_prefSocketType == nsMsgSocketType::alwaysSTARTTLS) + // tell user to change to plaintext pw + m_urlErrorState = NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL; + else + // tell user to change to plaintext pw, with big warning + m_urlErrorState = NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL; + } + // pref has plaintext pw & server claims to support encrypted pw + else if (m_prefAuthMethods == (SMTP_AUTH_LOGIN_ENABLED | + SMTP_AUTH_PLAIN_ENABLED) && + m_flags & SMTP_AUTH_CRAM_MD5_ENABLED) + // tell user to change to encrypted pw + m_urlErrorState = NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT; + else + { + // just "change auth method" + m_urlErrorState = NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED; + } + } + else if (m_failedAuthMethods == SMTP_AUTH_GSSAPI_ENABLED) + { + // We have only GSSAPI, and it failed, so nothing left to do. + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("GSSAPI only and it failed")); + m_urlErrorState = NS_ERROR_SMTP_AUTH_GSSAPI; + } + else + { + // we tried to login, but it all failed + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("All auth attempts failed")); + m_urlErrorState = NS_ERROR_SMTP_AUTH_FAILURE; + } + m_nextState = SMTP_ERROR_DONE; + return NS_ERROR_SMTP_AUTH_FAILURE; + } + + return NS_OK; +} + + + +nsresult nsSmtpProtocol::AuthLoginResponse(nsIInputStream * stream, uint32_t length) +{ + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP Login response, code %d", m_responseCode)); + nsresult status = NS_OK; + + switch (m_responseCode/100) + { + case 2: + m_nextState = SMTP_SEND_HELO_RESPONSE; + // fake to 250 because SendHeloResponse() tests for this + m_responseCode = 250; + break; + case 3: + m_nextState = SMTP_SEND_AUTH_LOGIN_STEP2; + break; + case 5: + default: + nsCOMPtr<nsISmtpServer> smtpServer; + m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer)); + if (smtpServer) + { + // If one authentication failed, mark it failed, so that we're going to + // fall back on a less secure login method. + MarkAuthMethodAsFailed(m_currentAuthMethod); + + bool allFailed = NS_FAILED(ChooseAuthMethod()); + if (allFailed && m_failedAuthMethods > 0 && + m_failedAuthMethods != SMTP_AUTH_GSSAPI_ENABLED && + m_failedAuthMethods != SMTP_AUTH_EXTERNAL_ENABLED) + { + // We've tried all avail. methods, and they all failed, and we have no mechanism left. + // Ask user to try with a new password. + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Warning, + ("SMTP: ask user what to do (after login failed): new password, retry or cancel")); + + nsCOMPtr<nsISmtpServer> smtpServer; + nsresult rv = m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString hostname; + rv = smtpServer->GetHostname(hostname); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t buttonPressed = 1; + if (NS_SUCCEEDED(MsgPromptLoginFailed(nullptr, hostname, + &buttonPressed))) + { + if (buttonPressed == 1) // Cancel button + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Warning, ("cancel button pressed")); + // abort and get out of here + status = NS_ERROR_ABORT; + break; + } + else if (buttonPressed == 2) // 'New password' button + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Warning, ("new password button pressed")); + // Change password was pressed. For now, forget the stored + // password and we'll prompt for a new one next time around. + smtpServer->ForgetPassword(); + if (m_usernamePrompted) + smtpServer->SetUsername(EmptyCString()); + + // Let's restore the original auth flags from SendEhloResponse + // so we can try them again with new password and username + ResetAuthMethods(); + // except for GSSAPI and EXTERNAL, which don't care about passwords. + MarkAuthMethodAsFailed(SMTP_AUTH_GSSAPI_ENABLED); + MarkAuthMethodAsFailed(SMTP_AUTH_EXTERNAL_ENABLED); + } + else if (buttonPressed == 0) // Retry button + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Warning, ("retry button pressed")); + // try all again, including GSSAPI + ResetAuthMethods(); + } + } + } + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, + ("SMTP: login failed: failed %X, current %X", m_failedAuthMethods, m_currentAuthMethod)); + + m_nextState = SMTP_AUTH_PROCESS_STATE; // try auth (ProcessAuth()) again, with other method + } + else + status = NS_ERROR_SMTP_PASSWORD_UNDEFINED; + break; + } + + return (status); +} + +nsresult nsSmtpProtocol::AuthGSSAPIFirst() +{ + NS_ASSERTION(m_currentAuthMethod == SMTP_AUTH_GSSAPI_ENABLED, "called in invalid state"); + nsAutoCString command("AUTH GSSAPI "); + nsAutoCString resp; + nsAutoCString service("smtp@"); + nsCString hostName; + nsCString userName; + nsresult rv; + nsCOMPtr<nsISmtpServer> smtpServer; + rv = m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer)); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + + rv = smtpServer->GetUsername(userName); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + + rv = smtpServer->GetHostname(hostName); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + service.Append(hostName); + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP: GSSAPI step 1 for user %s at server %s, service %s", + userName.get(), hostName.get(), service.get())); + + rv = DoGSSAPIStep1(service.get(), userName.get(), resp); + if (NS_FAILED(rv)) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("SMTP: GSSAPI step 1 failed early")); + MarkAuthMethodAsFailed(SMTP_AUTH_GSSAPI_ENABLED); + m_nextState = SMTP_AUTH_PROCESS_STATE; + return NS_OK; + } + else + command.Append(resp); + command.Append(CRLF); + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_SEND_AUTH_GSSAPI_STEP; + SetFlag(SMTP_PAUSE_FOR_READ); + return SendData(command.get()); +} + +// GSSAPI may consist of multiple round trips + +nsresult nsSmtpProtocol::AuthGSSAPIStep() +{ + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP: GSSAPI auth step 2")); + NS_ASSERTION(m_currentAuthMethod == SMTP_AUTH_GSSAPI_ENABLED, "called in invalid state"); + nsresult rv; + nsAutoCString cmd; + + // Check to see what the server said + if (m_responseCode / 100 != 3) { + m_nextState = SMTP_AUTH_LOGIN_RESPONSE; + return NS_OK; + } + + rv = DoGSSAPIStep2(m_responseText, cmd); + if (NS_FAILED(rv)) + cmd = "*"; + cmd += CRLF; + + m_nextStateAfterResponse = (rv == NS_SUCCESS_AUTH_FINISHED)?SMTP_AUTH_LOGIN_RESPONSE:SMTP_SEND_AUTH_GSSAPI_STEP; + m_nextState = SMTP_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + + return SendData(cmd.get()); +} + + +// LOGIN and MSN consist of three steps (MSN not through the mechanism +// but by non-RFC2821 compliant implementation in MS servers) not two as +// PLAIN or CRAM-MD5, so we've to start here and continue with AuthStep1 +// if the server responds with with a 3xx code to "AUTH LOGIN" or "AUTH MSN" +nsresult nsSmtpProtocol::AuthLoginStep0() +{ + NS_ASSERTION(m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED || + m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED, + "called in invalid state"); + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP: MSN or LOGIN auth, step 0")); + nsAutoCString command(m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED + ? "AUTH MSN" CRLF : "AUTH LOGIN" CRLF); + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_AUTH_LOGIN_STEP0_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + + return SendData(command.get()); +} + +void nsSmtpProtocol::AuthLoginStep0Response() +{ + NS_ASSERTION(m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED || + m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED, + "called in invalid state"); + // need the test to be here instead in AuthLoginResponse() to + // continue with step 1 instead of 2 in case of a code 3xx + m_nextState = (m_responseCode/100 == 3) ? + SMTP_SEND_AUTH_LOGIN_STEP1 : SMTP_AUTH_LOGIN_RESPONSE; +} + +nsresult nsSmtpProtocol::AuthLoginStep1() +{ + char buffer[512]; // TODO nsAutoCString + nsresult rv; + nsresult status = NS_OK; + nsCString username; + char *base64Str = nullptr; + nsAutoCString password; + nsCOMPtr<nsISmtpServer> smtpServer; + rv = m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer)); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + + rv = smtpServer->GetUsername(username); + if (username.IsEmpty()) + { + rv = GetUsernamePassword(username, password); + m_usernamePrompted = true; + if (username.IsEmpty() || password.IsEmpty()) + return NS_ERROR_SMTP_PASSWORD_UNDEFINED; + } + + nsCString hostname; + smtpServer->GetHostname(hostname); + + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP AuthLoginStep1() for %s@%s", + username.get(), hostname.get())); + + GetPassword(password); + if (password.IsEmpty()) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("SMTP: password undefined")); + m_urlErrorState = NS_ERROR_SMTP_PASSWORD_UNDEFINED; + return NS_ERROR_SMTP_PASSWORD_UNDEFINED; + } + + if (m_currentAuthMethod == SMTP_AUTH_CRAM_MD5_ENABLED) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("CRAM auth, step 1")); + PR_snprintf(buffer, sizeof(buffer), "AUTH CRAM-MD5" CRLF); + } + else if (m_currentAuthMethod == SMTP_AUTH_NTLM_ENABLED || + m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("NTLM/MSN auth, step 1")); + nsAutoCString response; + rv = DoNtlmStep1(username.get(), password.get(), response); + PR_snprintf(buffer, sizeof(buffer), TestFlag(SMTP_AUTH_NTLM_ENABLED) ? + "AUTH NTLM %.256s" CRLF : + "%.256s" CRLF, response.get()); + } + else if (m_currentAuthMethod == SMTP_AUTH_PLAIN_ENABLED) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("PLAIN auth")); + char plain_string[512]; + int len = 1; /* first <NUL> char */ + + memset(plain_string, 0, 512); + PR_snprintf(&plain_string[1], 510, "%s", username.get()); + len += username.Length(); + len++; /* second <NUL> char */ + PR_snprintf(&plain_string[len], 511-len, "%s", password.get()); + len += password.Length(); + + base64Str = PL_Base64Encode(plain_string, len, nullptr); + PR_snprintf(buffer, sizeof(buffer), "AUTH PLAIN %.256s" CRLF, base64Str); + } + else if (m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("LOGIN auth")); + base64Str = PL_Base64Encode(username.get(), + username.Length(), nullptr); + PR_snprintf(buffer, sizeof(buffer), "%.256s" CRLF, base64Str); + } + else + return (NS_ERROR_COMMUNICATIONS_ERROR); + + status = SendData(buffer, true); + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_AUTH_LOGIN_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + NS_Free(base64Str); + + return (status); +} + +nsresult nsSmtpProtocol::AuthLoginStep2() +{ + /* use cached smtp password first + * if not then use cached pop password + * if pop password undefined + * sync with smtp password + */ + nsresult status = NS_OK; + nsresult rv; + nsAutoCString password; + + GetPassword(password); + if (password.IsEmpty()) + { + m_urlErrorState = NS_ERROR_SMTP_PASSWORD_UNDEFINED; + return NS_ERROR_SMTP_PASSWORD_UNDEFINED; + } + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP AuthLoginStep2")); + + if (!password.IsEmpty()) + { + char buffer[512]; + if (m_currentAuthMethod == SMTP_AUTH_CRAM_MD5_ENABLED) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("CRAM auth, step 2")); + unsigned char digest[DIGEST_LENGTH]; + char * decodedChallenge = PL_Base64Decode(m_responseText.get(), + m_responseText.Length(), nullptr); + + if (decodedChallenge) + rv = MSGCramMD5(decodedChallenge, strlen(decodedChallenge), password.get(), password.Length(), digest); + else + rv = NS_ERROR_FAILURE; + + PR_Free(decodedChallenge); + if (NS_SUCCEEDED(rv)) + { + nsAutoCString encodedDigest; + char hexVal[8]; + + for (uint32_t j=0; j<16; j++) + { + PR_snprintf (hexVal,8, "%.2x", 0x0ff & (unsigned short)digest[j]); + encodedDigest.Append(hexVal); + } + + nsCOMPtr<nsISmtpServer> smtpServer; + rv = m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer)); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + + nsCString userName; + rv = smtpServer->GetUsername(userName); + + PR_snprintf(buffer, sizeof(buffer), "%s %s", userName.get(), encodedDigest.get()); + char *base64Str = PL_Base64Encode(buffer, strlen(buffer), nullptr); + PR_snprintf(buffer, sizeof(buffer), "%s" CRLF, base64Str); + NS_Free(base64Str); + } + if (NS_FAILED(rv)) + PR_snprintf(buffer, sizeof(buffer), "*" CRLF); + } + else if (m_currentAuthMethod == SMTP_AUTH_NTLM_ENABLED || + m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("NTLM/MSN auth, step 2")); + nsAutoCString response; + rv = DoNtlmStep2(m_responseText, response); + PR_snprintf(buffer, sizeof(buffer), "%.509s" CRLF, response.get()); + } + else if (m_currentAuthMethod == SMTP_AUTH_PLAIN_ENABLED || + m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("PLAIN/LOGIN auth, step 2")); + char *base64Str = PL_Base64Encode(password.get(), password.Length(), nullptr); + PR_snprintf(buffer, sizeof(buffer), "%.256s" CRLF, base64Str); + NS_Free(base64Str); + } + else + return NS_ERROR_COMMUNICATIONS_ERROR; + + status = SendData(buffer, true); + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_AUTH_LOGIN_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + return (status); + } + + // XXX -1 is not a valid nsresult + return static_cast<nsresult>(-1); +} + +nsresult nsSmtpProtocol::AuthOAuth2Step1() +{ + MOZ_ASSERT(mOAuth2Support, "Can't do anything without OAuth2 support"); + + nsresult rv = mOAuth2Support->Connect(true, this); + NS_ENSURE_SUCCESS(rv, rv); + + m_nextState = SMTP_SUSPENDED; + return NS_OK; +} + +nsresult nsSmtpProtocol::OnSuccess(const nsACString &aAccessToken) +{ + MOZ_ASSERT(mOAuth2Support, "Can't do anything without OAuth2 support"); + + nsCString base64Str; + mOAuth2Support->BuildXOAuth2String(base64Str); + + // Send the AUTH XOAUTH2 command, and then siphon us back to the regular + // authentication login stream. + nsAutoCString buffer; + buffer.AppendLiteral("AUTH XOAUTH2 "); + buffer += base64Str; + buffer += CRLF; + nsresult rv = SendData(buffer.get(), true); + if (NS_FAILED(rv)) + { + m_nextState = SMTP_ERROR_DONE; + } + else + { + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_AUTH_LOGIN_RESPONSE; + } + + SetFlag(SMTP_PAUSE_FOR_READ); + + ProcessProtocolState(nullptr, nullptr, 0, 0); + return NS_OK; +} + +nsresult nsSmtpProtocol::OnFailure(nsresult aError) +{ + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("OAuth2 login error %08x", + (uint32_t)aError)); + m_urlErrorState = aError; + m_nextState = SMTP_ERROR_DONE; + return ProcessProtocolState(nullptr, nullptr, 0, 0); +} + + +nsresult nsSmtpProtocol::SendMailResponse() +{ + nsresult status = NS_OK; + nsAutoCString buffer; + nsresult rv; + + if (m_responseCode/10 != 25) + { + nsresult errorcode; + if (TestFlag(SMTP_EHLO_SIZE_ENABLED)) + errorcode = (m_responseCode == 452) ? NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED : + (m_responseCode == 552) ? NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2 : + NS_ERROR_SENDING_FROM_COMMAND; + else + errorcode = NS_ERROR_SENDING_FROM_COMMAND; + + rv = nsExplainErrorDetails(m_runningURL, errorcode, m_responseText.get()); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return(NS_ERROR_SENDING_FROM_COMMAND); + } + + /* Send the RCPT TO: command */ + bool requestDSN = false; + rv = m_runningURL->GetRequestDSN(&requestDSN); + + nsCOMPtr <nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv,rv); + + bool requestOnSuccess = false; + rv = prefBranch->GetBoolPref("mail.dsn.request_on_success_on", &requestOnSuccess); + + bool requestOnFailure = false; + rv = prefBranch->GetBoolPref("mail.dsn.request_on_failure_on", &requestOnFailure); + + bool requestOnDelay = false; + rv = prefBranch->GetBoolPref("mail.dsn.request_on_delay_on", &requestOnDelay); + + bool requestOnNever = false; + rv = prefBranch->GetBoolPref("mail.dsn.request_never_on", &requestOnNever); + + nsCString &address = m_addresses[m_addressesLeft - 1]; + if (TestFlag(SMTP_EHLO_DSN_ENABLED) && requestDSN && (requestOnSuccess || requestOnFailure || requestOnDelay || requestOnNever)) + { + char *encodedAddress = esmtp_value_encode(address.get()); + nsAutoCString dsnBuffer; + + if (encodedAddress) + { + buffer = "RCPT TO:<"; + buffer += address; + buffer += "> NOTIFY="; + + if (requestOnNever) + dsnBuffer += "NEVER"; + else + { + if (requestOnSuccess) + dsnBuffer += "SUCCESS"; + + if (requestOnFailure) + dsnBuffer += dsnBuffer.IsEmpty() ? "FAILURE" : ",FAILURE"; + + if (requestOnDelay) + dsnBuffer += dsnBuffer.IsEmpty() ? "DELAY" : ",DELAY"; + } + + buffer += dsnBuffer; + buffer += " ORCPT=rfc822;"; + buffer += encodedAddress; + buffer += CRLF; + PR_FREEIF(encodedAddress); + } + else + { + m_urlErrorState = NS_ERROR_OUT_OF_MEMORY; + return (NS_ERROR_OUT_OF_MEMORY); + } + } + else + { + buffer = "RCPT TO:<"; + buffer += address; + buffer += ">"; + buffer += CRLF; + } + status = SendData(buffer.get()); + + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_SEND_RCPT_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + + return(status); +} + +nsresult nsSmtpProtocol::SendRecipientResponse() +{ + nsresult status = NS_OK; + nsAutoCString buffer; + nsresult rv; + + if (m_responseCode / 10 != 25) + { + nsresult errorcode; + if (TestFlag(SMTP_EHLO_SIZE_ENABLED)) + errorcode = (m_responseCode == 452) ? NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED : + (m_responseCode == 552) ? NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2 : + NS_ERROR_SENDING_RCPT_COMMAND; + else + errorcode = NS_ERROR_SENDING_RCPT_COMMAND; + + if (errorcode == NS_ERROR_SENDING_RCPT_COMMAND) { + rv = nsExplainErrorDetails( + m_runningURL, errorcode, NS_ConvertUTF8toUTF16(m_responseText).get(), + NS_ConvertUTF8toUTF16(m_addresses[m_addressesLeft - 1]).get()); + } else { + rv = nsExplainErrorDetails(m_runningURL, errorcode, + m_responseText.get(), + m_addresses[m_addressesLeft - 1].get()); + } + + if (!NS_SUCCEEDED(rv)) + NS_ASSERTION(false, "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return(NS_ERROR_SENDING_RCPT_COMMAND); + } + + if (--m_addressesLeft > 0) + { + // more senders to RCPT to + // fake to 250 because SendMailResponse() can't handle 251 + m_responseCode = 250; + m_nextState = SMTP_SEND_MAIL_RESPONSE; + return NS_OK; + } + + /* else send the DATA command */ + buffer = "DATA"; + buffer += CRLF; + status = SendData(buffer.get()); + + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_SEND_DATA_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + + return(status); +} + + +nsresult nsSmtpProtocol::SendData(const char *dataBuffer, bool aSuppressLogging) +{ + // XXX -1 is not a valid nsresult + if (!dataBuffer) return static_cast<nsresult>(-1); + + if (!aSuppressLogging) { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, ("SMTP Send: %s", dataBuffer)); + } else { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, ("Logging suppressed for this command (it probably contained authentication information)")); + } + return nsMsgAsyncWriteProtocol::SendData(dataBuffer); +} + + +nsresult nsSmtpProtocol::SendDataResponse() +{ + nsresult status = NS_OK; + + if (m_responseCode != 354) + { + mozilla::DebugOnly<nsresult> rv = nsExplainErrorDetails(m_runningURL, + NS_ERROR_SENDING_DATA_COMMAND, + m_responseText.get()); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return(NS_ERROR_SENDING_DATA_COMMAND); + } + + m_nextState = SMTP_SEND_POST_DATA; + ClearFlag(SMTP_PAUSE_FOR_READ); /* send data directly */ + + UpdateStatus(u"smtpDeliveringMail"); + + { +// m_runningURL->GetBodySize(&m_totalMessageSize); + } + return(status); +} + +void nsSmtpProtocol::SendMessageInFile() +{ + nsCOMPtr<nsIFile> file; + nsCOMPtr<nsIURI> url = do_QueryInterface(m_runningURL); + m_runningURL->GetPostMessageFile(getter_AddRefs(file)); + if (url && file) + // need to fully qualify to avoid getting overwritten by a #define + // in some windows header file + nsMsgAsyncWriteProtocol::PostMessage(url, file); + + SetFlag(SMTP_PAUSE_FOR_READ); + + // for now, we are always done at this point..we aren't making multiple calls + // to post data... + + UpdateStatus(u"smtpDeliveringMail"); + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_SEND_MESSAGE_RESPONSE; +} + +void nsSmtpProtocol::SendPostData() +{ + // mscott: as a first pass, I'm writing everything at once and am not + // doing it in chunks... + + /* returns 0 on done and negative on error + * positive if it needs to continue. + */ + + // check to see if url is a file..if it is...call our file handler... + bool postMessageInFile = true; + m_runningURL->GetPostMessage(&postMessageInFile); + if (postMessageInFile) + { + SendMessageInFile(); + } + + /* Update the thermo and the status bar. This is done by hand, rather + than using the FE_GraphProgress* functions, because there seems to be + no way to make FE_GraphProgress shut up and not display anything more + when all the data has arrived. At the end, we want to show the + "message sent; waiting for reply" status; FE_GraphProgress gets in + the way of that. See bug #23414. */ +} + + + +nsresult nsSmtpProtocol::SendMessageResponse() +{ + if((m_responseCode/10 != 25)) + { + mozilla::DebugOnly<nsresult> rv = nsExplainErrorDetails(m_runningURL, + NS_ERROR_SENDING_MESSAGE, + m_responseText.get()); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return(NS_ERROR_SENDING_MESSAGE); + } + + UpdateStatus(u"smtpMailSent"); + + /* else */ + return SendQuit(); +} + +nsresult nsSmtpProtocol::SendQuit(SmtpState aNextStateAfterResponse) +{ + m_sendDone = true; + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = aNextStateAfterResponse; + + return SendData("QUIT" CRLF); // send a quit command to close the connection with the server. +} + +nsresult nsSmtpProtocol::LoadUrl(nsIURI * aURL, nsISupports * aConsumer ) +{ + if (!aURL) + return NS_OK; + + Initialize(aURL); + + m_continuationResponse = -1; /* init */ + m_runningURL = do_QueryInterface(aURL); + if (!m_runningURL) + return NS_ERROR_FAILURE; + + // we had a bug where we failed to bring up an alert if the host + // name was empty....so throw up an alert saying we don't have + // a host name and inform the caller that we are not going to + // run the url... + nsAutoCString hostName; + aURL->GetHost(hostName); + if (hostName.IsEmpty()) + { + nsCOMPtr <nsIMsgMailNewsUrl> aMsgUrl = do_QueryInterface(aURL); + if (aMsgUrl) + { + aMsgUrl->SetUrlState(true, NS_OK); + // set the url as a url currently being run... + aMsgUrl->SetUrlState(false /* we aren't running the url */, + NS_ERROR_SMTP_AUTH_FAILURE); + } + return NS_ERROR_BUT_DONT_SHOW_ALERT; + } + + bool postMessage = false; + m_runningURL->GetPostMessage(&postMessage); + + if (postMessage) + { + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_EXTN_LOGIN_RESPONSE; + + // compile a minimal list of valid target addresses by + // - looking only at mailboxes + // - dropping addresses with invalid localparts (until we implement RFC 6532) + // - using ACE for IDN domainparts + // - stripping duplicates + nsCString addresses; + m_runningURL->GetRecipients(getter_Copies(addresses)); + + ExtractEmails(EncodedHeader(addresses), UTF16ArrayAdapter<>(m_addresses)); + + nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID); + addresses.Truncate(); + uint32_t count = m_addresses.Length(); + for (uint32_t i = 0; i < count; i++) + { + const char *start = m_addresses[i].get(); + // Location of the @ character + const char *lastAt = nullptr; + const char *ch = start; + for (; *ch; ch++) + { + if (*ch == '@') + lastAt = ch; + // Check for first illegal character (outside 0x09,0x20-0x7e) + else if ((*ch < ' ' || *ch > '~') && (*ch != '\t')) + { + break; + } + } + // validate the just parsed address + if (*ch || m_addresses[i].IsEmpty()) + { + // Fortunately, we will always have an @ in each mailbox address. + // We try to fix illegal character in the domain part by converting + // that to ACE. Illegal characters in the local part are not fixable + // (which charset would it be anyway?), hence we error out in that + // case as well. + nsresult rv = NS_ERROR_FAILURE; // anything but NS_OK + if (lastAt) + { + // Illegal char in the domain part, hence use ACE + nsAutoCString domain; + domain.Assign(lastAt + 1); + rv = converter->ConvertUTF8toACE(domain, domain); + if (NS_SUCCEEDED(rv)) + { + m_addresses[i].SetLength(lastAt - start + 1); + m_addresses[i] += domain; + } + } + if (NS_FAILED(rv)) + { + // Throw an error, including the broken address + m_nextState = SMTP_ERROR_DONE; + ClearFlag(SMTP_PAUSE_FOR_READ); + // Unfortunately, nsExplainErrorDetails will show the error above + // the mailnews main window, because we don't necessarily get + // passed down a compose window - we might be sending in the + // background! + rv = nsExplainErrorDetails(m_runningURL, + NS_ERROR_ILLEGAL_LOCALPART, start); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain illegal localpart"); + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return NS_ERROR_BUT_DONT_SHOW_ALERT; + } + } + } + + // final cleanup + m_addressesLeft = m_addresses.Length(); + + // hmm no addresses to send message to... + if (m_addressesLeft == 0) + { + m_nextState = SMTP_ERROR_DONE; + ClearFlag(SMTP_PAUSE_FOR_READ); + m_urlErrorState = NS_MSG_NO_RECIPIENTS; + return NS_MSG_NO_RECIPIENTS; + } + } // if post message + + return nsMsgProtocol::LoadUrl(aURL, aConsumer); +} + +/* + * returns negative if the transfer is finished or error'd out + * + * returns zero or more if the transfer needs to be continued. + */ +nsresult nsSmtpProtocol::ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream, + uint64_t sourceOffset, uint32_t length) + { + nsresult status = NS_OK; + ClearFlag(SMTP_PAUSE_FOR_READ); /* already paused; reset */ + + while(!TestFlag(SMTP_PAUSE_FOR_READ)) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, ("SMTP entering state: %d", + m_nextState)); + switch(m_nextState) + { + case SMTP_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SmtpResponse(inputStream, length); + break; + + case SMTP_START_CONNECT: + SetFlag(SMTP_PAUSE_FOR_READ); + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_EXTN_LOGIN_RESPONSE; + break; + case SMTP_FINISH_CONNECT: + SetFlag(SMTP_PAUSE_FOR_READ); + break; + case SMTP_TLS_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SendTLSResponse(); + break; + case SMTP_EXTN_LOGIN_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = ExtensionLoginResponse(inputStream, length); + break; + + case SMTP_SEND_HELO_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SendHeloResponse(inputStream, length); + break; + case SMTP_SEND_EHLO_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SendEhloResponse(inputStream, length); + break; + case SMTP_AUTH_PROCESS_STATE: + status = ProcessAuth(); + break; + + case SMTP_SEND_AUTH_GSSAPI_FIRST: + status = AuthGSSAPIFirst(); + break; + + case SMTP_SEND_AUTH_GSSAPI_STEP: + status = AuthGSSAPIStep(); + break; + + case SMTP_SEND_AUTH_LOGIN_STEP0: + status = AuthLoginStep0(); + break; + + case SMTP_AUTH_LOGIN_STEP0_RESPONSE: + AuthLoginStep0Response(); + status = NS_OK; + break; + + case SMTP_AUTH_EXTERNAL_RESPONSE: + case SMTP_AUTH_LOGIN_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = AuthLoginResponse(inputStream, length); + break; + + case SMTP_SEND_AUTH_LOGIN_STEP1: + status = AuthLoginStep1(); + break; + + case SMTP_SEND_AUTH_LOGIN_STEP2: + status = AuthLoginStep2(); + break; + + case SMTP_AUTH_OAUTH2_STEP: + status = AuthOAuth2Step1(); + break; + + + case SMTP_SEND_MAIL_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SendMailResponse(); + break; + + case SMTP_SEND_RCPT_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SendRecipientResponse(); + break; + + case SMTP_SEND_DATA_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SendDataResponse(); + break; + + case SMTP_SEND_POST_DATA: + SendPostData(); + status = NS_OK; + break; + + case SMTP_SEND_MESSAGE_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SendMessageResponse(); + break; + case SMTP_DONE: + { + nsCOMPtr <nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(m_runningURL); + mailNewsUrl->SetUrlState(false, NS_OK); + } + + m_nextState = SMTP_FREE; + break; + + case SMTP_ERROR_DONE: + { + nsCOMPtr <nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(m_runningURL); + // propagate the right error code + mailNewsUrl->SetUrlState(false, m_urlErrorState); + } + + m_nextState = SMTP_FREE; + break; + + case SMTP_FREE: + // smtp is a one time use connection so kill it if we get here... + nsMsgAsyncWriteProtocol::CloseSocket(); + return NS_OK; /* final end */ + + // This state means we're going into an async loop and waiting for + // something (say auth) to happen. ProcessProtocolState will be + // retriggered when necessary. + case SMTP_SUSPENDED: + return NS_OK; + + default: /* should never happen !!! */ + m_nextState = SMTP_ERROR_DONE; + break; + } + + /* check for errors during load and call error + * state if found + */ + if (NS_FAILED(status) && m_nextState != SMTP_FREE) { + // send a quit command to close the connection with the server. + if (NS_FAILED(SendQuit(SMTP_ERROR_DONE))) + { + m_nextState = SMTP_ERROR_DONE; + // Don't exit - loop around again and do the free case + ClearFlag(SMTP_PAUSE_FOR_READ); + } + } + } /* while(!SMTP_PAUSE_FOR_READ) */ + + return NS_OK; +} + +nsresult +nsSmtpProtocol::GetPassword(nsCString &aPassword) +{ + nsresult rv; + nsCOMPtr<nsISmtpUrl> smtpUrl = do_QueryInterface(m_runningURL, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsISmtpServer> smtpServer; + rv = smtpUrl->GetSmtpServer(getter_AddRefs(smtpServer)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = smtpServer->GetPassword(aPassword); + NS_ENSURE_SUCCESS(rv,rv); + + if (!aPassword.IsEmpty()) + return rv; + // empty password + + nsCOMPtr <nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCString username; + rv = smtpServer->GetUsername(username); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ConvertASCIItoUTF16 usernameUTF16(username); + + nsCString hostname; + rv = smtpServer->GetHostname(hostname); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString hostnameUTF16; + CopyASCIItoUTF16(hostname, hostnameUTF16); + + const char16_t *formatStrings[] = + { + hostnameUTF16.get(), + usernameUTF16.get() + }; + + rv = PromptForPassword(smtpServer, smtpUrl, formatStrings, aPassword); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +/** + * formatStrings is an array for the prompts, item 0 is the hostname, item 1 + * is the username. + */ +nsresult +nsSmtpProtocol::PromptForPassword(nsISmtpServer *aSmtpServer, nsISmtpUrl *aSmtpUrl, const char16_t **formatStrings, nsACString &aPassword) +{ + nsCOMPtr<nsIStringBundleService> stringService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(stringService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> composeStringBundle; + nsresult rv = stringService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(composeStringBundle)); + NS_ENSURE_SUCCESS(rv,rv); + + nsString passwordPromptString; + if(formatStrings[1]) + rv = composeStringBundle->FormatStringFromName( + u"smtpEnterPasswordPromptWithUsername", + formatStrings, 2, getter_Copies(passwordPromptString)); + else + rv = composeStringBundle->FormatStringFromName( + u"smtpEnterPasswordPrompt", + formatStrings, 1, getter_Copies(passwordPromptString)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAuthPrompt> netPrompt; + rv = aSmtpUrl->GetAuthPrompt(getter_AddRefs(netPrompt)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString passwordTitle; + rv = composeStringBundle->GetStringFromName( + u"smtpEnterPasswordPromptTitle", + getter_Copies(passwordTitle)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = aSmtpServer->GetPasswordWithUI(passwordPromptString.get(), passwordTitle.get(), + netPrompt, aPassword); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +nsresult +nsSmtpProtocol::GetUsernamePassword(nsACString &aUsername, + nsACString &aPassword) +{ + nsresult rv; + nsCOMPtr<nsISmtpUrl> smtpUrl = do_QueryInterface(m_runningURL, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsISmtpServer> smtpServer; + rv = smtpUrl->GetSmtpServer(getter_AddRefs(smtpServer)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = smtpServer->GetPassword(aPassword); + NS_ENSURE_SUCCESS(rv,rv); + + if (!aPassword.IsEmpty()) + { + rv = smtpServer->GetUsername(aUsername); + NS_ENSURE_SUCCESS(rv,rv); + + if (!aUsername.IsEmpty()) + return rv; + } + // empty password + + aPassword.Truncate(); + + nsCString hostname; + rv = smtpServer->GetHostname(hostname); + NS_ENSURE_SUCCESS(rv, rv); + + const char16_t *formatStrings[] = + { + NS_ConvertASCIItoUTF16(hostname).get(), + nullptr + }; + + rv = PromptForPassword(smtpServer, smtpUrl, formatStrings, aPassword); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + |