From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/mailnews/test/fakeserver/Smtpd.jsm | 274 ++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 comm/mailnews/test/fakeserver/Smtpd.jsm (limited to 'comm/mailnews/test/fakeserver/Smtpd.jsm') diff --git a/comm/mailnews/test/fakeserver/Smtpd.jsm b/comm/mailnews/test/fakeserver/Smtpd.jsm new file mode 100644 index 0000000000..646b626b86 --- /dev/null +++ b/comm/mailnews/test/fakeserver/Smtpd.jsm @@ -0,0 +1,274 @@ +/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// This file implements test SMTP servers + +var EXPORTED_SYMBOLS = ["SmtpDaemon", "SMTP_RFC2821_handler"]; + +var { AuthPLAIN, AuthLOGIN, AuthCRAM } = ChromeUtils.import( + "resource://testing-common/mailnews/Auth.jsm" +); + +class SmtpDaemon { + _messages = {}; +} + +// SMTP TEST SERVERS +// ----------------- + +var kStateAuthNeeded = 0; +var kStateAuthOptional = 2; +var kStateAuthenticated = 3; + +/** + * This handler implements the bare minimum required by RFC 2821. + * + * @see RFC 2821 + * If dropOnAuthFailure is set, the server will drop the connection + * on authentication errors, to simulate servers that do the same. + */ +class SMTP_RFC2821_handler { + kAuthRequired = false; + kUsername = "testsmtp"; + kPassword = "smtptest"; + kAuthSchemes = ["CRAM-MD5", "PLAIN", "LOGIN"]; + kCapabilities = ["8BITMIME", "SIZE", "CLIENTID"]; + _nextAuthFunction = undefined; + + constructor(daemon) { + this._daemon = daemon; + this.closing = false; + this.dropOnAuthFailure = false; + + this._kAuthSchemeStartFunction = { + "CRAM-MD5": this.authCRAMStart, + PLAIN: this.authPLAINStart, + LOGIN: this.authLOGINStart, + }; + + this.resetTest(); + } + + resetTest() { + this._state = this.kAuthRequired ? kStateAuthNeeded : kStateAuthOptional; + this._nextAuthFunction = undefined; + this._multiline = false; + this.expectingData = false; + this._daemon.post = ""; + } + EHLO(args) { + var capa = "250-fakeserver greets you"; + if (this.kCapabilities.length > 0) { + capa += "\n250-" + this.kCapabilities.join("\n250-"); + } + if (this.kAuthSchemes.length > 0) { + capa += "\n250-AUTH " + this.kAuthSchemes.join(" "); + } + capa += "\n250 HELP"; // the odd one: no "-", per RFC 2821 + return capa; + } + CLIENTID(args) { + return "250 ok"; + } + AUTH(lineRest) { + if (this._state == kStateAuthenticated) { + return "503 You're already authenticated"; + } + var args = lineRest.split(" "); + var scheme = args[0].toUpperCase(); + // |scheme| contained in |kAuthSchemes|? + if ( + !this.kAuthSchemes.some(function (s) { + return s == scheme; + }) + ) { + return "504 AUTH " + scheme + " not supported"; + } + var func = this._kAuthSchemeStartFunction[scheme]; + if (!func || typeof func != "function") { + return ( + "504 I just pretended to implement AUTH " + scheme + ", but I don't" + ); + } + dump("Starting AUTH " + scheme + "\n"); + return func.call(this, args.length > 1 ? args[1] : undefined); + } + MAIL(args) { + if (this._state == kStateAuthNeeded) { + return "530 5.7.0 Authentication required"; + } + return "250 ok"; + } + RCPT(args) { + if (this._state == kStateAuthNeeded) { + return "530 5.7.0 Authentication required"; + } + return "250 ok"; + } + DATA(args) { + if (this._state == kStateAuthNeeded) { + return "530 5.7.0 Authentication required"; + } + this.expectingData = true; + this._daemon.post = ""; + return "354 ok\n"; + } + RSET(args) { + return "250 ok\n"; + } + VRFY(args) { + if (this._state == kStateAuthNeeded) { + return "530 5.7.0 Authentication required"; + } + return "250 ok\n"; + } + EXPN(args) { + return "250 ok\n"; + } + HELP(args) { + return "211 ok\n"; + } + NOOP(args) { + return "250 ok\n"; + } + QUIT(args) { + this.closing = true; + return "221 done"; + } + onStartup() { + this.closing = false; + return "220 ok"; + } + + /** + * AUTH implementations + * + * @see RFC 4954 + */ + authPLAINStart(lineRest) { + if (lineRest) { + // all in one command, called initial client response, see RFC 4954 + return this.authPLAINCred(lineRest); + } + + this._nextAuthFunction = this.authPLAINCred; + this._multiline = true; + + return "334 "; + } + authPLAINCred(line) { + var req = AuthPLAIN.decodeLine(line); + if (req.username == this.kUsername && req.password == this.kPassword) { + this._state = kStateAuthenticated; + return "235 2.7.0 Hello friend! Friends give friends good advice: Next time, use CRAM-MD5"; + } + if (this.dropOnAuthFailure) { + this.closing = true; + } + return "535 5.7.8 Wrong username or password, crook!"; + } + + authCRAMStart(lineRest) { + this._nextAuthFunction = this.authCRAMDigest; + this._multiline = true; + + this._usedCRAMMD5Challenge = AuthCRAM.createChallenge("localhost"); + return "334 " + this._usedCRAMMD5Challenge; + } + authCRAMDigest(line) { + var req = AuthCRAM.decodeLine(line); + var expectedDigest = AuthCRAM.encodeCRAMMD5( + this._usedCRAMMD5Challenge, + this.kPassword + ); + if (req.username == this.kUsername && req.digest == expectedDigest) { + this._state = kStateAuthenticated; + return "235 2.7.0 Hello friend!"; + } + if (this.dropOnAuthFailure) { + this.closing = true; + } + return "535 5.7.8 Wrong username or password, crook!"; + } + + authLOGINStart(lineRest) { + this._nextAuthFunction = this.authLOGINUsername; + this._multiline = true; + + return "334 " + btoa("Username:"); + } + authLOGINUsername(line) { + var req = AuthLOGIN.decodeLine(line); + if (req == this.kUsername) { + this._nextAuthFunction = this.authLOGINPassword; + } else { + // Don't return error yet, to not reveal valid usernames. + this._nextAuthFunction = this.authLOGINBadUsername; + } + this._multiline = true; + return "334 " + btoa("Password:"); + } + authLOGINBadUsername(line) { + if (this.dropOnAuthFailure) { + this.closing = true; + } + return "535 5.7.8 Wrong username or password, crook!"; + } + authLOGINPassword(line) { + var req = AuthLOGIN.decodeLine(line); + if (req == this.kPassword) { + this._state = kStateAuthenticated; + return "235 2.7.0 Hello friend! Where did you pull out this old auth scheme?"; + } + if (this.dropOnAuthFailure) { + this.closing = true; + } + return "535 5.7.8 Wrong username or password, crook!"; + } + + onError(command, args) { + return "500 Command " + command + " not recognized\n"; + } + onServerFault(e) { + return "451 Internal server error: " + e; + } + onMultiline(line) { + if (this._nextAuthFunction) { + var func = this._nextAuthFunction; + this._multiline = false; + this._nextAuthFunction = undefined; + if (line == "*") { + // abort, per RFC 4954 and others + return "501 Okay, as you wish. Chicken"; + } + if (!func || typeof func != "function") { + return "451 I'm lost. Internal server error during auth"; + } + try { + return func.call(this, line); + } catch (e) { + return "451 " + e; + } + } + if (line == ".") { + if (this.expectingData) { + this.expectingData = false; + return "250 Wonderful article, your style is gorgeous!"; + } + return "503 Huch? How did you get here?"; + } + + if (this.expectingData) { + if (line.startsWith(".")) { + line = line.substring(1); + } + // This uses CR LF to match with the specification + this._daemon.post += line + "\r\n"; + } + return undefined; + } + postCommand(reader) { + if (this.closing) { + reader.closeSocket(); + } + reader.setMultiline(this._multiline || this.expectingData); + } +} -- cgit v1.2.3