Skip to content

Commit

Permalink
Issue 461: Added two line authentication support for XOAUTH2 connecti…
Browse files Browse the repository at this point in the history
…ons (#477)

Signed-off-by: Mateusz Marzecki <mmarzecki@atlassian.com>

Co-authored-by: Mateusz Marzecki <mmarzecki@atlassian.com>
Co-authored-by: Lukas Jungmann <lukas.jungmann@oracle.com>
  • Loading branch information
3 people committed Jan 18, 2021
1 parent 7cf16f1 commit ca3bb5c
Show file tree
Hide file tree
Showing 3 changed files with 234 additions and 11 deletions.
76 changes: 67 additions & 9 deletions mail/src/main/java/com/sun/mail/pop3/Protocol.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import java.util.logging.Level;
import java.nio.charset.StandardCharsets;
import javax.net.ssl.SSLSocket;

import com.sun.mail.auth.Ntlm;
import com.sun.mail.util.ASCIIUtility;
import com.sun.mail.util.BASE64DecoderStream;
Expand All @@ -44,7 +43,7 @@ class Response {
}

/**
* This class provides a POP3 connection and implements
* This class provides a POP3 connection and implements
* the POP3 protocol requests.
*
* APOP support courtesy of "chamness".
Expand Down Expand Up @@ -76,7 +75,7 @@ class Protocol {
// sometimes the returned size isn't quite big enough
private static final int SLOP = 128;

/**
/**
* Open a connection to the POP3 server.
*/
Protocol(String host, int port, MailLogger logger,
Expand Down Expand Up @@ -393,7 +392,7 @@ synchronized String login(String user, String password)
}

/**
* Gets the APOP message digest.
* Gets the APOP message digest.
* From RFC 1939:
*
* The 'digest' parameter is calculated by applying the MD5
Expand Down Expand Up @@ -446,6 +445,25 @@ boolean enabled() {
return enabled;
}

/**
* Run authentication query based on command and initial response
*
* @param command - command passed to server
* @param ir - initial response, part of the query
* @throws IOException
*/
protected void runAuthenticationCommand(String command, String ir) throws IOException {
if (logger.isLoggable(Level.FINE)) {
logger.fine(command + " using one line authentication format");
}

if (ir != null) {
resp = simpleCommand(command + " " + (ir.length() == 0 ? "=" : ir));
} else {
resp = simpleCommand(command);
}
}

/**
* Start the authentication handshake by issuing the AUTH command.
* Delegate to the doAuth method to do the mechanism-specific
Expand All @@ -461,11 +479,8 @@ boolean authenticate(String host, String authzid,
logger.fine("AUTH " + mech + " command trace suppressed");
suspendTracing();
}
if (ir != null)
resp = simpleCommand("AUTH " + mech + " " +
(ir.length() == 0 ? "=" : ir));
else
resp = simpleCommand("AUTH " + mech);

runAuthenticationCommand("AUTH " + mech, ir);

if (resp.cont)
doAuth(host, authzid, user, passwd);
Expand Down Expand Up @@ -681,6 +696,26 @@ String getInitialResponse(String host, String authzid, String user,
return ASCIIUtility.toString(b);
}

@Override
protected void runAuthenticationCommand(String command, String ir) throws IOException {
Boolean isTwoLineAuthenticationFormat = getBoolProp(
props,
prefix + ".xoauth.two.line.authentication.format");

if (isTwoLineAuthenticationFormat) {
if (logger.isLoggable(Level.FINE)) {
logger.fine(command + " using two line authentication format");
}

resp = twoLinesCommand(
command,
(ir.length() == 0 ? "=" : ir)
);
} else {
super.runAuthenticationCommand(command, ir);
}
}

@Override
void doAuth(String host, String authzid, String user, String passwd)
throws IOException {
Expand Down Expand Up @@ -1120,6 +1155,29 @@ private Response simpleCommand(String cmd) throws IOException {
return r;
}

/**
* Issue a two line POP3 command and return the response
* Refer to {@link #simpleCommand(String)} for a single line command
*
* @param firstCommand first command we want to pass to server e.g AUTH XOAUTH2
* @param secondCommand second command e.g Base64 encoded authorization string
* @return Response
* @throws IOException
*/
private Response twoLinesCommand(String firstCommand, String secondCommand) throws IOException {
String cmd = firstCommand + " " + secondCommand;

batchCommandStart(cmd);
simpleCommand(firstCommand);
batchCommandContinue(cmd);

Response r = simpleCommand(secondCommand);

batchCommandEnd();

return r;
}

/**
* Send the specified command.
*/
Expand Down
22 changes: 21 additions & 1 deletion mail/src/test/java/com/sun/mail/pop3/POP3Handler.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,9 @@ public void handleCommand() throws IOException {
} else if (commandName.equals("PASS")) {
this.pass();
} else if (commandName.equals("CAPA")) {
this.println("-ERR CAPA not supported");
this.capa();
} else if (commandName.equals("AUTH")) {
this.auth();
} else {
LOGGER.log(Level.SEVERE, "ERROR command unknown: {0}", commandName);
this.println("-ERR unknown command");
Expand Down Expand Up @@ -276,4 +278,22 @@ public void user() throws IOException {
public void pass() throws IOException {
this.println("+OK");
}

/**
* CAPA command
*
* @throws IOException unable to write to socket
*/
public void capa() throws IOException {
this.println("-ERR CAPA not supported");
}

/**
* AUTH command
*
* @throws IOException unable to write to socket
*/
public void auth() throws IOException {
this.println("-ERR AUTH not supported");
}
}
147 changes: 146 additions & 1 deletion mail/src/test/java/com/sun/mail/pop3/POP3StoreTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2009, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2009, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand All @@ -19,13 +19,15 @@
import java.io.IOException;
import java.util.Properties;

import jakarta.mail.AuthenticationFailedException;
import jakarta.mail.Folder;
import jakarta.mail.Session;
import jakarta.mail.Store;

import com.sun.mail.test.TestServer;

import org.junit.Test;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;

Expand Down Expand Up @@ -111,6 +113,149 @@ public void testApopNotSupported() {
}
}

/**
* Check whether POP3 XOAUTH2 connection can be established using single line authentication format (default)
*/
@Test
public void testXOAUTH2POP3Connection() {
TestServer server = null;

try {
final POP3Handler handler = new POP3HandlerXOAUTH();
server = new TestServer(handler);
server.start();

final Properties properties = new Properties();
properties.setProperty("mail.pop3.host", "localhost");
properties.setProperty("mail.pop3.port", "" + server.getPort());
properties.setProperty("mail.pop3.auth.mechanisms", "XOAUTH2");

final Session session = Session.getInstance(properties);

final POP3Store store = (POP3Store) session.getStore("pop3");
try {
store.protocolConnect("localhost", server.getPort(), "test", "test");
} catch (Exception ex) {
System.out.println(ex);
ex.printStackTrace();
fail(ex.toString());
} finally {
store.close();
}
} catch (final Exception e) {
e.printStackTrace();
fail(e.getMessage());
} finally {
if (server != null) {
server.quit();
}
}
}

/**
* Check whether POP3 XOAUTH2 connection can be established using single line authentication format
* when the authentication format has ben set
* using: mail.pop3.xoauth.two.line.authentication.format property
*/
@Test
public void testXOAUTH2POP3ConnectionWithSingleLineAuthenticationFlag() {
TestServer server = null;

try {
final POP3Handler handler = new POP3HandlerXOAUTH();
server = new TestServer(handler);
server.start();

final Properties properties = new Properties();
properties.setProperty("mail.pop3.host", "localhost");
properties.setProperty("mail.pop3.port", "" + server.getPort());
properties.setProperty("mail.pop3.auth.mechanisms", "XOAUTH2");
properties.setProperty("mail.pop3.disablecapa", "false");
properties.setProperty("mail.pop3.xoauth.two.line.authentication.format", "false");

final Session session = Session.getInstance(properties);

final POP3Store store = (POP3Store) session.getStore("pop3");
try {
store.protocolConnect("localhost", server.getPort(), "test", "test");
} catch (Exception ex) {
System.out.println(ex);
ex.printStackTrace();
fail(ex.toString());
} finally {
store.close();
}
} catch (final Exception e) {
e.printStackTrace();
fail(e.getMessage());
} finally {
if (server != null) {
server.quit();
}
}
}

/**
* Check whether POP3 XOAUTH2 authentication method is invoked using two line authentication format
* using: mail.pop3.xoauth.two.line.authentication.format property
*/
@Test
public void testXOAUTH2POP3ConnectionWithTwoLineAuthenticationFlag() {
TestServer server = null;

try {
final POP3Handler handler = new POP3HandlerXOAUTH();
server = new TestServer(handler);
server.start();

final Properties properties = new Properties();
properties.setProperty("mail.pop3.host", "localhost");
properties.setProperty("mail.pop3.port", "" + server.getPort());
properties.setProperty("mail.pop3.auth.mechanisms", "XOAUTH2");
properties.setProperty("mail.pop3.disablecapa", "false");
properties.setProperty("mail.pop3.xoauth.two.line.authentication.format", "true");

final Session session = Session.getInstance(properties);

final POP3Store store = (POP3Store) session.getStore("pop3");
try {
store.protocolConnect("localhost", server.getPort(), "test", "test");
} catch (Exception ex) {
assertTrue(ex instanceof AuthenticationFailedException);
assertTrue("We are expecting an exception here as the test server " +
"do not allow for two lane authentication format ", ex.toString().contains("unknown command"));
} finally {
store.close();
}
} catch (final Exception e) {
e.printStackTrace();
fail(e.getMessage());
} finally {
if (server != null) {
server.quit();
}
}
}

/**
* Custom handler of AUTH command.
*
* @author Mateusz Marzęcki
*/
private static class POP3HandlerXOAUTH extends POP3Handler {
@Override
public void auth() throws IOException {
this.println("+OK POP3 server ready");
}

@Override
public void capa() throws IOException {
this.writer.println("+OK");
this.writer.println("SASL PLAIN XOAUTH2");
this.println(".");
}
}

/**
* Custom handler. Returns ERR for NOOP.
*
Expand Down

0 comments on commit ca3bb5c

Please sign in to comment.