diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt index 2f77308668..9250fbac60 100644 --- a/Documentation/config-gerrit.txt +++ b/Documentation/config-gerrit.txt @@ -760,7 +760,21 @@ By default, 127.0.0.1 (aka localhost). + Port number of the SMTP server in sendemail.smtpserver. + -By default, 25. +By default, 25, or 465 if smtpEncryption is 'ssl'. + +[[sendemail.smtpEncryption]]sendemail.smtpEncryption:: ++ +Specify the encryption to use, either 'ssl' or 'tls'. ++ +By default, 'none', indicating no encryption is used. + +[[sendemail.sslVerify]]sendemail.sslVerify:: ++ +If false and sendemail.smtpEncryption is 'ssl' or 'tls', Gerrit +will not verify the server certificate when it connects to send +an email message. ++ +By default, true, requiring the certificate to be verified. [[sendemail.smtpUser]]sendemail.smtpUser:: + diff --git a/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java b/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java index 7fab210191..cb2fa67ee6 100644 --- a/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java +++ b/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java @@ -15,6 +15,7 @@ package com.google.gerrit.server.mail; import com.google.gerrit.pgm.Version; +import com.google.gerrit.server.config.ConfigUtil; import com.google.gerrit.server.config.GerritServerConfig; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -34,12 +35,18 @@ import java.util.Map; /** Sends email via a nearby SMTP server. */ @Singleton public class SmtpEmailSender implements EmailSender { + public static enum Encryption { + NONE, SSL, TLS; + } + private final boolean enabled; private String smtpHost; private int smtpPort; private String smtpUser; private String smtpPass; + private Encryption smtpEncryption; + private boolean sslVerify; private String[] allowrcpt; @Inject @@ -50,7 +57,26 @@ public class SmtpEmailSender implements EmailSender { if (smtpHost == null) { smtpHost = "127.0.0.1"; } - smtpPort = cfg.getInt("sendemail", null, "smtpserverport", 25); + + smtpEncryption = + ConfigUtil.getEnum(cfg, "sendemail", null, "smtpencryption", + Encryption.NONE); + sslVerify = cfg.getBoolean("sendemail", null, "sslverify", true); + + final int defaultPort; + switch (smtpEncryption) { + case SSL: + defaultPort = 465; + break; + + case NONE: + case TLS: + default: + defaultPort = 25; + break; + } + smtpPort = cfg.getInt("sendemail", null, "smtpserverport", defaultPort); + smtpUser = cfg.getString("sendemail", null, "smtpuser"); smtpPass = cfg.getString("sendemail", null, "smtpuserpass"); allowrcpt = cfg.getStringList("sendemail", null, "allowrcpt"); @@ -136,6 +162,11 @@ public class SmtpEmailSender implements EmailSender { private SMTPClient open() throws EmailException { final AuthSMTPClient client = new AuthSMTPClient("UTF-8"); client.setAllowRcpt(allowrcpt); + + if (smtpEncryption == Encryption.SSL) { + client.enableSSL(sslVerify); + } + try { client.connect(smtpHost, smtpPort); if (!SMTPReply.isPositiveCompletion(client.getReplyCode())) { @@ -145,6 +176,17 @@ public class SmtpEmailSender implements EmailSender { String e = client.getReplyString(); throw new EmailException("SMTP server rejected login: " + e); } + + if (smtpEncryption == Encryption.TLS) { + if (!client.startTLS(smtpHost, smtpPort, sslVerify)) { + throw new EmailException("SMTP server does not support TLS"); + } + if (!client.login()) { + String e = client.getReplyString(); + throw new EmailException("SMTP server rejected login: " + e); + } + } + if (smtpUser != null && !client.auth(smtpUser, smtpPass)) { String e = client.getReplyString(); throw new EmailException("SMTP server rejected auth: " + e); diff --git a/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java b/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java index 0e623e6bbe..30eecd63b6 100644 --- a/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java +++ b/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java @@ -14,12 +14,15 @@ package org.apache.commons.net.smtp; +import com.google.gerrit.server.ioutil.BlindSSLSocketFactory; + import org.eclipse.jgit.util.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.net.SocketException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; @@ -29,6 +32,7 @@ import java.util.Set; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import javax.net.ssl.SSLSocketFactory; public class AuthSMTPClient extends SMTPClient { private static final Logger log = @@ -41,6 +45,29 @@ public class AuthSMTPClient extends SMTPClient { super(charset); } + public void enableSSL(final boolean verify) { + _socketFactory_ = sslFactory(verify); + } + + public boolean startTLS(final String hostname, final int port, + final boolean verify) throws SocketException, IOException { + if (sendCommand("STARTTLS") != 220) { + return false; + } + + _socket_ = sslFactory(verify).createSocket(_socket_, hostname, port, true); + _connectAction_(); + return true; + } + + private static SSLSocketFactory sslFactory(final boolean verify) { + if (verify) { + return (SSLSocketFactory) SSLSocketFactory.getDefault(); + } else { + return (SSLSocketFactory) BlindSSLSocketFactory.getDefault(); + } + } + public void setAllowRcpt(final String[] allowed) { if (allowed != null && allowed.length > 0) { if (allowedRcptTo == null) {