Skip to content

Commit

Permalink
Add password authentification on external smtp servers (#240)
Browse files Browse the repository at this point in the history
* Add SMTP Authentification

* refactor: restrict access to getEmailSession()

* fix: FreemarkerEmailFactory test, defined protocols if TLS port is used to send email

* fix: add check for TLSv1.3 support
  • Loading branch information
litvinovg committed Apr 14, 2022
1 parent 03624e5 commit eff04e0
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Properties;
import java.util.Scanner;

import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.net.ssl.SSLContext;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
Expand All @@ -40,11 +45,18 @@
* syntactically invalid, an exception is thrown and startup is aborted.
*/
public class FreemarkerEmailFactory {

private static final Log log = LogFactory
.getLog(FreemarkerEmailFactory.class);

private static final int DEFAULT_SMTP_PORT = 25;
private static final int TLS_PORT = 587;
private static final int SSL_PORT = 465;
public static final String SMTP_HOST_PROPERTY = "email.smtpHost";
public static final String REPLY_TO_PROPERTY = "email.replyTo";
public static final String EMAIL_PASSWORD = "email.password";
public static final String EMAIL_USERNAME = "email.username";
public static final String EMAIL_PORT = "email.port";

private static final String ATTRIBUTE_NAME = FreemarkerEmailFactory.class
.getName();
Expand Down Expand Up @@ -91,13 +103,20 @@ private static FreemarkerEmailFactory getFactory(HttpServletRequest req) {
private final String smtpHost;
private final InternetAddress replyToAddress;
private final Session emailSession;
private final String password;
private final String userName;
private final int emailPort;


public FreemarkerEmailFactory(ServletContext ctx) {
this.smtpHost = getSmtpHostFromConfig(ctx);
new SmtpHostTester().test(this.smtpHost);

this.emailPort = getPortFromConfig(ctx);
new SmtpHostTester().test(this.smtpHost, emailPort);
this.replyToAddress = getReplyToAddressFromConfig(ctx);
this.password = getPasswordFromConfig(ctx);
this.userName = getUserNameFromConfig(ctx);
this.emailSession = createEmailSession(smtpHost);

}

String getSmtpHost() {
Expand All @@ -108,10 +127,10 @@ InternetAddress getReplyToAddress() {
return replyToAddress;
}

Session getEmailSession() {
private Session getEmailSession() {
return emailSession;
}

private String getSmtpHostFromConfig(ServletContext ctx) {
ConfigurationProperties config = ConfigurationProperties.getBean(ctx);
String hostName = config.getProperty(SMTP_HOST_PROPERTY, "");
Expand All @@ -121,6 +140,28 @@ private String getSmtpHostFromConfig(ServletContext ctx) {
return hostName;
}

private String getPasswordFromConfig(ServletContext ctx) {
ConfigurationProperties config = ConfigurationProperties.getBean(ctx);
String password = config.getProperty(EMAIL_PASSWORD, "");
return password;
}

private String getUserNameFromConfig(ServletContext ctx) {
ConfigurationProperties config = ConfigurationProperties.getBean(ctx);
String userName = config.getProperty(EMAIL_USERNAME, "");
return userName;
}

private int getPortFromConfig(ServletContext ctx) {
ConfigurationProperties config = ConfigurationProperties.getBean(ctx);
String port = config.getProperty(EMAIL_PORT, Integer.toString(DEFAULT_SMTP_PORT));
try {
return Integer.parseInt(port);
} catch (NumberFormatException e) {
return DEFAULT_SMTP_PORT;
}
}

private InternetAddress getReplyToAddressFromConfig(ServletContext ctx) {
ConfigurationProperties config = ConfigurationProperties.getBean(ctx);
String rawAddress = config.getProperty(REPLY_TO_PROPERTY, "");
Expand Down Expand Up @@ -150,7 +191,43 @@ private InternetAddress getReplyToAddressFromConfig(ServletContext ctx) {
private Session createEmailSession(String hostName) {
Properties props = new Properties(System.getProperties());
props.put("mail.smtp.host", hostName);
return Session.getDefaultInstance(props, null);
props.put("mail.smtp.port", emailPort);
if (emailPort == TLS_PORT) {
props.put("mail.smtp.starttls.enable", "true");
if (isTLS13Supported()) {
props.put("mail.smtp.ssl.protocols", "TLSv1.3 TLSv1.2");
}
}
if (emailPort == SSL_PORT) {
props.put("mail.smtp.socketFactory.port", emailPort);
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
}
Authenticator auth = null;
if (!password.isEmpty() && !userName.isEmpty()) {
props.put("mail.smtp.auth", "true");
auth = getAuthenticator();
}
return Session.getDefaultInstance(props, auth);
}

private boolean isTLS13Supported() {
String[] protocols;
try {
protocols = SSLContext.getDefault().getSupportedSSLParameters().getProtocols();
return (Arrays.stream(protocols).anyMatch("TLSv1.3"::equals));
} catch (NoSuchAlgorithmException e) {
log.error("No SSL context found. Suppose TLSv1.3 is not supported.");
log.error(e, e);
}
return false;
}

private Authenticator getAuthenticator() {
return new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(userName, password);
}
};
}

// ----------------------------------------------------------------------
Expand Down Expand Up @@ -188,17 +265,18 @@ public static class SmtpHostTester {
/**
* Try to open a connection to the SMTP host and conduct an "empty"
* conversation using SMTP.
* @param emailPort
*
* @throws InvalidSmtpHost
* If anything goes wrong.
*/
public void test(String smtpHost) throws InvalidSmtpHost {
public void test(String smtpHost, int emailPort) throws InvalidSmtpHost {
Socket socket = null;
PrintStream out = null;
Scanner in = null;
try {
InetAddress hostAddr = InetAddress.getByName(smtpHost);
socket = new Socket(hostAddr, SMTP_PORT);
socket = new Socket(hostAddr, emailPort);

out = new PrintStream(socket.getOutputStream());
in = new Scanner(new InputStreamReader(socket.getInputStream()));
Expand All @@ -216,7 +294,7 @@ public void test(String smtpHost) throws InvalidSmtpHost {
"host name is not recognized");
} catch (ConnectException e) {
throw new InvalidSmtpHost(smtpHost,
"refused connection on port " + SMTP_PORT);
"refused connection on port " + emailPort);
} catch (IOException e) {
throw new RuntimeException("unrecognized problem: ", e);
} finally {
Expand Down Expand Up @@ -265,7 +343,7 @@ public void contextInitialized(ServletContextEvent sce) {
+ "to users.", e);
}
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
sce.getServletContext().removeAttribute(ATTRIBUTE_NAME);
Expand Down
3 changes: 3 additions & 0 deletions home/src/main/resources/config/example.runtime.properties
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ vitro.local.solr.url = http://localhost:8983/solr/vitrocore
# Example:
# email.smtpHost = smtp.mydomain.edu
# email.replyTo = vitroAdmin@mydomain.edu
# email.username = vivtroAdmin@mydomain.edu
# email.password = secret
# email.port = 25 or 465 or 587
#
email.smtpHost =
email.replyTo =
Expand Down

0 comments on commit eff04e0

Please sign in to comment.