Skip to content

Commit

Permalink
#111: use the correct Session properties based on the current Transpo…
Browse files Browse the repository at this point in the history
…rt Strategy while also handle various scenario's where Transport Strategy was not provided
  • Loading branch information
bbottema committed Nov 3, 2017
1 parent 9c11c79 commit b1d789b
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 46 deletions.
2 changes: 1 addition & 1 deletion src/main/java/org/simplejavamail/mailer/Mailer.java
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ public boolean isTransportModeLoggingOnly() {
}

/**
* Configures the current session to trust all hosts and don't validate any SSL keys. The property "mail.smtp.ssl.trust" is set to "*".
* Configures the current session to trust all hosts and don't validate any SSL keys. The property "mail.smtp(s).ssl.trust" is set to "*".
* <p>
* Refer to https://javamail.java.net/nonav/docs/api/com/sun/mail/smtp/package-summary.html#mail.smtp.ssl.trust
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/**
* The proxy configuration that indicates whether the connections should be routed through a proxy.
* <p>
* In case a proxy is required, the properties <em>"mail.smtp.socks.host"</em> and <em>"mail.smtp.socks.port"</em> will be set.
* In case a proxy is required, the properties <em>"mail.smtp(s).socks.host"</em> and <em>"mail.smtp(s).socks.port"</em> will be set.
* <p>
* As the underlying JavaMail framework only support anonymous SOCKS proxy servers for non-ssl connections, authenticated SOCKS5 proxy is made
* possible using an intermediary anonymous proxy server which relays the connection through an authenticated remote proxy server. Anonymous proxies
Expand Down Expand Up @@ -119,7 +119,7 @@ public int getProxyBridgePort() {
* @param proxyBridgePort Port override for the temporary intermediary SOCKS5 relay server bridge (default is {@value
* #DEFAULT_PROXY_BRIDGE_PORT}).
*/
public void setProxyBridgePort(final int proxyBridgePort) {
public void setProxyBridgePort(@SuppressWarnings("SameParameterValue") final int proxyBridgePort) {
this.proxyBridgePort = proxyBridgePort;
}

Expand Down
180 changes: 176 additions & 4 deletions src/main/java/org/simplejavamail/mailer/config/TransportStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,62 @@ public String propertyNameUsername() {
public String propertyNameAuthenticate() {
return "mail.smtp.auth";
}

/**
* @return "mail.smtp.socks.host"
*/
@Override
public String propertyNameSocksHost() {
return "mail.smtp.socks.host";
}

/**
* @return "mail.smtp.socks.port"
*/
@Override
public String propertyNameSocksPort() {
return "mail.smtp.socks.port";
}

/**
* @return "mail.smtp.connectiontimeout"
*/
@Override
public String propertyNameConnectionTimeout() {
return "mail.smtp.connectiontimeout";
}

/**
* @return "mail.smtp.timeout"
*/
@Override
public String propertyNameTimeout() {
return "mail.smtp.timeout";
}

/**
* @return "mail.smtp.writetimeout"
*/
@Override
public String propertyNameWriteTimeout() {
return "mail.smtp.writetimeout";
}

/**
* @return "mail.smtp.from"
*/
@Override
public String propertyNameEnvelopeFrom() {
return "mail.smtp.from";
}

/**
* @return "mail.smtp.ssl.trust"
*/
@Override
public String propertyNameSSLTrust() {
return "mail.smtp.ssl.trust";
}
},
/**
* SMTPS / SSL transport strategy, that returns the ".smtps." variation of the SMTP_PLAIN version. Additionally the transport protocol is
Expand Down Expand Up @@ -122,6 +178,62 @@ public String propertyNameUsername() {
public String propertyNameAuthenticate() {
return "mail.smtps.auth";
}

/**
* @return "mail.smtps.socks.host"
*/
@Override
public String propertyNameSocksHost() {
return "mail.smtps.socks.host";
}

/**
* @return "mail.smtps.socks.port"
*/
@Override
public String propertyNameSocksPort() {
return "mail.smtps.socks.port";
}

/**
* @return "mail.smtps.connectiontimeout"
*/
@Override
public String propertyNameConnectionTimeout() {
return "mail.smtps.connectiontimeout";
}

/**
* @return "mail.smtps.timeout"
*/
@Override
public String propertyNameTimeout() {
return "mail.smtps.timeout";
}

/**
* @return "mail.smtps.writetimeout"
*/
@Override
public String propertyNameWriteTimeout() {
return "mail.smtps.writetimeout";
}

/**
* @return "mail.smtps.from"
*/
@Override
public String propertyNameEnvelopeFrom() {
return "mail.smtps.from";
}

/**
* @return "mail.smtps.ssl.trust"
*/
@Override
public String propertyNameSSLTrust() {
return "mail.smtps.ssl.trust";
}
},
/**
* <strong>NOTE: this code is in untested beta state</strong>
Expand Down Expand Up @@ -174,6 +286,62 @@ public String propertyNameUsername() {
public String propertyNameAuthenticate() {
return "mail.smtp.auth";
}

/**
* @return "mail.smtp.socks.host"
*/
@Override
public String propertyNameSocksHost() {
return "mail.smtp.socks.host";
}

