Updated Javamail to version 1.6.5

This commit is contained in:
M66B 2020-03-17 12:01:34 +01:00
parent d25834cd27
commit 786fde2ca4
5 changed files with 636 additions and 8 deletions

View File

@ -242,7 +242,7 @@ dependencies {
def exif_version = "1.3.0-alpha01"
def biometric_version = "1.0.1"
def billingclient_version = "2.1.0"
def javamail_version = "1.6.5-SNAPSHOT"
def javamail_version = "1.6.5"
def jsoup_version = "1.12.1"
def dnsjava_version = "2.1.9"
def openpgp_version = "12.0"

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2020 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
@ -16,7 +16,9 @@
package com.sun.mail.pop3;
import java.util.Locale;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.lang.reflect.*;
@ -280,11 +282,16 @@ public class POP3Store extends Store {
supportsUidl = capabilities == null || capabilities.containsKey("UIDL");
String msg = null;
if ((msg = p.login(user, passwd)) != null) {
throw cleanupAndThrow(p, new EOFException(msg));
try {
if (!authenticate(p, user, passwd))
throw cleanupAndThrow(p, new EOFException("login failed"));
} catch (EOFException ex) {
throw cleanupAndThrow(p, ex);
} catch (Exception ex) {
throw cleanupAndThrow(p, new EOFException(ex.getMessage()));
}
/*
* If a Folder closes the port, and then a Folder
* is opened, the Store won't have a port. In that
@ -322,6 +329,117 @@ public class POP3Store extends Store {
return ife;
}
/**
* Authenticate to the server.
*
* XXX - This extensible authentication mechanism scheme was adapted
* from the SMTPTransport class. The work was done at the last
* minute for the 1.6.5 release and so is not as clean as it
* could be. There's great confusion over boolean success/failure
* return codes vs exceptions. This should all be cleaned up at
* some point, and more testing should be done, but I'm leaving
* it in this "I believe it works" state for now. I've tested
* it with LOGIN, PLAIN, and XOAUTH2 mechanisms, the latter being
* the primary motivation for the work right now.
*
* @param p the Protocol object to use
* @param user the user to authenticate as
* @param passwd the password for the user
* @return true if authentication succeeds
* @exception MessagingException if authentication fails
* @since Jakarta Mail 1.6.5
*/
private boolean authenticate(Protocol p, String user, String passwd)
throws MessagingException {
// setting mail.pop3.auth.mechanisms controls which mechanisms will
// be used, and in what order they'll be considered. only the first
// match is used.
String mechs = session.getProperty("mail." + name + ".auth.mechanisms");
boolean usingDefaultMechs = false;
if (mechs == null) {
mechs = p.getDefaultMechanisms();
usingDefaultMechs = true;
}
String authzid =
session.getProperty("mail." + name + ".sasl.authorizationid");
if (authzid == null)
authzid = user;
/*
* XXX - maybe someday
*
if (enableSASL) {
logger.fine("Authenticate with SASL");
try {
if (sasllogin(getSASLMechanisms(), getSASLRealm(), authzid,
user, passwd)) {
return true; // success
} else {
logger.fine("SASL authentication failed");
return false;
}
} catch (UnsupportedOperationException ex) {
logger.log(Level.FINE, "SASL support failed", ex);
// if the SASL support fails, fall back to non-SASL
}
}
*/
if (logger.isLoggable(Level.FINE))
logger.fine("Attempt to authenticate using mechanisms: " + mechs);
/*
* Loop through the list of mechanisms supplied by the user
* (or defaulted) and try each in turn. If the server supports
* the mechanism and we have an authenticator for the mechanism,
* and it hasn't been disabled, use it.
*/
StringTokenizer st = new StringTokenizer(mechs);
while (st.hasMoreTokens()) {
String m = st.nextToken();
m = m.toUpperCase(Locale.ENGLISH);
if (!p.supportsMechanism(m)) {
logger.log(Level.FINE, "no authenticator for mechanism {0}", m);
continue;
}
if (!p.supportsAuthentication(m)) {
logger.log(Level.FINE, "mechanism {0} not supported by server",
m);
continue;
}
/*
* If using the default mechanisms, check if this one is disabled.
*/
if (usingDefaultMechs) {
String dprop = "mail." + name + ".auth." +
m.toLowerCase(Locale.ENGLISH) + ".disable";
boolean disabled = PropUtil.getBooleanProperty(
session.getProperties(),
dprop, !p.isMechanismEnabled(m));
if (disabled) {
if (logger.isLoggable(Level.FINE))
logger.fine("mechanism " + m +
" disabled by property: " + dprop);
continue;
}
}
// only the first supported and enabled mechanism is used
logger.log(Level.FINE, "Using mechanism {0}", m);
String msg =
p.authenticate(m, host, authzid, user, passwd);
if (msg != null)
throw new AuthenticationFailedException(msg);
return true;
}
// if no authentication mechanism found, fail
throw new AuthenticationFailedException(
"No authentication mechanisms supported by both server and client");
}
private static boolean isRecoverable(Throwable t) {
return (t instanceof Exception) || (t instanceof LinkageError);
}

View File

@ -21,8 +21,13 @@ import java.net.*;
import java.io.*;
import java.security.*;
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;
import com.sun.mail.util.BASE64EncoderStream;
import com.sun.mail.util.PropUtil;
import com.sun.mail.util.MailLogger;
import com.sun.mail.util.SocketFetcher;
@ -33,6 +38,7 @@ import com.sun.mail.util.SharedByteArrayOutputStream;
class Response {
boolean ok = false; // true if "+OK"
boolean cont = false; // true if "+ " continuation line
String data = null; // rest of line after "+OK" or "-ERR"
InputStream bytes = null; // all the bytes from a multi-line response
}
@ -61,6 +67,9 @@ class Protocol {
private boolean pipelining;
private boolean noauthdebug = true; // hide auth info in debug output
private boolean traceSuspended; // temporarily suspend tracing
private Map<String, Authenticator> authenticators = new HashMap<>();
private String defaultAuthenticationMechanisms; // set in constructor
private String localHostName;
private static final int POP3_PORT = 110; // standard POP3 port
private static final String CRLF = "\r\n";
@ -117,6 +126,21 @@ class Protocol {
PropUtil.getBooleanProperty(props, prefix + ".pipelining", false);
if (pipelining)
logger.config("PIPELINING enabled");
// created here, because they're inner classes that reference "this"
Authenticator[] a = new Authenticator[] {
new LoginAuthenticator(),
new PlainAuthenticator(),
//new DigestMD5Authenticator(),
new NtlmAuthenticator(),
new OAuth2Authenticator()
};
StringBuilder sb = new StringBuilder();
for (int i = 0; i < a.length; i++) {
authenticators.put(a[i].getMechanism(), a[i]);
sb.append(a[i].getMechanism()).append(' ');
}
defaultAuthenticationMechanisms = sb.toString();
}
private static IOException cleanupAndThrow(Socket socket, IOException ife) {
@ -236,6 +260,84 @@ class Protocol {
return capabilities;
}
/**
* Does this Protocol object support the named authentication mechanism?
*
* @since Jakarta Mail 1.6.5
*/
boolean supportsMechanism(String mech) {
return authenticators.containsKey(mech.toUpperCase(Locale.ENGLISH));
}
/**
* Return the whitespace separated string list of default authentication
* mechanisms.
*
* @since Jakarta Mail 1.6.5
*/
String getDefaultMechanisms() {
return defaultAuthenticationMechanisms;
}
/**
* Is the named authentication mechanism enabled?
*
* @since Jakarta Mail 1.6.5
*/
boolean isMechanismEnabled(String mech) {
Authenticator a = authenticators.get(mech.toUpperCase(Locale.ENGLISH));
return a != null && a.enabled();
}
/**
* Authenticate to the server using the named authentication mechanism
* and the supplied credentials.
*
* @since Jakarta Mail 1.6.5
*/
synchronized String authenticate(String mech,
String host, String authzid,
String user, String passwd) {
Authenticator a = authenticators.get(mech.toUpperCase(Locale.ENGLISH));
if (a == null)
return "No such authentication mechanism: " + mech;
try {
if (!a.authenticate(host, authzid, user, passwd))
return "login failed";
return null;
} catch (IOException ex) {
return ex.getMessage();
}
}
/**
* Does the server we're connected to support the specified
* authentication mechanism? Uses the information
* returned by the server from the CAPA command.
*
* @param auth the authentication mechanism
* @return true if the authentication mechanism is supported
*
* @since Jakarta Mail 1.6.5
*/
synchronized boolean supportsAuthentication(String auth) {
assert Thread.holdsLock(this);
if (auth.equals("LOGIN"))
return true;
if (capabilities == null)
return false;
String a = capabilities.get("SASL");
if (a == null)
return false;
StringTokenizer st = new StringTokenizer(a);
while (st.hasMoreTokens()) {
String tok = st.nextToken();
if (tok.equalsIgnoreCase(auth))
return true;
}
return false;
}
/**
* Login to the server, using the USER and PASS commands.
*/
@ -317,6 +419,318 @@ class Protocol {
return toHex(digest);
}
/**
* Abstract base class for POP3 authentication mechanism implementations.
*
* @since Jakarta Mail 1.6.5
*/
private abstract class Authenticator {
protected Response resp; // the response, used by subclasses
private final String mech; // the mechanism name, set in the constructor
private final boolean enabled; // is this mechanism enabled by default?
Authenticator(String mech) {
this(mech, true);
}
Authenticator(String mech, boolean enabled) {
this.mech = mech.toUpperCase(Locale.ENGLISH);
this.enabled = enabled;
}
String getMechanism() {
return mech;
}
boolean enabled() {
return enabled;
}
/**
* Start the authentication handshake by issuing the AUTH command.
* Delegate to the doAuth method to do the mechanism-specific
* part of the handshake.
*/
boolean authenticate(String host, String authzid,
String user, String passwd) throws IOException {
Throwable thrown = null;
try {
// use "initial response" capability, if supported
String ir = getInitialResponse(host, authzid, user, passwd);
if (noauthdebug && isTracing()) {
logger.fine("AUTH " + mech + " command trace suppressed");
suspendTracing();
}
if (ir != null)
resp = simpleCommand("AUTH " + mech + " " +
(ir.length() == 0 ? "=" : ir));
else
resp = simpleCommand("AUTH " + mech);
if (resp.cont)
doAuth(host, authzid, user, passwd);
} catch (IOException ex) { // should never happen, ignore
logger.log(Level.FINE, "AUTH " + mech + " failed", ex);
} catch (Throwable t) { // crypto can't be initialized?
logger.log(Level.FINE, "AUTH " + mech + " failed", t);
thrown = t;
} finally {
if (noauthdebug && isTracing())
logger.fine("AUTH " + mech + " " +
(resp.ok ? "succeeded" : "failed"));
resumeTracing();
if (!resp.ok) {
close();
if (thrown != null) {
if (thrown instanceof Error)
throw (Error)thrown;
if (thrown instanceof Exception) {
EOFException ex = new EOFException(
resp.data != null ?
resp.data : "authentication failed");
ex.initCause(thrown);
throw ex;
}
assert false : "unknown Throwable"; // can't happen
}
throw new EOFException(resp.data != null ?
resp.data : "authentication failed");
}
}
return true;
}
/**
* Provide the initial response to use in the AUTH command,
* or null if not supported. Subclasses that support the
* initial response capability will override this method.
*/
String getInitialResponse(String host, String authzid, String user,
String passwd) throws IOException {
return null;
}
abstract void doAuth(String host, String authzid, String user,
String passwd) throws IOException;
}
/**
* Perform the authentication handshake for LOGIN authentication.
*
* @since Jakarta Mail 1.6.5
*/
private class LoginAuthenticator extends Authenticator {
LoginAuthenticator() {
super("LOGIN");
}
@Override
boolean authenticate(String host, String authzid,
String user, String passwd) throws IOException {
String msg = null;
if ((msg = login(user, passwd)) != null) {
throw new EOFException(msg);
}
return true;
}
@Override
void doAuth(String host, String authzid, String user, String passwd)
throws IOException {
// should never get here
throw new EOFException("LOGIN asked for more");
}
}
/**
* Perform the authentication handshake for PLAIN authentication.
*
* @since Jakarta Mail 1.6.5
*/
private class PlainAuthenticator extends Authenticator {
PlainAuthenticator() {
super("PLAIN");
}
@Override
String getInitialResponse(String host, String authzid, String user,
String passwd) throws IOException {
// return "authzid<NUL>user<NUL>passwd"
ByteArrayOutputStream bos = new ByteArrayOutputStream();
OutputStream b64os =
new BASE64EncoderStream(bos, Integer.MAX_VALUE);
if (authzid != null)
b64os.write(authzid.getBytes(StandardCharsets.UTF_8));
b64os.write(0);
b64os.write(user.getBytes(StandardCharsets.UTF_8));
b64os.write(0);
b64os.write(passwd.getBytes(StandardCharsets.UTF_8));
b64os.flush(); // complete the encoding
return ASCIIUtility.toString(bos.toByteArray());
}
@Override
void doAuth(String host, String authzid, String user, String passwd)
throws IOException {
// should never get here
throw new EOFException("PLAIN asked for more");
}
}
/**
* Perform the authentication handshake for DIGEST-MD5 authentication.
*
* @since Jakarta Mail 1.6.5
*/
/*
* XXX - Need to move DigestMD5 class to com.sun.mail.auth
*
private class DigestMD5Authenticator extends Authenticator {
private DigestMD5 md5support; // only create if needed
DigestMD5Authenticator() {
super("DIGEST-MD5");
}
private synchronized DigestMD5 getMD5() {
if (md5support == null)
md5support = new DigestMD5(logger);
return md5support;
}
@Override
void doAuth(Protocol p, String host, String authzid,
String user, String passwd)
throws IOException {
DigestMD5 md5 = getMD5();
assert md5 != null;
byte[] b = md5.authClient(host, user, passwd, getSASLRealm(),
resp.data);
resp = p.simpleCommand(b);
if (resp.cont) { // client authenticated by server
if (!md5.authServer(resp.data)) {
// server NOT authenticated by client !!!
resp.ok = false;
} else {
// send null response
resp = simpleCommand(new byte[0]);
}
}
}
}
*/
/**
* Perform the authentication handshake for NTLM authentication.
*
* @since Jakarta Mail 1.6.5
*/
private class NtlmAuthenticator extends Authenticator {
private Ntlm ntlm;
NtlmAuthenticator() {
super("NTLM");
}
@Override
String getInitialResponse(String host, String authzid, String user,
String passwd) throws IOException {
ntlm = new Ntlm(props.getProperty(prefix + ".auth.ntlm.domain"),
getLocalHost(), user, passwd, logger);
int flags = PropUtil.getIntProperty(
props, prefix + ".auth.ntlm.flags", 0);
boolean v2 = PropUtil.getBooleanProperty(
props, prefix + ".auth.ntlm.v2", true);
String type1 = ntlm.generateType1Msg(flags, v2);
return type1;
}
@Override
void doAuth(String host, String authzid, String user, String passwd)
throws IOException {
assert ntlm != null;
String type3 = ntlm.generateType3Msg(
resp.data.substring(4).trim());
resp = simpleCommand(type3);
}
}
/**
* Perform the authentication handshake for XOAUTH2 authentication.
*
* @since Jakarta Mail 1.6.5
*/
private class OAuth2Authenticator extends Authenticator {
OAuth2Authenticator() {
super("XOAUTH2", false); // disabled by default
}
@Override
String getInitialResponse(String host, String authzid, String user,
String passwd) throws IOException {
String resp = "user=" + user + "\001auth=Bearer " +
passwd + "\001\001";
byte[] b = BASE64EncoderStream.encode(
resp.getBytes(StandardCharsets.UTF_8));
return ASCIIUtility.toString(b);
}
@Override
void doAuth(String host, String authzid, String user, String passwd)
throws IOException {
// OAuth2 failure returns a JSON error code,
// which looks like a "please continue" to the authenticate()
// code, so we turn that into a clean failure here.
String err = "";
if (resp.data != null) {
byte[] b = resp.data.getBytes(StandardCharsets.UTF_8);
b = BASE64DecoderStream.decode(b);
err = new String(b, StandardCharsets.UTF_8);
}
throw new EOFException("OAUTH2 authentication failed: " + err);
}
}
/**
* Get the name of the local host.
*
* @return the local host name
* @since Jakarta Mail 1.6.5
*/
private synchronized String getLocalHost() {
// get our hostname and cache it for future use
try {
if (localHostName == null || localHostName.length() == 0) {
InetAddress localHost = InetAddress.getLocalHost();
localHostName = localHost.getCanonicalHostName();
// if we can't get our name, use local address literal
if (localHostName == null)
// XXX - not correct for IPv6
localHostName = "[" + localHost.getHostAddress() + "]";
}
} catch (UnknownHostException uhex) {
}
// last chance, try to get our address from our socket
if (localHostName == null || localHostName.length() <= 0) {
if (socket != null && socket.isBound()) {
InetAddress localHost = socket.getLocalAddress();
localHostName = localHost.getCanonicalHostName();
// if we can't get our name, use local address literal
if (localHostName == null)
// XXX - not correct for IPv6
localHostName = "[" + localHost.getHostAddress() + "]";
}
}
return localHostName;
}
private static char[] digits = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
@ -758,7 +1172,10 @@ class Protocol {
Response r = new Response();
if (line.startsWith("+OK"))
r.ok = true;
else if (line.startsWith("-ERR"))
else if (line.startsWith("+ ")) {
r.ok = true;
r.cont = true;
} else if (line.startsWith("-ERR"))
r.ok = false;
else
throw new IOException("Unexpected response: " + line);

View File

@ -4,7 +4,7 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<!--
Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
Copyright (c) 1997, 2020 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
@ -297,6 +297,99 @@ Defaults to false.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.auth.mechanisms">mail.pop3.auth.mechanisms</A></TD>
<TD>String</TD>
<TD>
If set, lists the authentication mechanisms to consider, and the order
in which to consider them. Only mechanisms supported by the server and
supported by the current implementation will be used.
The default is <code>"LOGIN PLAIN DIGEST-MD5 NTLM"</code>, which includes all
the authentication mechanisms supported by the current implementation
except XOAUTH2.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.auth.login.disable">mail.pop3.auth.login.disable</A></TD>
<TD>boolean</TD>
<TD>If true, prevents use of the <code>USER</code> and <code>PASS</code>
commands.
Default is false.</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.auth.plain.disable">mail.pop3.auth.plain.disable</A></TD>
<TD>boolean</TD>
<TD>If true, prevents use of the <code>AUTH PLAIN</code> command.
Default is false.</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.auth.digest-md5.disable">mail.pop3.auth.digest-md5.disable</A></TD>
<TD>boolean</TD>
<TD>If true, prevents use of the <code>AUTH DIGEST-MD5</code> command.
Default is false.</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.auth.ntlm.disable">mail.pop3.auth.ntlm.disable</A></TD>
<TD>boolean</TD>
<TD>If true, prevents use of the <code>AUTH NTLM</code> command.
Default is false.</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.auth.ntlm.domain">mail.pop3.auth.ntlm.domain</A></TD>
<TD>String</TD>
<TD>
The NTLM authentication domain.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.auth.ntlm.flags">mail.pop3.auth.ntlm.flags</A></TD>
<TD>int</TD>
<TD>
NTLM protocol-specific flags.
See <A HREF="http://curl.haxx.se/rfc/ntlm.html#theNtlmFlags" TARGET="_top">
http://curl.haxx.se/rfc/ntlm.html#theNtlmFlags</A> for details.
</TD>
</TR>
<!--
<TR>
<TD><A ID="mail.pop3.auth.ntlm.unicode">mail.pop3.auth.ntlm.unicode</A></TD>
<TD>boolean</TD>
<TD>
Set this to "true" if the username or password may use
Unicode UTF-8 encoded characters. Default is "true".
Currently has no effect.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.auth.ntlm.lmcompat">mail.pop3.auth.ntlm.lmcompat</A></TD>
<TD>int</TD>
<TD>
Sets the LM compatibility level, as described here:
<A HREF="http://curl.haxx.se/rfc/ntlm.html#ntlmVersion2" TARGET="_top">
http://curl.haxx.se/rfc/ntlm.html#ntlmVersion2</A>
Defaults to "3". Currently not used.
</TD>
</TR>
-->
<TR>
<TD><A ID="mail.pop3.auth.xoauth2.disable">mail.pop3.auth.xoauth2.disable</A></TD>
<TD>boolean</TD>
<TD>If true, prevents use of the <code>AUTHENTICATE XOAUTH2</code> command.
Because the OAuth 2.0 protocol requires a special access token instead of
a password, this mechanism is disabled by default. Enable it by explicitly
setting this property to "false" or by setting the "mail.pop3.auth.mechanisms"
property to "XOAUTH2".</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.socketFactory">mail.pop3.socketFactory</A></TD>
<TD>SocketFactory</TD>

View File

@ -22,5 +22,5 @@ package javax.mail;
* at build time.
*/
class Version {
public static final String version = "1.6.5-SNAPSHOT";
public static final String version = "1.6.5";
}