Bug 1532388 - implement full support for IMAP and SMTP CLIENTID. r=mkmelin

IMAP RFC Draft: https://tools.ietf.org/html/draft-yu-imap-client-id-01
SMTP RFC Draft: https://tools.ietf.org/html/draft-storey-smtp-client-id-05

This also updates the imapd.js server to expose a pseudo CLIENTID
command, and adds a new xpcshell test for clientid.
This commit is contained in:
Daniel Fraser 2020-01-15 18:12:33 +02:00
Родитель b1dd446f2c
Коммит 096323989d
19 изменённых файлов: 493 добавлений и 3 удалений

Просмотреть файл

@ -19,6 +19,9 @@ var { MailServices } = ChromeUtils.import(
* @return {nsIMsgAccount} - the newly created account
*/
function createAccountInBackend(config) {
let uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(
Ci.nsIUUIDGenerator
);
// incoming server
let inServer = MailServices.accounts.createIncomingServer(
config.incoming.username,
@ -28,6 +31,46 @@ function createAccountInBackend(config) {
inServer.port = config.incoming.port;
inServer.authMethod = config.incoming.auth;
inServer.password = config.incoming.password;
// This new CLIENTID is for the outgoing server, and will be applied to the
// incoming only if the incoming username and hostname match the outgoing.
// We must generate this unconditionally because we cannot determine whether
// the outgoing server has clientid enabled yet or not, and we need to do it
// here in order to populate the incoming server if the outgoing matches.
let newOutgoingClientid = uuidGen
.generateUUID()
.toString()
.replace(/[{}]/g, "");
// Grab the base domain of both incoming and outgoing hostname in order to
// compare the two to detect if the base domain is the same.
let incomingBaseDomain;
let outgoingBaseDomain;
try {
incomingBaseDomain = Services.eTLD.getBaseDomainFromHost(
config.incoming.hostname
);
} catch (e) {
incomingBaseDomain = config.incoming.hostname;
}
try {
outgoingBaseDomain = Services.eTLD.getBaseDomainFromHost(
config.outgoing.hostname
);
} catch (e) {
outgoingBaseDomain = config.outgoing.hostname;
}
if (
config.incoming.username == config.outgoing.username &&
incomingBaseDomain == outgoingBaseDomain
) {
inServer.clientid = newOutgoingClientid;
} else {
// If the username/hostname are different then generate a new CLIENTID.
inServer.clientid = uuidGen
.generateUUID()
.toString()
.replace(/[{}]/g, "");
}
if (config.rememberPassword && config.incoming.password.length) {
rememberPassword(inServer, config.incoming.password);
}
@ -105,6 +148,10 @@ function createAccountInBackend(config) {
outServer.hostname = config.outgoing.hostname;
outServer.port = config.outgoing.port;
outServer.authMethod = config.outgoing.auth;
// Populate the clientid if it is enabled for this outgoing server.
if (outServer.clientidEnabled) {
outServer.clientid = newOutgoingClientid;
}
if (config.outgoing.auth > 1) {
outServer.username = username;
outServer.password = config.incoming.password;

Просмотреть файл

@ -76,6 +76,17 @@ interface nsIMsgIncomingServer : nsISupports {
*/
attribute ACString type;
/**
* The CLIENTID to use for this server.
* @see https://tools.ietf.org/html/draft-yu-imap-client-id-01
*/
attribute ACString clientid;
/**
* Whether the CLIENTID feature above is enabled.
*/
attribute boolean clientidEnabled;
/**
* The proper instance of nsIMsgProtocolInfo corresponding to this server type.
*/

Просмотреть файл

@ -38,6 +38,18 @@ function run_test() {
"account1,account2"
);
// Set server1 and server2 username and hostname to test Clientid population.
Services.prefs.setCharPref("mail.server.server1.userName", "testuser1");
Services.prefs.setCharPref("mail.server.server2.userName", "testuser2");
Services.prefs.setCharPref(
"mail.server.server1.hostname",
"mail.sampledomain1.com"
);
Services.prefs.setCharPref(
"mail.server.server2.hostname",
"mail.sampledomain2.com"
);
loadABFile("data/remoteContent", kPABData.fileName);
let uriAllowed = Services.io.newURI(
@ -77,6 +89,10 @@ function run_test() {
// Now migrate the prefs.
migrateMailnews();
// Check that server 1 and server 2 have the same clientid.
Assert.ok(Services.prefs.prefHasUserValue("mail.server.server1.clientid"));
Assert.ok(Services.prefs.prefHasUserValue("mail.server.server2.clientid"));
// Check what has been set.
Assert.ok(!Services.prefs.prefHasUserValue("mail.server.server1.authMethod"));
Assert.ok(Services.prefs.prefHasUserValue("mail.server.server2.authMethod"));
@ -107,6 +123,18 @@ function run_test() {
// smtp2 has useSecAuth set to true, auth_method unset
Services.prefs.setBoolPref("mail.smtpserver.smtp2.useSecAuth", true);
// Set server1 and server2 username and hostname to test clientid population.
Services.prefs.setCharPref("mail.smtpserver.smtp1.username", "testuser1");
Services.prefs.setCharPref("mail.smtpserver.smtp2.username", "testuser2");
Services.prefs.setCharPref(
"mail.smtpserver.smtp1.hostname",
"mail.sampledomain1.com"
);
Services.prefs.setCharPref(
"mail.smtpserver.smtp2.hostname",
"mail.sampledomain2.com"
);
// Migration should now have added permissions for the address that had them
// and not for the one that didn't have them.
Assert.ok(Services.prefs.getIntPref("mail.ab_remote_content.migrated") > 0);
@ -122,6 +150,10 @@ function run_test() {
// Now migrate the prefs
migrateMailnews();
// Check that smtpserver 1 and smtpserver 2 now have a clientid.
Assert.ok(Services.prefs.prefHasUserValue("mail.smtpserver.smtp1.clientid"));
Assert.ok(Services.prefs.prefHasUserValue("mail.smtpserver.smtp2.clientid"));
Assert.ok(
!Services.prefs.prefHasUserValue("mail.smtpserver.smtp1.authMethod")
);
@ -137,6 +169,9 @@ function run_test() {
// setting the value back to "3", i.e. Ci.nsMsgAuthMethod.passwordCleartext.
Services.prefs.clearUserPref("mail.smtpserver.smtp2.authMethod");
// Now clear the mail.server.server1.clientid to test re-population.
Services.prefs.clearUserPref("mail.server.server2.clientid");
// Now attempt migration again, e.g. a second load of TB
migrateMailnews();
@ -147,4 +182,11 @@ function run_test() {
Assert.ok(
!Services.prefs.prefHasUserValue("mail.smtpserver.smtp2.authMethod")
);
// The server2 clientid should be the same as the smtpserver2 now since
// they are for the same mail.sampledomain2.com domain.
Assert.equal(
Services.prefs.getCharPref("mail.smtpserver.smtp2.clientid"),
Services.prefs.getCharPref("mail.server.server2.clientid")
);
}

Просмотреть файл

@ -23,6 +23,12 @@ var kABRemoteContentPrefVersion = 1;
var kDefaultCharsetsPrefVersion = 1;
function migrateMailnews() {
try {
MigrateProfileClientid();
} catch (e) {
logException(e);
}
try {
MigrateServerAuthPref();
} catch (e) {
@ -42,6 +48,108 @@ function migrateMailnews() {
}
}
/**
* Creates the server specific 'CLIENTID' prefs and tries to pair up any imap
* services with smtp services which are using the same username and hostname.
*/
function MigrateProfileClientid() {
let uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(
Ci.nsIUUIDGenerator
);
// Comma-separated list of all account ids.
let accounts = Services.prefs.getCharPref("mail.accountmanager.accounts");
// Comma-separated list of all smtp servers.
let smtpServers = Services.prefs.getCharPref("mail.smtpservers");
// If both accounts and smtpservers are empty then there is nothing to do.
if (accounts.length == 0 && smtpServers.length == 0) {
return;
}
// A cache to allow CLIENTIDS to be stored and shared across services that
// share a username and hostname.
let clientidCache = new Map();
// There may be accounts but no smtpservers so check the length before
// trying to split the smtp servers and iterate in the loop below.
if (smtpServers.length > 0) {
// Since the length of the smtpServers string is non-zero then we can split
// the string by comma and iterate each entry in the comma-separated list.
let smtpServerKeys = smtpServers.split(",");
// Now walk all smtp servers and generate any missing CLIENTIDS, caching
// all CLIENTIDS along the way to be reused for matching imap servers
// if possible.
for (let smtpServerKey of smtpServerKeys) {
let server = "mail.smtpserver." + smtpServerKey + ".";
if (
!Services.prefs.prefHasUserValue(server + "clientid") ||
!Services.prefs.getCharPref(server + "clientid")
) {
// Always give outgoing servers a new unique CLIENTID.
let newClientid = uuidGen
.generateUUID()
.toString()
.replace(/[{}]/g, "");
Services.prefs.setCharPref(server + "clientid", newClientid);
}
// Cache all CLIENTIDs from all outgoing servers to reuse them for any
// incoming servers which have a matching username and hostname.
let username = Services.prefs.getCharPref(server + "username");
let hostname = Services.prefs.getCharPref(server + "hostname");
let combinedKey;
try {
combinedKey =
username + "@" + Services.eTLD.getBaseDomainFromHost(hostname);
} catch (e) {
combinedKey = username + "@" + hostname;
}
clientidCache.set(
combinedKey,
Services.prefs.getCharPref(server + "clientid")
);
}
}
let accountKeys = accounts.split(",");
// Now walk all imap accounts and generate any missing CLIENTIDS, reusing
// cached CLIENTIDS if possible.
for (let accountKey of accountKeys) {
let serverKey = Services.prefs.getCharPref(
"mail.account." + accountKey + ".server"
);
let server = "mail.server." + serverKey + ".";
// Check if this server needs the CLIENTID preference to be populated.
if (
!Services.prefs.prefHasUserValue(server + "clientid") ||
!Services.prefs.getCharPref(server + "clientid")
) {
// Grab username + hostname to check if a CLIENTID is cached.
let username = Services.prefs.getCharPref(server + "userName");
let hostname = Services.prefs.getCharPref(server + "hostname");
let combinedKey;
try {
combinedKey =
username + "@" + Services.eTLD.getBaseDomainFromHost(hostname);
} catch (e) {
combinedKey = username + "@" + hostname;
}
if (!clientidCache.has(combinedKey)) {
// Generate a new CLIENTID if no matches were found from smtp servers.
let newClientid = uuidGen
.generateUUID()
.toString()
.replace(/[{}]/g, "");
Services.prefs.setCharPref(server + "clientid", newClientid);
} else {
// Otherwise if a cached CLIENTID was found for this username + hostname
// then we can just use the outgoing CLIENTID which was matching.
Services.prefs.setCharPref(
server + "clientid",
clientidCache.get(combinedKey)
);
}
}
}
}
/**
* Migrates from pref useSecAuth to pref authMethod
*/

Просмотреть файл

@ -1115,12 +1115,14 @@ nsMsgIncomingServer::OnUserOrHostNameChanged(const nsACString &oldName,
rv = accountManager->NotifyServerChanged(this);
NS_ENSURE_SUCCESS(rv, rv);
// 4. Lastly, replace all occurrences of old name in the acct name with the
// new one.
// 4. Replace all occurrences of old name in the acct name with the new one.
nsString acctName;
rv = GetPrettyName(acctName);
NS_ENSURE_SUCCESS(rv, rv);
// 5. Clear the clientid because the user or host have changed.
SetClientid(EmptyCString());
// If new username contains @ then better do not update the account name.
if (acctName.IsEmpty() || (!hostnameChanged && (atPos != kNotFound)))
return NS_OK;
@ -1554,6 +1556,8 @@ NS_IMPL_SERVERPREF_STR(nsMsgIncomingServer, Username, "userName")
NS_IMPL_SERVERPREF_INT(nsMsgIncomingServer, AuthMethod, "authMethod")
NS_IMPL_SERVERPREF_INT(nsMsgIncomingServer, BiffMinutes, "check_time")
NS_IMPL_SERVERPREF_STR(nsMsgIncomingServer, Type, "type")
NS_IMPL_SERVERPREF_STR(nsMsgIncomingServer, Clientid, "clientid")
NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, ClientidEnabled, "clientidEnabled")
NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, DownloadOnBiff, "download_on_biff")
NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, Valid, "valid")
NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, EmptyTrashOnExit,

Просмотреть файл

@ -36,6 +36,17 @@ interface nsISmtpServer : nsISupports {
/// The username to access the server with (if required)
attribute ACString username;
/**
* The CLIENTID to use for this server (if required).
* @see https://tools.ietf.org/html/draft-storey-smtp-client-id-05
*/
attribute ACString clientid;
/**
* Whether the CLIENTID feature above is enabled.
*/
attribute boolean clientidEnabled;
/**
* The password to access the server with (if required).
*

Просмотреть файл

@ -95,6 +95,10 @@ const char* errorStringNameForErrorCode(nsresult aCode) {
return "smtpAuthMechNotSupported";
case NS_ERROR_ILLEGAL_LOCALPART:
return "errorIllegalLocalPart";
case NS_ERROR_CLIENTID:
return "smtpClientid";
case NS_ERROR_CLIENTID_PERMISSION:
return "smtpClientidPermission";
default:
return "sendFailed";
}

Просмотреть файл

@ -67,6 +67,9 @@
#define NS_ERROR_ILLEGAL_LOCALPART NS_MSG_GENERATE_FAILURE(12601)
#define NS_ERROR_CLIENTID NS_MSG_GENERATE_FAILURE(12610)
#define NS_ERROR_CLIENTID_PERMISSION NS_MSG_GENERATE_FAILURE(12611)
const char* errorStringNameForErrorCode(nsresult aCode);
#endif /* _nsComposeStrings_H__ */

Просмотреть файл

@ -113,6 +113,8 @@ nsresult nsExplainErrorDetails(nsISmtpUrl *aSmtpUrl, nsresult aCode,
case NS_ERROR_SENDING_DATA_COMMAND:
case NS_ERROR_SENDING_MESSAGE:
case NS_ERROR_SMTP_GREETING:
case NS_ERROR_CLIENTID:
case NS_ERROR_CLIENTID_PERMISSION:
exitString = errorStringNameForErrorCode(aCode);
bundle->GetStringFromName(exitString, eMsg);
if (aCode == NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_1) {
@ -233,6 +235,7 @@ nsresult nsSmtpProtocol::Initialize(nsIURI *aURL) {
m_usernamePrompted = false;
m_prefSocketType = nsMsgSocketType::trySTARTTLS;
m_tlsInitiated = false;
m_clientIDInitialized = false;
m_url = aURL; // Needed in nsMsgAsyncWriteProtocol::UpdateProgress().
m_urlErrorState = NS_ERROR_FAILURE;
@ -277,6 +280,12 @@ nsresult nsSmtpProtocol::Initialize(nsIURI *aURL) {
smtpServer->GetAuthMethod(&authMethod);
smtpServer->GetSocketType(&m_prefSocketType);
smtpServer->GetHelloArgument(m_helloArgument);
bool clientidEnabled = false;
if (NS_SUCCEEDED(smtpServer->GetClientidEnabled(&clientidEnabled)) &&
clientidEnabled)
smtpServer->GetClientid(m_clientId);
else
m_clientId.Truncate();
// Query for OAuth2 support. If the SMTP server preferences don't allow
// for OAuth2, then don't carry around the OAuth2 module any longer
@ -848,6 +857,10 @@ nsresult nsSmtpProtocol::SendEhloResponse(nsIInputStream *inputStream,
SetFlag(SMTP_EHLO_STARTTLS_ENABLED);
} else if (responseLine.LowerCaseEqualsLiteral("dsn")) {
SetFlag(SMTP_EHLO_DSN_ENABLED);
} else if (responseLine.LowerCaseEqualsLiteral("clientid")) {
SetFlag(SMTP_EHLO_CLIENTID_ENABLED);
// If we have "clientid" in the ehlo response, then TLS must be present.
if (m_prefSocketType == nsMsgSocketType::SSL) m_tlsEnabled = true;
} else if (StringBeginsWith(responseLine, NS_LITERAL_CSTRING("AUTH"),
nsCaseInsensitiveCStringComparator())) {
SetFlag(SMTP_AUTH);
@ -948,6 +961,38 @@ nsresult nsSmtpProtocol::SendTLSResponse() {
return rv;
}
nsresult nsSmtpProtocol::SendClientIDResponse() {
if (m_responseCode / 10 == 25) {
// ClientID success!
m_clientIDInitialized = true;
ClearFlag(SMTP_EHLO_CLIENTID_ENABLED);
m_nextState = SMTP_AUTH_PROCESS_STATE;
return NS_OK;
}
// ClientID failed
nsresult errorCode;
if (m_responseCode == 550) {
// 'You are not permitted to access this'
// 'Access Denied' + server response
errorCode = NS_ERROR_CLIENTID_PERMISSION;
} else {
if (MOZ_LOG_TEST(SMTPLogModule, mozilla::LogLevel::Error)) {
if (m_responseCode != 501 && m_responseCode != 503 &&
m_responseCode != 504 && m_responseCode / 100 != 4) {
// If not 501, 503, 504 or 4xx, log an error.
MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error,
("SendClientIDResponse: Unexpected error occurred, server "
"responded: %s\n",
m_responseText.get()));
}
}
errorCode = NS_ERROR_CLIENTID;
}
nsExplainErrorDetails(m_runningURL, errorCode, m_responseText.get(), nullptr);
m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT;
return NS_ERROR_SMTP_AUTH_FAILURE;
}
void nsSmtpProtocol::InitPrefAuthMethods(int32_t authMethodPrefValue) {
// for m_prefAuthMethods, using the same flags as server capabilities.
switch (authMethodPrefValue) {
@ -1093,6 +1138,19 @@ nsresult nsSmtpProtocol::ProcessAuth() {
}
}
if (!m_clientIDInitialized && m_tlsEnabled && !m_clientId.IsEmpty()) {
if (TestFlag(SMTP_EHLO_CLIENTID_ENABLED)) {
buffer = "CLIENTID UUID ";
buffer += m_clientId;
buffer += CRLF;
status = SendData(buffer.get());
m_nextState = SMTP_RESPONSE;
m_nextStateAfterResponse = SMTP_CLIENTID_RESPONSE;
SetFlag(SMTP_PAUSE_FOR_READ);
return status;
}
}
(void)ChooseAuthMethod(); // advance m_currentAuthMethod
// We don't need to auth, per pref, or the server doesn't advertise AUTH,
@ -1937,6 +1995,12 @@ nsresult nsSmtpProtocol::ProcessProtocolState(nsIURI *url,
else
status = SendEhloResponse(inputStream, length);
break;
case SMTP_CLIENTID_RESPONSE:
if (inputStream == nullptr)
SetFlag(SMTP_PAUSE_FOR_READ);
else
status = SendClientIDResponse();
break;
case SMTP_AUTH_PROCESS_STATE:
status = ProcessAuth();
break;

Просмотреть файл

@ -53,6 +53,7 @@ typedef enum _SmtpState {
SMTP_SUSPENDED, // 25
SMTP_AUTH_OAUTH2_STEP, // 26
SMTP_AUTH_OAUTH2_RESPONSE, // 27
SMTP_CLIENTID_RESPONSE, // 28
} SmtpState;
// State Flags (Note, I use the word state in terms of storing
@ -64,6 +65,7 @@ typedef enum _SmtpState {
#define SMTP_EHLO_STARTTLS_ENABLED 0x00000008
#define SMTP_EHLO_SIZE_ENABLED 0x00000010
#define SMTP_EHLO_8BIT_ENABLED 0x00000020
#define SMTP_EHLO_CLIENTID_ENABLED 0x00000040
// insecure mechanisms follow
#define SMTP_AUTH_LOGIN_ENABLED 0x00000100
@ -139,6 +141,7 @@ class nsSmtpProtocol : public nsMsgAsyncWriteProtocol,
uint32_t m_addressesLeft;
nsCString m_mailAddr;
nsCString m_helloArgument;
nsCString m_clientId;
int32_t m_sizelimit;
// *** the following should move to the smtp server when we support
@ -149,6 +152,8 @@ class nsSmtpProtocol : public nsMsgAsyncWriteProtocol,
bool m_tlsInitiated;
bool m_clientIDInitialized;
bool m_sendDone;
int32_t m_totalAmountRead;
@ -198,6 +203,7 @@ class nsSmtpProtocol : public nsMsgAsyncWriteProtocol,
nsresult AuthOAuth2Step1();
nsresult SendTLSResponse();
nsresult SendClientIDResponse();
nsresult SendMailResponse();
nsresult SendRecipientResponse();
nsresult SendDataResponse();

Просмотреть файл

@ -285,6 +285,43 @@ nsSmtpServer::SetUsername(const nsACString &aUsername) {
return NS_OK;
}
NS_IMETHODIMP
nsSmtpServer::GetClientid(nsACString &aClientid) {
nsresult rv;
rv = mPrefBranch->GetCharPref("clientid", aClientid);
if (NS_FAILED(rv)) {
rv = mDefPrefBranch->GetCharPref("clientid", aClientid);
if (NS_FAILED(rv)) aClientid.Truncate();
}
return NS_OK;
}
NS_IMETHODIMP
nsSmtpServer::SetClientid(const nsACString &aClientid) {
if (!aClientid.IsEmpty())
return mPrefBranch->SetCharPref("clientid", aClientid);
// If the pref value is already empty, ClearUserPref will return
// NS_ERROR_UNEXPECTED, so don't check the rv here.
mPrefBranch->ClearUserPref("clientid");
return NS_OK;
}
NS_IMETHODIMP nsSmtpServer::GetClientidEnabled(bool *aClientidEnabled) {
NS_ENSURE_ARG_POINTER(aClientidEnabled);
nsresult rv;
rv = mPrefBranch->GetBoolPref("clientidEnabled", aClientidEnabled);
if (NS_FAILED(rv)) {
rv = mDefPrefBranch->GetBoolPref("clientidEnabled", aClientidEnabled);
if (NS_FAILED(rv)) *aClientidEnabled = false;
}
return NS_OK;
}
NS_IMETHODIMP nsSmtpServer::SetClientidEnabled(bool aClientidEnabled) {
return mPrefBranch->SetBoolPref("clientidEnabled", aClientidEnabled);
}
NS_IMETHODIMP
nsSmtpServer::GetPassword(nsAString &aPassword) {
if (m_password.IsEmpty() && !m_logonFailed) {

Просмотреть файл

@ -143,6 +143,7 @@ const eIMAPCapabilityFlag kHasListExtendedCapability = 0x100000000LL; /* RFC
const eIMAPCapabilityFlag kHasSpecialUseCapability = 0x200000000LL; /* RFC 6154: Sent, Draft etc. folders */
const eIMAPCapabilityFlag kGmailImapCapability = 0x400000000LL; /* X-GM-EXT-1 capability extension for gmail */
const eIMAPCapabilityFlag kHasXOAuth2Capability = 0x800000000LL; /* AUTH XOAUTH2 extension */
const eIMAPCapabilityFlag kHasClientIDCapability = 0x1000000000LL; /* ClientID capability */
// this used to be part of the connection object class - maybe we should move it into

Просмотреть файл

@ -873,6 +873,14 @@ nsresult nsImapProtocol::SetupWithUrl(nsIURI *aURL, nsISupports *aConsumer) {
m_preferPlainText = preferPlainText;
}
}
// If enabled, retrieve the clientid so that we can use it later.
bool clientidEnabled = false;
if (NS_SUCCEEDED(server->GetClientidEnabled(&clientidEnabled)) &&
clientidEnabled)
server->GetClientid(m_clientId);
else {
m_clientId.Truncate();
}
bool proxyCallback = false;
if (m_runningUrl && !m_transport /* and we don't have a transport yet */) {
@ -5558,6 +5566,20 @@ nsresult nsImapProtocol::SendDataParseIMAPandCheckForNewMail(
return rv;
}
nsresult nsImapProtocol::ClientID() {
IncrementCommandTagNumber();
nsCString command(GetServerCommandTag());
command += " CLIENTID UUID ";
command += m_clientId;
command += CRLF;
nsresult rv = SendDataParseIMAPandCheckForNewMail(command.get(), nullptr);
NS_ENSURE_SUCCESS(rv, rv);
if (!GetServerStateParser().LastCommandSuccessful()) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
nsresult nsImapProtocol::AuthLogin(const char *userName,
const nsString &aPassword,
eIMAPCapabilityFlag flag) {
@ -8120,6 +8142,45 @@ bool nsImapProtocol::TryToLogon() {
}
}
// Check the uri host for localhost indicators to see if we
// should bypass the SSL check for clientid.
// Unfortunately we cannot call IsOriginPotentiallyTrustworthy
// here because it can only be called from the main thread.
bool isLocalhostConnection = false;
if (m_mockChannel) {
nsCOMPtr<nsIURI> uri;
m_mockChannel->GetURI(getter_AddRefs(uri));
if (uri) {
nsCString uriHost;
uri->GetHost(uriHost);
if (uriHost.Equals("127.0.0.1") || uriHost.Equals("::1") ||
uriHost.Equals("localhost")) {
isLocalhostConnection = true;
}
}
}
// Whether our connection can be considered 'secure' and whether
// we should allow the CLIENTID to be sent over this channel.
bool isSecureConnection =
(m_connectionType.EqualsLiteral("starttls") ||
m_connectionType.EqualsLiteral("ssl") || isLocalhostConnection);
// Before running the ClientID command we check for clientid
// support by checking the server capability flags for the
// flag kHasClientIDCapability.
// We check that the m_clientId string is not empty, and
// we ensure the connection can be considered secure.
if ((GetServerStateParser().GetCapabilityFlag() & kHasClientIDCapability) &&
!m_clientId.IsEmpty() && isSecureConnection) {
rv = ClientID();
if (NS_FAILED(rv)) {
MOZ_LOG(IMAP, LogLevel::Error,
("TryToLogon: Could not issue CLIENTID command"));
skipLoop = true;
}
}
// Get username, either the stored one or from user
rv = m_imapServerSink->GetLoginUsername(userName);
if (NS_FAILED(rv) || userName.IsEmpty()) {

Просмотреть файл

@ -535,6 +535,7 @@ class nsImapProtocol : public nsIImapProtocol,
void Language(); // set the language on the server if it supports it
void Namespace();
void InsecureLogin(const char *userName, const nsCString &password);
nsresult ClientID();
nsresult AuthLogin(const char *userName, const nsString &password,
eIMAPCapabilityFlag flag);
nsresult SendDataParseIMAPandCheckForNewMail(const char *data,
@ -681,6 +682,8 @@ class nsImapProtocol : public nsIImapProtocol,
nsString mAcceptLanguages;
nsCString m_clientId;
// progress stuff
void SetProgressString(uint32_t aStringIndex);

Просмотреть файл

@ -2002,6 +2002,8 @@ void nsImapServerResponseParser::capability_data() {
else if (token.Equals("HIGHESTMODSEQ",
nsCaseInsensitiveCStringComparator()))
fCapabilityFlag |= kHasHighestModSeqCapability;
else if (token.Equals("CLIENTID", nsCaseInsensitiveCStringComparator()))
fCapabilityFlag |= kHasClientIDCapability;
}
} while (fNextToken && endToken < 0 && !fAtEndOfLine && ContinueParse());

Просмотреть файл

@ -0,0 +1,63 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var { MailServices } = ChromeUtils.import(
"resource:///modules/MailServices.jsm"
);
var incomingServer, server;
const kUserName = "user";
const kValidPassword = "password";
var gTests = [
{
title: "Cleartext password, with server only supporting old-style login",
clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
serverAuthMethods: [],
expectSuccess: true,
transaction: [
"capability",
"CLIENTID",
"authenticate PLAIN",
"list",
"lsub",
],
},
];
add_task(async function() {
let daemon = new imapDaemon();
server = makeServer(daemon, "", {
// Make username of server match the singons.txt file
// (pw there is intentionally invalid)
kUsername: kUserName,
kPassword: kValidPassword,
});
server.setDebugLevel(fsDebugAll);
incomingServer = createLocalIMAPServer(server.port);
// Turn on CLIENTID and populate the clientid with a uuid.
incomingServer.clientidEnabled = true;
incomingServer.clientid = "4d8776ca-0251-11ea-8d71-362b9e155667";
// Connect.
incomingServer.performExpand(null);
server.performTest("LSUB");
do_check_transaction(server.playTransaction(), gTests[0].transaction, false);
server.resetTest();
});
registerCleanupFunction(function() {
incomingServer.closeCachedConnections();
server.stop();
var thread = gThreadManager.currentThread;
while (thread.hasPendingEvents()) {
thread.processNextEvent(true);
}
});

Просмотреть файл

@ -25,6 +25,7 @@ tail =
skip-if = true
[test_imapAutoSync.js]
[test_imapChunks.js]
[test_imapClientid.js]
[test_imapContentLength.js]
[test_imapCopyTimeout.js]
[test_imapFilterActions.js]

Просмотреть файл

@ -453,6 +453,15 @@ pref("mail.collect_addressbook", "jsaddrbook://history.sqlite");
pref("mail.default_sendlater_uri", "mailbox://nobody@Local%20Folders/Unsent%20Messages");
pref("mail.server.default.clientid", "");
pref("mail.smtpserver.default.clientid", "");
// This is not to be enabled by default until the prerequisite
// changes are completed. See here for details:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1565379
pref("mail.server.default.clientidEnabled", false);
pref("mail.smtpserver.default.clientidEnabled", false);
pref("mail.smtpservers", "");
pref("mail.accountmanager.accounts", "");

Просмотреть файл

@ -727,6 +727,7 @@ function IMAP_RFC3501_handler(daemon) {
this.kAuthSchemes = []; // Added by RFC2195 extension. Test may modify as needed.
this.kCapabilities = [
/* "LOGINDISABLED", "STARTTLS", */
"CLIENTID",
]; // Test may modify as needed.
this.kUidCommands = ["FETCH", "STORE", "SEARCH", "COPY"];
@ -738,7 +739,15 @@ function IMAP_RFC3501_handler(daemon) {
this._enabledCommands = {
// IMAP_STATE_NOT_AUTHED
0: ["CAPABILITY", "NOOP", "LOGOUT", "STARTTLS", "AUTHENTICATE", "LOGIN"],
0: [
"CAPABILITY",
"NOOP",
"LOGOUT",
"STARTTLS",
"CLIENTID",
"AUTHENTICATE",
"LOGIN",
],
// IMAP_STATE_AUTHED
1: [
"CAPABILITY",
@ -798,6 +807,7 @@ function IMAP_RFC3501_handler(daemon) {
NOOP: [],
LOGOUT: [],
STARTTLS: [],
CLIENTID: ["string", "string"],
AUTHENTICATE: ["atom", "..."],
LOGIN: ["string", "string"],
SELECT: ["mailbox"],
@ -1038,6 +1048,9 @@ IMAP_RFC3501_handler.prototype = {
capa += "\0OK CAPABILITY completed";
return capa;
},
CLIENTID(args) {
return "OK Recognized a valid CLIENTID command, used for authentication methods";
},
LOGOUT(args) {
this.closing = true;
if (this._selectedMailbox) {