Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 021bfbfb authored by Wolf-Martell Montwé's avatar Wolf-Martell Montwé Committed by GitHub
Browse files

Merge pull request #9394 from yantarou/issue-6327

fix(smtp): use "ehlo.thunderbird.net" as EHLO hostname
parents add0bf61 d9daefd0
Loading
Loading
Loading
Loading
+6 −16
Original line number Diff line number Diff line
@@ -29,7 +29,6 @@ import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.IOException
import java.io.OutputStream
import java.net.Inet6Address
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Socket
@@ -46,6 +45,10 @@ private const val SOCKET_SEND_MESSAGE_READ_TIMEOUT = 5 * 60 * 1000 // 5 minutes
private const val SMTP_CONTINUE_REQUEST = 334
private const val SMTP_AUTHENTICATION_FAILURE_ERROR_CODE = 535

// We use "ehlo.thunderbird.net" for privacy reasons,
// see https://ehlo.thunderbird.net/
public const val SMTP_HELLO_NAME = "ehlo.thunderbird.net"

class SmtpTransport(
    serverSettings: ServerSettings,
    private val trustedSocketFactory: TrustedSocketFactory,
@@ -100,8 +103,7 @@ class SmtpTransport(

            readGreeting()

            val helloName = buildHostnameToReport()
            var extensions = sendHello(helloName)
            var extensions = sendHello(SMTP_HELLO_NAME)

            is8bitEncodingAllowed = extensions.containsKey("8BITMIME")
            isEnhancedStatusCodesProvided = extensions.containsKey("ENHANCEDSTATUSCODES")
@@ -123,7 +125,7 @@ class SmtpTransport(
                    outputStream = BufferedOutputStream(tlsSocket.getOutputStream(), 1024)

                    // Now resend the EHLO. Required by RFC2487 Sec. 5.2, and more specifically, Exim.
                    extensions = sendHello(helloName)
                    extensions = sendHello(SMTP_HELLO_NAME)
                    secureConnection = true
                } else {
                    throw MissingCapabilityException("STARTTLS")
@@ -259,18 +261,6 @@ class SmtpTransport(
        }
    }

    private fun buildHostnameToReport(): String {
        val localAddress = socket!!.localAddress

        // We use local IP statically for privacy reasons,
        // see https://github.com/thunderbird/thunderbird-android/pull/3798
        return if (localAddress is Inet6Address) {
            "[IPv6:::1]"
        } else {
            "[127.0.0.1]"
        }
    }

    private fun parseOptionalSizeValue(sizeParameters: List<String>?) {
        if (sizeParameters != null && sizeParameters.isNotEmpty()) {
            val sizeParameter = sizeParameters.first()
+14 −14
Original line number Diff line number Diff line
@@ -45,8 +45,8 @@ class SmtpServerSettingsValidatorTest {
    fun `valid server settings with password should return Success`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250-ENHANCEDSTATUSCODES")
            output("250-AUTH PLAIN LOGIN")
            output("250 HELP")
@@ -85,8 +85,8 @@ class SmtpServerSettingsValidatorTest {
        )
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250-ENHANCEDSTATUSCODES")
            output("250-AUTH PLAIN LOGIN OAUTHBEARER")
            output("250 HELP")
@@ -119,8 +119,8 @@ class SmtpServerSettingsValidatorTest {
    fun `authentication error should return AuthenticationError`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250-ENHANCEDSTATUSCODES")
            output("250-AUTH PLAIN LOGIN")
            output("250 HELP")
@@ -178,8 +178,8 @@ class SmtpServerSettingsValidatorTest {
    fun `missing capability should return MissingServerCapabilityError`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello 127.0.0.1")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 HELP")
            expect("QUIT")
            closeConnection()
@@ -209,8 +209,8 @@ class SmtpServerSettingsValidatorTest {
        trustedSocketFactory.injectClientCertificateError(ClientCertificateError.RetrievalFailure)
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello 127.0.0.1")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250-STARTTLS")
            output("250 HELP")
            expect("STARTTLS")
@@ -242,8 +242,8 @@ class SmtpServerSettingsValidatorTest {
        trustedSocketFactory.injectClientCertificateError(ClientCertificateError.CertificateExpired)
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello 127.0.0.1")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250-STARTTLS")
            output("250 HELP")
            expect("STARTTLS")
@@ -275,8 +275,8 @@ class SmtpServerSettingsValidatorTest {
        fakeTrustManager.shouldThrowException = true
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello 127.0.0.1")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250-STARTTLS")
            output("250 HELP")
            expect("STARTTLS")
+51 −51
Original line number Diff line number Diff line
@@ -50,8 +50,8 @@ class SmtpTransportTest {
    fun `open() should issue EHLO command`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 OK")
        }
        val transport = startServerAndCreateSmtpTransportWithoutAuthentication(server)
@@ -66,8 +66,8 @@ class SmtpTransportTest {
    fun `open() without AUTH LOGIN extension should connect when not using authentication`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 OK")
        }
        val transport = startServerAndCreateSmtpTransportWithoutAuthentication(server)
@@ -82,8 +82,8 @@ class SmtpTransportTest {
    fun `open() with AUTH PLAIN extension`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH PLAIN LOGIN")
            expect("AUTH PLAIN AHVzZXIAcGFzc3dvcmQ=")
            output("235 2.7.0 Authentication successful")
@@ -100,8 +100,8 @@ class SmtpTransportTest {
    fun `open() with AUTH LOGIN extension`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH LOGIN")
            expect("AUTH LOGIN")
            output("250 OK")
@@ -122,8 +122,8 @@ class SmtpTransportTest {
    fun `open() without LOGIN and PLAIN AUTH extensions should throw`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH")
            expect("QUIT")
            output("221 BYE")
@@ -143,8 +143,8 @@ class SmtpTransportTest {
    fun `open() with CRAM-MD5 AUTH extension`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH CRAM-MD5")
            expect("AUTH CRAM-MD5")
            output("334 " + Base64.encode("<24609.1047914046@localhost>"))
@@ -163,8 +163,8 @@ class SmtpTransportTest {
    fun `open() without CRAM-MD5 AUTH extension should throw`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH PLAIN LOGIN")
            expect("QUIT")
            output("221 BYE")
@@ -184,8 +184,8 @@ class SmtpTransportTest {
    fun `open() with OAUTHBEARER method`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH OAUTHBEARER")
            expect("AUTH OAUTHBEARER bixhPXVzZXIsAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE=")
            output("235 2.7.0 Authentication successful")
@@ -202,8 +202,8 @@ class SmtpTransportTest {
    fun `open() with OAUTHBEARER method when XOAUTH2 method is also available`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH XOAUTH2 OAUTHBEARER")
            expect("AUTH OAUTHBEARER bixhPXVzZXIsAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE=")
            output("235 2.7.0 Authentication successful")
@@ -220,8 +220,8 @@ class SmtpTransportTest {
    fun `open() with XOAUTH2 extension`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH XOAUTH2")
            expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE=")
            output("235 2.7.0 Authentication successful")
@@ -238,8 +238,8 @@ class SmtpTransportTest {
    fun `open() with XOAUTH2 extension should throw on 401 response`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250-ENHANCEDSTATUSCODES")
            output("250 AUTH XOAUTH2")
            expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE=")
@@ -272,8 +272,8 @@ class SmtpTransportTest {
    fun `open() with XOAUTH2 extension should invalidate and retry on 400 response`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH XOAUTH2")
            expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE=")
            output("334 " + XOAuth2ChallengeParserTestData.STATUS_400_RESPONSE)
@@ -300,8 +300,8 @@ class SmtpTransportTest {
    fun `open() with XOAUTH2 extension should invalidate and retry on invalid JSON response`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH XOAUTH2")
            expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE=")
            output("334 " + XOAuth2ChallengeParserTestData.INVALID_RESPONSE)
@@ -328,8 +328,8 @@ class SmtpTransportTest {
    fun `open() with XOAUTH2 extension should invalidate and retry on missing status JSON response`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH XOAUTH2")
            expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE=")
            output("334 " + XOAuth2ChallengeParserTestData.MISSING_STATUS_RESPONSE)
@@ -356,8 +356,8 @@ class SmtpTransportTest {
    fun `open() with XOAUTH2 extension should throw on multiple failures`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250-ENHANCEDSTATUSCODES")
            output("250 AUTH XOAUTH2")
            expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE=")
@@ -392,8 +392,8 @@ class SmtpTransportTest {
    fun `open() with XOAUTH2 extension should throw on failure to fetch token`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH XOAUTH2")
            expect("QUIT")
            output("221 BYE")
@@ -416,8 +416,8 @@ class SmtpTransportTest {
    fun `open() without OAUTHBEARER extension should throw`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH PLAIN LOGIN")
            expect("QUIT")
            output("221 BYE")
@@ -437,8 +437,8 @@ class SmtpTransportTest {
    fun `open() with AUTH EXTERNAL extension`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH EXTERNAL")
            expect("AUTH EXTERNAL dXNlcg==")
            output("235 2.7.0 Authentication successful")
@@ -455,8 +455,8 @@ class SmtpTransportTest {
    fun `open() without AUTH EXTERNAL extension should throw`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH")
            expect("QUIT")
            output("221 BYE")
@@ -476,9 +476,9 @@ class SmtpTransportTest {
    fun `open() with EHLO failing should try HELO`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("502 5.5.1, Unrecognized command.")
            expect("HELO [127.0.0.1]")
            expect("HELO " + SMTP_HELLO_NAME)
            output("250 localhost")
        }
        val transport = startServerAndCreateSmtpTransportWithoutAuthentication(server)
@@ -493,8 +493,8 @@ class SmtpTransportTest {
    fun `open() with support for ENHANCEDSTATUSCODES should throw strip enhanced status codes from error message`() {
        val server = MockSmtpServer()
        server.output("220 localhost Simple Mail Transfer Service Ready")
        server.expect("EHLO [127.0.0.1]")
        server.output("250-localhost Hello client.localhost")
        server.expect("EHLO " + SMTP_HELLO_NAME)
        server.output("250-localhost Hello " + SMTP_HELLO_NAME)
        server.output("250-ENHANCEDSTATUSCODES")
        server.output("250 AUTH XOAUTH2")
        server.expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE=")
@@ -526,7 +526,7 @@ class SmtpTransportTest {
    fun `open() with many extensions should parse all`() {
        val server = MockSmtpServer().apply {
            output("220 smtp.gmail.com ESMTP x25sm19117693wrx.27 - gsmtp")
            expect("EHLO [127.0.0.1]")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-smtp.gmail.com at your service, [86.147.34.216]")
            output("250-SIZE 35882577")
            output("250-8BITMIME")
@@ -550,15 +550,15 @@ class SmtpTransportTest {
    fun `open() with STARTTLS`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello 127.0.0.1")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250-STARTTLS")
            output("250 HELP")
            expect("STARTTLS")
            output("220 Ready to start TLS")
            startTls()
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello 127.0.0.1")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 AUTH PLAIN LOGIN")
            expect("AUTH PLAIN AHVzZXIAcGFzc3dvcmQ=")
            output("235 2.7.0 Authentication successful")
@@ -579,8 +579,8 @@ class SmtpTransportTest {
    fun `open() with STARTTLS but without STARTTLS capability should throw`() {
        val server = MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello 127.0.0.1")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)
            output("250 HELP")
            expect("QUIT")
            closeConnection()
@@ -940,8 +940,8 @@ class SmtpTransportTest {
    private fun createServerAndSetupForPlainAuthentication(vararg extensions: String): MockSmtpServer {
        return MockSmtpServer().apply {
            output("220 localhost Simple Mail Transfer Service Ready")
            expect("EHLO [127.0.0.1]")
            output("250-localhost Hello client.localhost")
            expect("EHLO " + SMTP_HELLO_NAME)
            output("250-localhost Hello " + SMTP_HELLO_NAME)

            for (extension in extensions) {
                output("250-$extension")