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

Commit a2b545fc authored by Philip Whitehouse's avatar Philip Whitehouse Committed by cketti
Browse files

SMTP: Further test coverage for various cases

parent d1904900
Loading
Loading
Loading
Loading
+0 −2
Original line number Diff line number Diff line
@@ -532,10 +532,8 @@ public class SmtpTransport extends Transport {
            entireMessageSent = true; // After the "\r\n." is attempted, we may have sent the message
            executeSimpleCommand(".");
        } catch (NegativeSmtpReplyException e) {
            e.printStackTrace();
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            MessagingException me = new MessagingException("Unable to send message", e);
            me.setPermanentFailure(entireMessageSent);

+268 −7
Original line number Diff line number Diff line
@@ -2,6 +2,8 @@ package com.fsck.k9.mail.transport;

import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.AuthenticationFailedException;
import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
@@ -12,6 +14,7 @@ import com.fsck.k9.mail.ssl.TrustedSocketFactory;
import com.fsck.k9.mail.store.StoreConfig;
import com.fsck.k9.mail.transport.mockServer.MockSmtpServer;
import com.fsck.k9.mail.transport.mockServer.TestMessage;
import com.fsck.k9.mailstore.BinaryMemoryBody;
import com.fsck.k9.testHelpers.TestTrustedSocketFactory;

import org.junit.Before;
@@ -24,6 +27,12 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@@ -86,7 +95,6 @@ public class SmtpTransportTest {
        server.output("220 localhost Simple Mail Transfer Service Ready");
        server.expect("EHLO localhost");
        server.output("250-localhost Hello client.localhost");
        server.output("250-SIZE 1000000");
        for (String extension: extensions) {
            server.output("250-"+extension);
        }
@@ -115,6 +123,26 @@ public class SmtpTransportTest {
        new SmtpTransport(storeConfig, trustedSocketFactory);
    }

    @Test
    public void open_withNoSecurityOrPasswordPlainAuth_connectsToServer_withoutLogin()
            throws MessagingException, IOException, InterruptedException {
        username = "user";
        authenticationType = AuthType.PLAIN;
        connectionSecurity = ConnectionSecurity.NONE;

        MockSmtpServer server = new MockSmtpServer();
        server.output("220 localhost Simple Mail Transfer Service Ready");
        server.expect("EHLO localhost");
        server.output("250-localhost Hello client.localhost");
        server.output("250 OK");

        SmtpTransport transport = startServerAndCreateSmtpTransport(server);
        transport.open();

        server.verifyConnectionStillOpen();
        server.verifyInteractionCompleted();
    }

    @Test
    public void open_withNoSecurityPlainAuth_connectsToServer()
            throws MessagingException, IOException, InterruptedException {
@@ -127,8 +155,7 @@ public class SmtpTransportTest {
        server.output("220 localhost Simple Mail Transfer Service Ready");
        server.expect("EHLO localhost");
        server.output("250-localhost Hello client.localhost");
        server.output("250-SIZE 1000000");
        server.output("250 AUTH LOGIN PLAIN CRAM-MD5");
        server.output("250 AUTH PLAIN LOGIN");
        server.expect("AUTH PLAIN AHVzZXIAcGFzc3dvcmQ=");
        server.output("235 2.7.0 Authentication successful");

@@ -139,6 +166,60 @@ public class SmtpTransportTest {
        server.verifyInteractionCompleted();
    }

    @Test
    public void open_withNoSecurityPlainAuth_usesLoginIfPlainUnavailable()
            throws MessagingException, IOException, InterruptedException {
        username = "user";
        password = "password";
        authenticationType = AuthType.PLAIN;
        connectionSecurity = ConnectionSecurity.NONE;

        MockSmtpServer server = new MockSmtpServer();
        server.output("220 localhost Simple Mail Transfer Service Ready");
        server.expect("EHLO localhost");
        server.output("250-localhost Hello client.localhost");
        server.output("250 AUTH LOGIN");
        server.expect("AUTH LOGIN");
        server.output("250 OK");
        server.expect("dXNlcg==");
        server.output("250 OK");
        server.expect("cGFzc3dvcmQ=");
        server.output("235 2.7.0 Authentication successful");

        SmtpTransport transport = startServerAndCreateSmtpTransport(server);
        transport.open();

        server.verifyConnectionStillOpen();
        server.verifyInteractionCompleted();
    }

    @Test
    public void open_withNoSecurityPlainAuth_withNeither_throwsException()
            throws MessagingException, IOException, InterruptedException {
        username = "user";
        password = "password";
        authenticationType = AuthType.PLAIN;
        connectionSecurity = ConnectionSecurity.NONE;

        MockSmtpServer server = new MockSmtpServer();
        server.output("220 localhost Simple Mail Transfer Service Ready");
        server.expect("EHLO localhost");
        server.output("250-localhost Hello client.localhost");
        server.output("250 AUTH");

        try {
            SmtpTransport transport = startServerAndCreateSmtpTransport(server);
            transport.open();
            fail("Exception expected");
        } catch (MessagingException e) {
            assertEquals("Authentication methods SASL PLAIN and LOGIN are unavailable.",
                    e.getMessage());
        }

        server.verifyConnectionStillOpen();
        server.verifyInteractionCompleted();
    }

    @Test
    public void open_withNoSecurityCramMd5Auth_connectsToServer()
            throws MessagingException, IOException, InterruptedException {
@@ -151,8 +232,7 @@ public class SmtpTransportTest {
        server.output("220 localhost Simple Mail Transfer Service Ready");
        server.expect("EHLO localhost");
        server.output("250-localhost Hello client.localhost");
        server.output("250-SIZE 1000000");
        server.output("250 AUTH LOGIN PLAIN CRAM-MD5");
        server.output("250 AUTH CRAM-MD5");
        server.expect("AUTH CRAM-MD5");
        server.output(Base64.encode("<24609.1047914046@localhost>"));
        server.expect("dXNlciA3NmYxNWEzZmYwYTNiOGI1NzcxZmNhODZlNTcyMDk2Zg==");
@@ -165,6 +245,33 @@ public class SmtpTransportTest {
        server.verifyInteractionCompleted();
    }

    @Test
    public void open_withNoSecurityCramMd5Auth_withNoSupport_throwsException()
            throws MessagingException, IOException, InterruptedException {
        username = "user";
        password = "password";
        authenticationType = AuthType.CRAM_MD5;
        connectionSecurity = ConnectionSecurity.NONE;

        MockSmtpServer server = new MockSmtpServer();
        server.output("220 localhost Simple Mail Transfer Service Ready");
        server.expect("EHLO localhost");
        server.output("250-localhost Hello client.localhost");
        server.output("250 AUTH PLAIN LOGIN");

        try {
            SmtpTransport transport = startServerAndCreateSmtpTransport(server);
            transport.open();
            fail("Exception expected");
        } catch (MessagingException e) {
            assertEquals("Authentication method CRAM-MD5 is unavailable.",
                    e.getMessage());
        }

        server.verifyConnectionStillOpen();
        server.verifyInteractionCompleted();
    }

    @Test
    public void open_withNoSecurityExternalAuth_connectsToServer()
            throws MessagingException, IOException, InterruptedException {
@@ -177,7 +284,6 @@ public class SmtpTransportTest {
        server.output("220 localhost Simple Mail Transfer Service Ready");
        server.expect("EHLO localhost");
        server.output("250-localhost Hello client.localhost");
        server.output("250-SIZE 1000000");
        server.output("250 AUTH EXTERNAL");
        server.expect("AUTH EXTERNAL dXNlcg==");
        server.output("235 2.7.0 Authentication successful");
@@ -189,6 +295,104 @@ public class SmtpTransportTest {
        server.verifyInteractionCompleted();
    }

    @Test
    public void open_withNoSecurityExternal_withNoSupport_throwsException()
            throws MessagingException, IOException, InterruptedException {
        username = "user";
        password = "password";
        authenticationType = AuthType.EXTERNAL;
        connectionSecurity = ConnectionSecurity.NONE;

        MockSmtpServer server = new MockSmtpServer();
        server.output("220 localhost Simple Mail Transfer Service Ready");
        server.expect("EHLO localhost");
        server.output("250-localhost Hello client.localhost");
        server.output("250 AUTH");

        try {
            SmtpTransport transport = startServerAndCreateSmtpTransport(server);
            transport.open();
            fail("Exception expected");
        } catch (CertificateValidationException e) {
            assertEquals(CertificateValidationException.Reason.MissingCapability, e.getReason());
        }

        server.verifyConnectionStillOpen();
        server.verifyInteractionCompleted();
    }
    @Test
    public void open_withNoSecurityAutomatic_connectsToServerWithCramMD5IfSupported()
            throws MessagingException, IOException, InterruptedException {
        username = "user";
        password = "password";
        authenticationType = AuthType.AUTOMATIC;
        connectionSecurity = ConnectionSecurity.NONE;

        MockSmtpServer server = new MockSmtpServer();
        server.output("220 localhost Simple Mail Transfer Service Ready");
        server.expect("EHLO localhost");
        server.output("250-localhost Hello client.localhost");
        server.output("250 AUTH CRAM-MD5");
        server.expect("AUTH CRAM-MD5");
        server.output(Base64.encode("<24609.1047914046@localhost>"));
        server.expect("dXNlciA3NmYxNWEzZmYwYTNiOGI1NzcxZmNhODZlNTcyMDk2Zg==");
        server.output("235 2.7.0 Authentication successful");

        SmtpTransport transport = startServerAndCreateSmtpTransport(server);
        transport.open();

        server.verifyConnectionStillOpen();
        server.verifyInteractionCompleted();
    }

    @Test
    public void open_withNoSecurityAutomatic_withCramMD5Unsupported_throwsException()
            throws MessagingException, IOException, InterruptedException {
        username = "user";
        password = "password";
        authenticationType = AuthType.AUTOMATIC;
        connectionSecurity = ConnectionSecurity.NONE;

        MockSmtpServer server = new MockSmtpServer();
        server.output("220 localhost Simple Mail Transfer Service Ready");
        server.expect("EHLO localhost");
        server.output("250-localhost Hello client.localhost");
        server.output("250 AUTH PLAIN LOGIN");

        try {
            SmtpTransport transport = startServerAndCreateSmtpTransport(server);
            transport.open();
            fail("Exception expected");
        } catch (MessagingException e) {
            assertEquals("Update your outgoing server authentication setting. AUTOMATIC auth. is unavailable.",
                    e.getMessage());
        }

        server.verifyConnectionStillOpen();
        server.verifyInteractionCompleted();
    }

    @Test
    public void open_triesHELO_whenServerDoesntSupportEHLO()
            throws MessagingException, IOException, InterruptedException {
        username = "user";
        authenticationType = AuthType.PLAIN;
        connectionSecurity = ConnectionSecurity.NONE;

        MockSmtpServer server = new MockSmtpServer();
        server.output("220 localhost Simple Mail Transfer Service Ready");
        server.expect("EHLO localhost");
        server.output("502 5.5.1, Unrecognized command.");
        server.expect("HELO localhost");
        server.output("250 localhost");

        SmtpTransport transport = startServerAndCreateSmtpTransport(server);
        transport.open();

        server.verifyConnectionStillOpen();
        server.verifyInteractionCompleted();
    }

    @Test
    public void sendMessage_withNoAddressToSendTo_doesntOpenConnection()
            throws MessagingException, IOException, InterruptedException {
@@ -257,4 +461,61 @@ public class SmtpTransportTest {
        SmtpTransport transport = startServerAndCreateSmtpTransport(server);
        transport.sendMessage(message);
    }

    @Test
    public void sendMessage_withMessageTooLarge_throwsException()
            throws MessagingException, IOException, InterruptedException {
        extensions.add("SIZE 1000");
        TestMessage message = new TestMessage();
        message.setFrom(new Address("user@localhost"));
        message.setRecipients(Message.RecipientType.TO, new Address[]{new Address("user2@localhost")});
        message.setAttachmentCount(1);
        message.setBody(new BinaryMemoryBody(new byte[1001], "US-ASCII"));

        MockSmtpServer server = new MockSmtpServer();
        setupConnectAndPlainAuthentication(server);

        SmtpTransport transport = startServerAndCreateSmtpTransport(server);

        try {
            transport.sendMessage(message);
            fail("Expected message too large error");
        } catch (MessagingException e) {
            assertTrue(e.isPermanentFailure());
            assertEquals("Message too large for server", e.getMessage());
        }
    }

    @Test
    public void sendMessage_withNegativeReply_throwsException()
            throws MessagingException, IOException, InterruptedException {
        TestMessage message = new TestMessage();
        message.setFrom(new Address("user@localhost"));
        message.setRecipients(Message.RecipientType.TO, new Address[]{new Address("user2@localhost")});

        MockSmtpServer server = new MockSmtpServer();
        setupConnectAndPlainAuthentication(server);
        server.expect("MAIL FROM:<user@localhost>");
        server.output("250 OK");
        server.expect("RCPT TO:<user2@localhost>");
        server.output("250 OK");
        server.expect("DATA");
        server.output("354 End data with <CR><LF>.<CR><LF>");
        server.expect("");
        server.expect(".");
        server.output("421 4.7.0 Temporary system problem");
        server.expect("QUIT");
        server.output("221 BYE");
        server.closeConnection();

        SmtpTransport transport = startServerAndCreateSmtpTransport(server);

        try {
            transport.sendMessage(message);
            fail("Expected exception");
        } catch (SmtpTransport.NegativeSmtpReplyException e) {
            assertEquals(421, e.getReplyCode());
            assertEquals("4.7.0 Temporary system problem", e.getReplyText());
        }
    }
}
+37 −0
Original line number Diff line number Diff line
package com.fsck.k9.mail.transport;

import android.annotation.SuppressLint;

import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.ServerSettings;
@@ -8,6 +10,7 @@ import org.junit.Test;

import static org.junit.Assert.assertEquals;

@SuppressLint("AuthLeak")
public class SmtpTransportUriTest {

    @Test
@@ -37,6 +40,33 @@ public class SmtpTransportUriTest {
        assertEquals("password", result.password);
    }

    @Test
    public void decodeUri_canDecodeUsername_withNoAuthType() {
        String storeUri = "smtp://user:password@server:123456";

        ServerSettings result = SmtpTransport.decodeUri(storeUri);

        assertEquals("user", result.username);
    }

    @Test
    public void decodeUri_canDecodeUsername_withNoPasswordOrAuthType() {
        String storeUri = "smtp://user@server:123456";

        ServerSettings result = SmtpTransport.decodeUri(storeUri);

        assertEquals("user", result.username);
    }

    @Test
    public void decodeUri_canDecodeAuthType_withEmptyPassword() {
        String storeUri = "smtp://user::PLAIN@server:123456";

        ServerSettings result = SmtpTransport.decodeUri(storeUri);

        assertEquals(AuthType.PLAIN, result.authenticationType);
    }

    @Test
    public void decodeUri_canDecodeHost() {
        String storeUri = "smtp://user:password:PLAIN@server:123456";
@@ -82,6 +112,13 @@ public class SmtpTransportUriTest {
        assertEquals("clientCert", result.clientCertificateAlias);
    }

    @Test(expected = IllegalArgumentException.class)
    public void decodeUri_forUnknownSchema_throwsIllegalArgumentException() {
        String storeUri = "unknown://user:clientCert:EXTERNAL@server:123456";

        ServerSettings result = SmtpTransport.decodeUri(storeUri);
    }

    @Test
    public void createUri_canEncodeSmtpSslUri() {
        ServerSettings serverSettings = new ServerSettings(
+4 −0
Original line number Diff line number Diff line
@@ -59,6 +59,10 @@ public class TestMessage extends MimeMessage {
        return (mAttachmentCount > 0);
    }

    public void setAttachmentCount(int i) {
        mAttachmentCount = i;
    }

    public int getAttachmentCount() {
        return mAttachmentCount;
    }