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:
Родитель
b1dd446f2c
Коммит
096323989d
|
@ -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) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче