Revision control
Copy as Markdown
Other Tools
/* 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,
/**
* Tests AUTH GSSAPI for SMTP, in particular that any text a server appends to
* the first "334 " continuation response is ignored. In GSSAPI the client
* speaks first, so that challenge never carries a token, but Exchange puts a
* human readable string there which is not valid base64.
*/
var { MailServices } = ChromeUtils.importESModule(
);
var { MockRegistrar } = ChromeUtils.importESModule(
);
var { PromiseTestUtils } = ChromeUtils.importESModule(
);
var { SMTP_GSSAPI_handler } = ChromeUtils.importESModule(
);
const kSecondChallenge = btoa("server-token");
// The challenges passed to nsIMailAuthModule.getNextToken, in order.
const gChallenges = [];
// Text the server appends to the first "334 " response.
var gFirstChallengeText = "";
var gServer;
var gSmtpServer;
var gIdentity;
/**
* A stand-in for the real GSSAPI auth module, which would need a Kerberos
* ticket. It records the challenges it is fed and hands out canned tokens.
*
* @implements {nsIMailAuthModule}
*/
const gMockAuthModule = {
QueryInterface: ChromeUtils.generateQI(["nsIMailAuthModule"]),
init(type, serviceName) {
Assert.equal(type, "sasl-gssapi", "auth module type should be sasl-gssapi");
Assert.equal(
serviceName,
"smtp@localhost",
"auth module service name should be the smtp service"
);
},
getNextToken(inToken) {
gChallenges.push(inToken);
return btoa(`client-token-${gChallenges.length}`);
},
};
add_setup(async function () {
gServer = setupServerDaemon(daemon => {
const handler = new SMTP_GSSAPI_handler(daemon);
handler.kFirstChallengeText = gFirstChallengeText;
handler.kSecondChallenge = kSecondChallenge;
return handler;
});
gServer.start();
localAccountUtils.loadLocalMailAccount();
gSmtpServer = getBasicSmtpServer(gServer.port);
gSmtpServer.authMethod = Ci.nsMsgAuthMethod.GSSAPI;
gIdentity = getSmtpIdentity("identity@foo.invalid", gSmtpServer);
const mockCid = MockRegistrar.register(
"@mozilla.org/mail/auth-module;1",
gMockAuthModule
);
registerCleanupFunction(() => {
MockRegistrar.unregister(mockCid);
gServer.stop();
});
});
/**
* Sends a message, and returns the commands the server saw. Note that the
* fake server does not record SASL continuation lines, so the tokens the
* client sent are not part of it.
*
* @returns {string[]}
*/
async function sendMessage() {
gChallenges.length = 0;
gServer.resetTest();
const messageId = Cc["@mozilla.org/messengercompose/computils;1"]
.createInstance(Ci.nsIMsgCompUtils)
.msgGenerateMessageId(gIdentity, null);
const listener = new PromiseTestUtils.PromiseMsgOutgoingListener();
gSmtpServer.sendMailMessage(
do_get_file("data/message1.eml"),
MailServices.headerParser.parseEncodedHeaderW("to@foo.invalid"),
[],
gIdentity,
"from@foo.invalid",
null,
null,
false,
messageId,
listener
);
await listener.promise;
gSmtpServer.closeCachedConnections();
let transaction = gServer.playTransaction();
if (Array.isArray(transaction)) {
transaction = transaction.at(-1);
}
return transaction.them;
}
/**
* A server which follows RFC 4954 and sends a bare "334 ".
*/
add_task(async function testEmptyFirstChallenge() {
gFirstChallengeText = "";
const them = await sendMessage();
Assert.deepEqual(
gChallenges,
["", kSecondChallenge],
"the challenges fed to the auth module should be the empty first challenge, then the server token"
);
Assert.ok(
them.includes("AUTH GSSAPI"),
"the client should have authenticated with GSSAPI"
);
});
/**
* Exchange appends text to the first "334 ". It is not base64 and must be
* ignored rather than passed to the auth module.
*/
add_task(async function testTextAfterFirstChallenge() {
gFirstChallengeText = "GSSAPI supported";
const them = await sendMessage();
Assert.deepEqual(
gChallenges,
["", kSecondChallenge],
"the text following the first 334 response should be ignored"
);
Assert.ok(
them.includes("AUTH GSSAPI"),
"the client should have authenticated with GSSAPI"
);
});