From 6e9a83f6ca5513dbb3729b153b14a8129cf74feb Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 2 Nov 2009 10:30:48 -0800 Subject: [PATCH] Support SMTP over SSL/TLS If sendemail.smtpEncryption is set to 'ssl' or 'tls' we now enable the proper encryption on top of the socket, permitting the client to use encryption when talking with the relay SMTP server. This might be necessary to protect the username/password used to authenticate prior to sending a message. Bug: issue 300 Change-Id: Idecb20326261cbc8951e2ff469b95ea7ee83e48c Signed-off-by: Shawn O. Pearce --- Documentation/config-gerrit.txt | 16 ++++++- .../gerrit/server/mail/SmtpEmailSender.java | 44 ++++++++++++++++++- .../commons/net/smtp/AuthSMTPClient.java | 27 ++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) 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) {