/**
* @return "mail.smtp.socks.port"
*/
@Override
public String propertyNameSocksPort() {
return "mail.smtp.socks.port";
}

/**
* @return "mail.smtp.connectiontimeout"
*/
@Override
public String propertyNameConnectionTimeout() {
return "mail.smtp.connectiontimeout";
}

/**
* @return "mail.smtp.timeout"
*/
@Override
public String propertyNameTimeout() {
return "mail.smtp.timeout";
}

/**
* @return "mail.smtp.writetimeout"
*/
@Override
public String propertyNameWriteTimeout() {
return "mail.smtp.writetimeout";
}

/**
* @return "mail.smtp.from"
*/
@Override
public String propertyNameEnvelopeFrom() {
return "mail.smtp.from";
}

/**
* @return "mail.smtp.ssl.trust"
*/
@Override
public String propertyNameSSLTrust() {
return "mail.smtp.ssl.trust";
}
};

/**
Expand All @@ -198,13 +366,17 @@ public Properties generateProperties() {
}

public abstract String propertyNameHost();

public abstract String propertyNamePort();

public abstract String propertyNameUsername();

public abstract String propertyNameAuthenticate();

public abstract String propertyNameSocksHost();
public abstract String propertyNameSocksPort();
public abstract String propertyNameConnectionTimeout();
public abstract String propertyNameWriteTimeout();
public abstract String propertyNameEnvelopeFrom();
public abstract String propertyNameSSLTrust();
public abstract String propertyNameTimeout();

/**
* @param session The session to determine the current transport strategy for
* @return Which strategy matches the current Session properties.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.mail.*;
import javax.mail.internet.MimeMessage;
import java.io.UnsupportedEncodingException;
Expand Down Expand Up @@ -67,6 +68,20 @@ public class MailSender {
*/
private final Session session;

/**
* The strategy is used initially to configure as much as possible, such as the Session and proxy configuration, however, at the time of
* actually sending an email, some more properties need to be set based on the current Email instance.
* <p>
* Depending on the transport strategy, these properties are different, that's why we need to keep a global hold on this instance.
* <p>
* <strong>NOTE:</strong><br>
* This is an optional parameter and as such some functions will throw an error when used (such as {@link #trustAllHosts(boolean)}) or
* will skip setting optional properties (such as default timeouts) and also skip mandatory properties which are assumed to be preconfigured on
* the Session instance (these will be logged on DEBUG level, such as proxy host and port properties).
*/
@Nullable
private final TransportStrategy transportStrategy;

