package com.fsck.k9.mail.transport.smtp; import java.io.IOException; import java.net.InetAddress; 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.K9LibRobolectricTestRunner; import com.fsck.k9.mail.Message; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.ServerSettings; import com.fsck.k9.mail.ServerSettings.Type; import com.fsck.k9.mail.XOAuth2ChallengeParserTest; import com.fsck.k9.mail.filter.Base64; import com.fsck.k9.mail.helpers.TestMessageBuilder; import com.fsck.k9.mail.helpers.TestTrustedSocketFactory; import com.fsck.k9.mail.internet.MimeMessage; import com.fsck.k9.mail.oauth.OAuth2TokenProvider; import com.fsck.k9.mail.ssl.TrustedSocketFactory; import com.fsck.k9.mail.store.StoreConfig; import com.fsck.k9.mail.transport.mockServer.MockSmtpServer; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(K9LibRobolectricTestRunner.class) public class SmtpTransportTest { private static final String LOCALHOST_NAME = "localhost"; private static final String USERNAME = "user"; private static final String PASSWORD = "password"; private static final String CLIENT_CERTIFICATE_ALIAS = null; private TrustedSocketFactory socketFactory; private OAuth2TokenProvider oAuth2TokenProvider; @Before public void before() throws AuthenticationFailedException { socketFactory = new TestTrustedSocketFactory(); oAuth2TokenProvider = mock(OAuth2TokenProvider.class); when(oAuth2TokenProvider.getToken(eq(USERNAME), anyInt())) .thenReturn("oldToken").thenReturn("newToken"); } @Test public void SmtpTransport_withValidTransportUri() throws Exception { StoreConfig storeConfig = createStoreConfigWithTransportUri("smtp://user:password:CRAM_MD5@server:123456"); new SmtpTransport(storeConfig, socketFactory, oAuth2TokenProvider); } @Test(expected = MessagingException.class) public void SmtpTransport_withInvalidTransportUri_shouldThrow() throws Exception { StoreConfig storeConfig = createStoreConfigWithTransportUri("smpt://"); new SmtpTransport(storeConfig, socketFactory, oAuth2TokenProvider); } @Test public void open_withoutAuthLoginExtension_shouldConnectWithoutAuthentication() throws Exception { 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 = startServerAndCreateSmtpTransportWithoutPassword(server); transport.open(); server.verifyConnectionStillOpen(); server.verifyInteractionCompleted(); } @Test public void open_withAuthPlainExtension() throws Exception { 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"); server.expect("AUTH PLAIN AHVzZXIAcGFzc3dvcmQ="); server.output("235 2.7.0 Authentication successful"); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.PLAIN, ConnectionSecurity.NONE); transport.open(); server.verifyConnectionStillOpen(); server.verifyInteractionCompleted(); } @Test public void open_withAuthLoginExtension() throws Exception { 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, AuthType.PLAIN, ConnectionSecurity.NONE); transport.open(); server.verifyConnectionStillOpen(); server.verifyInteractionCompleted(); } @Test public void open_withoutLoginAndPlainAuthExtensions_shouldThrow() throws Exception { 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"); server.expect("QUIT"); server.output("221 BYE"); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.PLAIN, ConnectionSecurity.NONE); try { 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_withCramMd5AuthExtension() throws Exception { 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, AuthType.CRAM_MD5, ConnectionSecurity.NONE); transport.open(); server.verifyConnectionStillOpen(); server.verifyInteractionCompleted(); } @Test public void open_withoutCramMd5AuthExtension_shouldThrow() throws Exception { 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"); server.expect("QUIT"); server.output("221 BYE"); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.CRAM_MD5, ConnectionSecurity.NONE); try { transport.open(); fail("Exception expected"); } catch (MessagingException e) { assertEquals("Authentication method CRAM-MD5 is unavailable.", e.getMessage()); } server.verifyConnectionClosed(); server.verifyInteractionCompleted(); } @Test public void open_withXoauth2Extension() throws Exception { 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 XOAUTH2"); server.expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE="); server.output("235 2.7.0 Authentication successful"); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.XOAUTH2, ConnectionSecurity.NONE); transport.open(); server.verifyConnectionStillOpen(); server.verifyInteractionCompleted(); } @Test public void open_withXoauth2Extension_shouldThrowOn401Response() throws Exception { 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 XOAUTH2"); server.expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE="); server.output("334 "+ XOAuth2ChallengeParserTest.STATUS_401_RESPONSE); server.expect(""); server.output("535-5.7.1 Username and Password not accepted. Learn more at"); server.output("535 5.7.1 http://support.google.com/mail/bin/answer.py?answer=14257 hx9sm5317360pbc.68"); server.expect("QUIT"); server.output("221 BYE"); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.XOAUTH2, ConnectionSecurity.NONE); try { transport.open(); fail("Exception expected"); } catch (AuthenticationFailedException e) { assertEquals( "5.7.1 Username and Password not accepted. Learn more at " + "5.7.1 http://support.google.com/mail/bin/answer.py?answer=14257 hx9sm5317360pbc.68", e.getMessage()); } InOrder inOrder = inOrder(oAuth2TokenProvider); inOrder.verify(oAuth2TokenProvider).getToken(eq(USERNAME), anyInt()); inOrder.verify(oAuth2TokenProvider).invalidateToken(USERNAME); server.verifyConnectionClosed(); server.verifyInteractionCompleted(); } @Test public void open_withXoauth2Extension_shouldInvalidateAndRetryOn400Response() throws Exception { 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 XOAUTH2"); server.expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE="); server.output("334 "+ XOAuth2ChallengeParserTest.STATUS_400_RESPONSE); server.expect(""); server.output("535-5.7.1 Username and Password not accepted. Learn more at"); server.output("535 5.7.1 http://support.google.com/mail/bin/answer.py?answer=14257 hx9sm5317360pbc.68"); server.expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG5ld1Rva2VuAQE="); server.output("235 2.7.0 Authentication successful"); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.XOAUTH2, ConnectionSecurity.NONE); transport.open(); InOrder inOrder = inOrder(oAuth2TokenProvider); inOrder.verify(oAuth2TokenProvider).getToken(eq(USERNAME), anyInt()); inOrder.verify(oAuth2TokenProvider).invalidateToken(USERNAME); inOrder.verify(oAuth2TokenProvider).getToken(eq(USERNAME), anyInt()); server.verifyConnectionStillOpen(); server.verifyInteractionCompleted(); } @Test public void open_withXoauth2Extension_shouldInvalidateAndRetryOnInvalidJsonResponse() throws Exception { 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 XOAUTH2"); server.expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE="); server.output("334 "+ XOAuth2ChallengeParserTest.INVALID_RESPONSE); server.expect(""); server.output("535-5.7.1 Username and Password not accepted. Learn more at"); server.output("535 5.7.1 http://support.google.com/mail/bin/answer.py?answer=14257 hx9sm5317360pbc.68"); server.expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG5ld1Rva2VuAQE="); server.output("235 2.7.0 Authentication successful"); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.XOAUTH2, ConnectionSecurity.NONE); transport.open(); InOrder inOrder = inOrder(oAuth2TokenProvider); inOrder.verify(oAuth2TokenProvider).getToken(eq(USERNAME), anyInt()); inOrder.verify(oAuth2TokenProvider).invalidateToken(USERNAME); inOrder.verify(oAuth2TokenProvider).getToken(eq(USERNAME), anyInt()); server.verifyConnectionStillOpen(); server.verifyInteractionCompleted(); } @Test public void open_withXoauth2Extension_shouldInvalidateAndRetryOnMissingStatusJsonResponse() throws Exception { 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 XOAUTH2"); server.expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE="); server.output("334 "+ XOAuth2ChallengeParserTest.MISSING_STATUS_RESPONSE); server.expect(""); server.output("535-5.7.1 Username and Password not accepted. Learn more at"); server.output("535 5.7.1 http://support.google.com/mail/bin/answer.py?answer=14257 hx9sm5317360pbc.68"); server.expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG5ld1Rva2VuAQE="); server.output("235 2.7.0 Authentication successful"); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.XOAUTH2, ConnectionSecurity.NONE); transport.open(); InOrder inOrder = inOrder(oAuth2TokenProvider); inOrder.verify(oAuth2TokenProvider).getToken(eq(USERNAME), anyInt()); inOrder.verify(oAuth2TokenProvider).invalidateToken(USERNAME); inOrder.verify(oAuth2TokenProvider).getToken(eq(USERNAME), anyInt()); server.verifyConnectionStillOpen(); server.verifyInteractionCompleted(); } @Test public void open_withXoauth2Extension_shouldThrowOnMultipleFailure() throws Exception { 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 XOAUTH2"); server.expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE="); server.output("334 " + XOAuth2ChallengeParserTest.STATUS_400_RESPONSE); server.expect(""); server.output("535-5.7.1 Username and Password not accepted. Learn more at"); server.output("535 5.7.1 http://support.google.com/mail/bin/answer.py?answer=14257 hx9sm5317360pbc.68"); server.expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG5ld1Rva2VuAQE="); server.output("334 " + XOAuth2ChallengeParserTest.STATUS_400_RESPONSE); server.expect(""); server.output("535-5.7.1 Username and Password not accepted. Learn more at"); server.output("535 5.7.1 http://support.google.com/mail/bin/answer.py?answer=14257 hx9sm5317360pbc.68"); server.expect("QUIT"); server.output("221 BYE"); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.XOAUTH2, ConnectionSecurity.NONE); try { transport.open(); fail("Exception expected"); } catch (AuthenticationFailedException e) { assertEquals( "5.7.1 Username and Password not accepted. Learn more at " + "5.7.1 http://support.google.com/mail/bin/answer.py?answer=14257 hx9sm5317360pbc.68", e.getMessage()); } server.verifyConnectionClosed(); server.verifyInteractionCompleted(); } @Test public void open_withXoauth2Extension_shouldThrowOnFailure_fetchingToken() throws Exception { 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 XOAUTH2"); server.expect("QUIT"); server.output("221 BYE"); when(oAuth2TokenProvider.getToken(anyString(), anyInt())).thenThrow(new AuthenticationFailedException("Failed to fetch token")); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.XOAUTH2, ConnectionSecurity.NONE); try { transport.open(); fail("Exception expected"); } catch (AuthenticationFailedException e) { assertEquals("Failed to fetch token", e.getMessage()); } server.verifyConnectionClosed(); server.verifyInteractionCompleted(); } @Test public void open_withoutXoauth2Extension_shouldThrow() throws Exception { 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"); server.expect("QUIT"); server.output("221 BYE"); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.XOAUTH2, ConnectionSecurity.NONE); try { transport.open(); fail("Exception expected"); } catch (MessagingException e) { assertEquals("Authentication method XOAUTH2 is unavailable.", e.getMessage()); } server.verifyConnectionClosed(); server.verifyInteractionCompleted(); } @Test public void open_withAuthExternalExtension() throws Exception { 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 EXTERNAL"); server.expect("AUTH EXTERNAL dXNlcg=="); server.output("235 2.7.0 Authentication successful"); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.EXTERNAL, ConnectionSecurity.NONE); transport.open(); server.verifyConnectionStillOpen(); server.verifyInteractionCompleted(); } @Test public void open_withoutAuthExternalExtension_shouldThrow() throws Exception { 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"); server.expect("QUIT"); server.output("221 BYE"); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.EXTERNAL, ConnectionSecurity.NONE); try { transport.open(); fail("Exception expected"); } catch (CertificateValidationException e) { assertEquals(CertificateValidationException.Reason.MissingCapability, e.getReason()); } server.verifyConnectionClosed(); server.verifyInteractionCompleted(); } @Test public void open_withAutomaticAuthAndNoTransportSecurityAndAuthCramMd5Extension_shouldUseAuthCramMd5() throws Exception { 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, AuthType.AUTOMATIC, ConnectionSecurity.NONE); transport.open(); server.verifyConnectionStillOpen(); server.verifyInteractionCompleted(); } @Test public void open_withAutomaticAuthAndNoTransportSecurityAndAuthPlainExtension_shouldThrow() throws Exception { 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"); server.expect("QUIT"); server.output("221 BYE"); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.AUTOMATIC, ConnectionSecurity.NONE); try { transport.open(); fail("Exception expected"); } catch (MessagingException e) { assertEquals("Update your outgoing server authentication setting. AUTOMATIC auth. is unavailable.", e.getMessage()); } server.verifyConnectionClosed(); server.verifyInteractionCompleted(); } @Test public void open_withEhloFailing_shouldTryHelo() throws Exception { 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 = startServerAndCreateSmtpTransportWithoutPassword(server); transport.open(); server.verifyConnectionStillOpen(); server.verifyInteractionCompleted(); } @Test public void open_withSupportWithEnhancedStatusCodesOnAuthFailure_shouldThrowEncodedMessage() throws Exception { 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-ENHANCEDSTATUSCODES"); server.output("250 AUTH XOAUTH2"); server.expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE="); server.output("334 " + XOAuth2ChallengeParserTest.STATUS_401_RESPONSE); server.expect(""); server.output("535-5.7.1 Username and Password not accepted. Learn more at"); server.output("535 5.7.1 http://support.google.com/mail/bin/answer.py?answer=14257 hx9sm5317360pbc.68"); server.expect("QUIT"); server.output("221 BYE"); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.XOAUTH2, ConnectionSecurity.NONE); try { transport.open(); fail("Exception expected"); } catch (AuthenticationFailedException e) { assertEquals( "Username and Password not accepted. Learn more at http://support.google.com/mail/bin/answer.py?answer=14257 hx9sm5317360pbc.68", e.getMessage()); } InOrder inOrder = inOrder(oAuth2TokenProvider); inOrder.verify(oAuth2TokenProvider).getToken(eq(USERNAME), anyInt()); inOrder.verify(oAuth2TokenProvider).invalidateToken(USERNAME); server.verifyConnectionClosed(); server.verifyInteractionCompleted(); } @Test public void open_withManyExtensions_shouldParseAll() throws Exception { MockSmtpServer server = new MockSmtpServer(); server.output("220 smtp.gmail.com ESMTP x25sm19117693wrx.27 - gsmtp"); server.expect("EHLO localhost"); server.output("250-smtp.gmail.com at your service, [86.147.34.216]"); server.output("250-SIZE 35882577"); server.output("250-8BITMIME"); server.output("250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH"); server.output("250-ENHANCEDSTATUSCODES"); server.output("250-PIPELINING"); server.output("250-CHUNKING"); server.output("250 SMTPUTF8"); server.expect("AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIG9sZFRva2VuAQE="); server.output("235 2.7.0 Authentication successful"); SmtpTransport transport = startServerAndCreateSmtpTransport(server, AuthType.XOAUTH2, ConnectionSecurity.NONE); transport.open(); server.verifyConnectionStillOpen(); server.verifyInteractionCompleted(); } @Test public void sendMessage_withoutAddressToSendTo_shouldNotOpenConnection() throws Exception { MimeMessage message = new MimeMessage(); MockSmtpServer server = createServerAndSetupForPlainAuthentication(); SmtpTransport transport = startServerAndCreateSmtpTransport(server); transport.sendMessage(message); server.verifyConnectionNeverCreated(); } @Test public void sendMessage_withSingleRecipient() throws Exception { Message message = getDefaultMessage(); MockSmtpServer server = createServerAndSetupForPlainAuthentication(); 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("[message data]"); server.expect("."); server.output("250 OK: queued as 12345"); server.expect("QUIT"); server.output("221 BYE"); server.closeConnection(); SmtpTransport transport = startServerAndCreateSmtpTransport(server); transport.sendMessage(message); server.verifyConnectionClosed(); server.verifyInteractionCompleted(); } @Test public void sendMessage_with8BitEncoding() throws Exception { Message message = getDefaultMessage(); MockSmtpServer server = createServerAndSetupForPlainAuthentication("8BITMIME"); server.expect("MAIL FROM:<user@localhost> BODY=8BITMIME"); 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("[message data]"); server.expect("."); server.output("250 OK: queued as 12345"); server.expect("QUIT"); server.output("221 BYE"); server.closeConnection(); SmtpTransport transport = startServerAndCreateSmtpTransport(server); transport.sendMessage(message); server.verifyConnectionClosed(); server.verifyInteractionCompleted(); } @Test public void sendMessage_with8BitEncodingExtensionNotCaseSensitive() throws Exception { Message message = getDefaultMessage(); MockSmtpServer server = createServerAndSetupForPlainAuthentication("8bitmime"); server.expect("MAIL FROM:<user@localhost> BODY=8BITMIME"); 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("[message data]"); server.expect("."); server.output("250 OK: queued as 12345"); server.expect("QUIT"); server.output("221 BYE"); server.closeConnection(); SmtpTransport transport = startServerAndCreateSmtpTransport(server); transport.sendMessage(message); server.verifyConnectionClosed(); server.verifyInteractionCompleted(); } @Test public void sendMessage_withMessageTooLarge_shouldThrow() throws Exception { Message message = getDefaultMessageBuilder() .setHasAttachments(true) .messageSize(1234L) .build(); MockSmtpServer server = createServerAndSetupForPlainAuthentication("SIZE 1000"); 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()); } //FIXME: Make sure connection was closed //server.verifyConnectionClosed(); } @Test public void sendMessage_withNegativeReply_shouldThrow() throws Exception { Message message = getDefaultMessage(); MockSmtpServer server = createServerAndSetupForPlainAuthentication(); 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("[message data]"); 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 (NegativeSmtpReplyException e) { assertEquals(421, e.getReplyCode()); assertEquals("4.7.0 Temporary system problem", e.getReplyText()); } server.verifyConnectionClosed(); server.verifyInteractionCompleted(); } private SmtpTransport startServerAndCreateSmtpTransport(MockSmtpServer server) throws IOException, MessagingException { return startServerAndCreateSmtpTransport(server, AuthType.PLAIN, ConnectionSecurity.NONE); } private SmtpTransport startServerAndCreateSmtpTransportWithoutPassword(MockSmtpServer server) throws IOException, MessagingException { return startServerAndCreateSmtpTransport(server, AuthType.PLAIN, ConnectionSecurity.NONE, null); } private SmtpTransport startServerAndCreateSmtpTransport(MockSmtpServer server, AuthType authenticationType, ConnectionSecurity connectionSecurity) throws IOException, MessagingException { return startServerAndCreateSmtpTransport(server, authenticationType, connectionSecurity, PASSWORD); } private SmtpTransport startServerAndCreateSmtpTransport(MockSmtpServer server, AuthType authenticationType, ConnectionSecurity connectionSecurity, String password) throws IOException, MessagingException { server.start(); String host = server.getHost(); int port = server.getPort(); ServerSettings serverSettings = new ServerSettings( Type.SMTP, host, port, connectionSecurity, authenticationType, USERNAME, password, CLIENT_CERTIFICATE_ALIAS); String uri = SmtpTransport.createUri(serverSettings); StoreConfig storeConfig = createStoreConfigWithTransportUri(uri); return new TestSmtpTransport(storeConfig, socketFactory, oAuth2TokenProvider); } private StoreConfig createStoreConfigWithTransportUri(String value) { StoreConfig storeConfig = mock(StoreConfig.class); when(storeConfig.getTransportUri()).thenReturn(value); return storeConfig; } private TestMessageBuilder getDefaultMessageBuilder() { return new TestMessageBuilder() .from("user@localhost") .to("user2@localhost"); } private Message getDefaultMessage() { return getDefaultMessageBuilder().build(); } private MockSmtpServer createServerAndSetupForPlainAuthentication(String... extensions) { MockSmtpServer server = new MockSmtpServer(); server.output("220 localhost Simple Mail Transfer Service Ready"); server.expect("EHLO localhost"); server.output("250-localhost Hello client.localhost"); for (String extension : extensions) { server.output("250-" + extension); } server.output("250 AUTH LOGIN PLAIN CRAM-MD5"); server.expect("AUTH PLAIN AHVzZXIAcGFzc3dvcmQ="); server.output("235 2.7.0 Authentication successful"); return server; } static class TestSmtpTransport extends SmtpTransport { TestSmtpTransport(StoreConfig storeConfig, TrustedSocketFactory trustedSocketFactory, OAuth2TokenProvider oAuth2TokenProvider) throws MessagingException { super(storeConfig, trustedSocketFactory, oAuth2TokenProvider); } @Override protected String getCanonicalHostName(InetAddress localAddress) { return LOCALHOST_NAME; } } }