/**
* Intermediary SOCKS5 relay server that acts as bridge between JavaMail and remote proxy (since JavaMail only supports anonymous SOCKS proxies).
* Only set when {@link ProxyConfig} is provided with authentication details.
Expand Down Expand Up @@ -116,8 +131,9 @@ public class MailSender {
* <li>{@link #transportModeLoggingOnly} from properties or default {@link #DEFAULT_MODE_LOGGING_ONLY}</li>
* </ul>
*/
public MailSender(final Session session, final ProxyConfig proxyConfig, final TransportStrategy transportStrategy) {
public MailSender(final Session session, final ProxyConfig proxyConfig, @Nullable final TransportStrategy transportStrategy) {
this.session = session;
this.transportStrategy = transportStrategy;
this.proxyServer = configureSessionWithProxy(proxyConfig, session, transportStrategy);
this.threadPoolSize = ConfigLoader.valueOrProperty(null, Property.DEFAULT_POOL_SIZE, DEFAULT_POOL_SIZE);
this.sessionTimeout = ConfigLoader.valueOrProperty(null, Property.DEFAULT_SESSION_TIMEOUT_MILLIS, DEFAULT_SESSION_TIMEOUT_MILLIS);
Expand All @@ -128,7 +144,7 @@ public MailSender(final Session session, final ProxyConfig proxyConfig, final Tr
* If a {@link ProxyConfig} was provided with a host address, then the appropriate properties are set on the {@link Session}, overriding any SOCKS
* properties already there.
* <p>
* These properties are <em>"mail.smtp.socks.host"</em> and <em>"mail.smtp.socks.port"</em>, which are set to "localhost" and {@link
* These properties are <em>"mail.smtp(s).socks.host"</em> and <em>"mail.smtp(s).socks.port"</em>, which are set to "localhost" and {@link
* ProxyConfig#getProxyBridgePort()}.
*
* @param proxyConfig Proxy server details, optionally with username / password.
Expand All @@ -147,11 +163,21 @@ private static AnonymousSocks5Server configureSessionWithProxy(final ProxyConfig
throw new MailSenderException(MailSenderException.INVALID_PROXY_SLL_COMBINATION);
}
final Properties sessionProperties = session.getProperties();
sessionProperties.put("mail.smtp.socks.host", effectiveProxyConfig.getRemoteProxyHost());
sessionProperties.put("mail.smtp.socks.port", String.valueOf(effectiveProxyConfig.getRemoteProxyPort()));
if (transportStrategy != null) {
sessionProperties.put(transportStrategy.propertyNameSocksHost(), effectiveProxyConfig.getRemoteProxyHost());
sessionProperties.put(transportStrategy.propertyNameSocksPort(), String.valueOf(effectiveProxyConfig.getRemoteProxyPort()));
} else {
LOGGER.debug("no transport strategy provided, expecting mail.smtp(s).socks.host and .port properties to be set to proxy " +
"config on Session");
}
if (effectiveProxyConfig.requiresAuthentication()) {
sessionProperties.put("mail.smtp.socks.host", "localhost");
sessionProperties.put("mail.smtp.socks.port", String.valueOf(effectiveProxyConfig.getProxyBridgePort()));
if (transportStrategy != null) {
sessionProperties.put(transportStrategy.propertyNameSocksHost(), "localhost");
sessionProperties.put(transportStrategy.propertyNameSocksPort(), String.valueOf(effectiveProxyConfig.getProxyBridgePort()));
} else {
LOGGER.debug("no transport strategy provided but authenticated proxy required, expecting mail.smtp(s).socks.host and .port " +
"properties to be set to localhost and port " + effectiveProxyConfig.getProxyBridgePort());
}
return new AnonymousSocks5Server(new AuthenticatingSocks5Bridge(effectiveProxyConfig),
effectiveProxyConfig.getProxyBridgePort());
}
Expand Down Expand Up @@ -217,11 +243,15 @@ public String toString() {
* Configures the {@link Session} with the same timeout for socket connection timeout, read and write timeout.
*/
private void configureSessionWithTimeout(final Session session, final int sessionTimeout) {
// socket timeouts handling
final Properties sessionProperties = session.getProperties();
sessionProperties.put("mail.smtp.connectiontimeout", String.valueOf(sessionTimeout));
sessionProperties.put("mail.smtp.timeout", String.valueOf(sessionTimeout));
sessionProperties.put("mail.smtp.writetimeout", String.valueOf(sessionTimeout));
if (transportStrategy != null) {
// socket timeouts handling
final Properties sessionProperties = session.getProperties();
sessionProperties.put(transportStrategy.propertyNameConnectionTimeout(), String.valueOf(sessionTimeout));
sessionProperties.put(transportStrategy.propertyNameTimeout(), String.valueOf(sessionTimeout));
sessionProperties.put(transportStrategy.propertyNameWriteTimeout(), String.valueOf(sessionTimeout));
} else {
LOGGER.debug("No transport strategy provided, skipping defaults for .connectiontimout, .timout and .writetimeout");
}
}

/**
Expand Down Expand Up @@ -289,8 +319,12 @@ private void sendMailClosure(@Nonnull final Session session, @Nonnull final Emai
private void configureBounceToAddress(Session session, Email email) {
Recipient bounceAddress = email.getBounceToRecipient();
if (bounceAddress != null) {
String formattedRecipient = format("%s <%s>", bounceAddress.getName(), bounceAddress.getAddress());
session.getProperties().setProperty("mail.smtp.from", formattedRecipient);
if (transportStrategy != null) {
String formattedRecipient = format("%s <%s>", bounceAddress.getName(), bounceAddress.getAddress());
session.getProperties().setProperty(transportStrategy.propertyNameEnvelopeFrom(), formattedRecipient);
} else {
throw new MailSenderException(MailSenderException.CANNOT_SET_BOUNCETO_WITHOUT_TRANSPORTSTRATEGY);
}
}
}

Expand Down Expand Up @@ -333,14 +367,18 @@ public void setDebug(final boolean debug) {
}

/**
* Configures the current session to trust all hosts and don't validate any SSL keys. The property "mail.smtp.ssl.trust" is set to "*".
* Configures the current session to trust all hosts and don't validate any SSL keys. The property "mail.smtp(s).ssl.trust" is set to "*".
* <p>
* Refer to https://javamail.java.net/nonav/docs/api/com/sun/mail/smtp/package-summary.html#mail.smtp.ssl.trust
*/
public void trustAllHosts(final boolean trustAllHosts) {
session.getProperties().remove("mail.smtp.ssl.trust");
if (trustAllHosts) {
session.getProperties().setProperty("mail.smtp.ssl.trust", "*");
if (transportStrategy != null) {
session.getProperties().remove(transportStrategy.propertyNameSSLTrust());
if (trustAllHosts) {
session.getProperties().setProperty(transportStrategy.propertyNameSSLTrust(), "*");
}
} else {
throw new MailSenderException(MailSenderException.CANNOT_SET_TRUST_WITHOUT_TRANSPORTSTRATEGY);
}
}

Expand All @@ -355,19 +393,22 @@ public void trustAllHosts(final boolean trustAllHosts) {
* regardless of the certificate issuer; attackers can abuse this behavior by serving a matching self-signed
* certificate during a man-in-the-middle attack.
* <p>
* This method sets the property {@code mail.smtp.ssl.trust} to a space-separated list of the provided
* {@code hosts}. If the provided list is empty, {@code mail.smtp.ssl.trust} is unset.
* This method sets the property {@code mail.smtp(s).ssl.trust} to a space-separated list of the provided
* {@code hosts}. If the provided list is empty, {@code mail.smtp(s).ssl.trust} is unset.
*
* @see <a href="https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html#mail.smtp.ssl.trust"><code>mail.smtp.ssl.trust</code></a>
*/
public void trustHosts(final String... hosts) {
trustAllHosts(false);
if (hosts.length > 0) {
if (transportStrategy == null) {
throw new MailSenderException(MailSenderException.CANNOT_SET_TRUST_WITHOUT_TRANSPORTSTRATEGY);
}
final StringBuilder builder = new StringBuilder(hosts[0]);
for (int i = 1; i < hosts.length; i++) {
builder.append(" ").append(hosts[i]);
}
session.getProperties().setProperty("mail.smtp.ssl.trust", builder.toString());
session.getProperties().setProperty(transportStrategy.propertyNameSSLTrust(), builder.toString());
}
}

Expand Down
Loading

0 comments on commit b1d789b

Please # to comment.