Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.google.gerrit.server.mail.send.SmtpEmailSender Maven / Gradle / Ivy
package com.google.gerrit.server.mail.send;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.flogger.FluentLogger;
import com.google.common.io.BaseEncoding;
import com.google.common.primitives.Ints;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.Version;
import com.google.gerrit.entities.Address;
import com.google.gerrit.entities.EmailHeader;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.mail.Encryption;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import org.apache.commons.net.smtp.AuthSMTPClient;
import org.apache.commons.net.smtp.SMTPClient;
import org.apache.commons.net.smtp.SMTPReply;
import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
import org.eclipse.jgit.lib.Config;
@Singleton
public class SmtpEmailSender implements EmailSender {
private static final int DEFAULT_CONNECT_TIMEOUT = 0 ;
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public static class Module extends AbstractModule {
@Override
protected void configure () {
bind(EmailSender.class).to(SmtpEmailSender.class);
}
}
private final boolean enabled;
private final int connectTimeout;
private String smtpHost;
private int smtpPort;
private String smtpUser;
private String smtpPass;
private Encryption smtpEncryption;
private boolean sslVerify;
private Set allowrcpt;
private Set denyrcpt;
private String importance;
private int expiryDays;
@Inject
SmtpEmailSender(@GerritServerConfig Config cfg) {
enabled = cfg.getBoolean("sendemail" , null , "enable" , true );
connectTimeout =
Ints.checkedCast(
ConfigUtil.getTimeUnit(
cfg,
"sendemail" ,
null ,
"connectTimeout" ,
DEFAULT_CONNECT_TIMEOUT,
TimeUnit.MILLISECONDS));
smtpHost = cfg.getString("sendemail" , null , "smtpserver" );
if (smtpHost == null ) {
smtpHost = "127.0.0.1" ;
}
smtpEncryption = cfg.getEnum("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 , "smtppass" );
Set rcpt = new HashSet<>();
Collections.addAll(rcpt, cfg.getStringList("sendemail" , null , "allowrcpt" ));
allowrcpt = Collections.unmodifiableSet(rcpt);
Set rcptdeny = new HashSet<>();
Collections.addAll(rcptdeny, cfg.getStringList("sendemail" , null , "denyrcpt" ));
denyrcpt = Collections.unmodifiableSet(rcptdeny);
importance = cfg.getString("sendemail" , null , "importance" );
expiryDays = cfg.getInt("sendemail" , null , "expiryDays" , 0 );
}
@Override
public boolean isEnabled () {
return enabled;
}
@Override
public boolean canEmail (String address) {
if (!isEnabled()) {
logger.atWarning().log("Not emailing %s (email is disabled)" , address);
return false ;
}
String domain = address.substring(address.lastIndexOf('@' ) + 1 );
if (isDenied(address, domain)) {
return false ;
}
return isAllowed(address, domain);
}
private boolean isDenied (String address, String domain) {
if (denyrcpt.isEmpty()) {
return false ;
}
if (denyrcpt.contains(address)
|| denyrcpt.contains(domain)
|| denyrcpt.contains("@" + domain)) {
logger.atWarning().log("Not emailing %s (prohibited by sendemail.denyrcpt)" , address);
return true ;
}
return false ;
}
private boolean isAllowed (String address, String domain) {
if (allowrcpt.isEmpty()) {
return true ;
}
if (allowrcpt.contains(address)
|| allowrcpt.contains(domain)
|| allowrcpt.contains("@" + domain)) {
return true ;
}
logger.atWarning().log("Not emailing %s (prohibited by sendemail.allowrcpt)" , address);
return false ;
}
@Override
public void send (
final Address from,
Collection
rcpt,
final Map callerHeaders,
String body)
throws EmailException {
send(from, rcpt, callerHeaders, body, null );
}
@Override
public void send (
final Address from,
Collection
rcpt,
final Map callerHeaders,
String textBody,
@Nullable String htmlBody)
throws EmailException {
if (!isEnabled()) {
throw new EmailException("Sending email is disabled" );
}
StringBuilder rejected = new StringBuilder();
try {
final SMTPClient client = open();
try {
if (!client.setSender(from .email())) {
throw new EmailException("Server " + smtpHost + " rejected from address " + from .email());
}
for (Address addr : rcpt) {
if (!client.addRecipient(addr.email())) {
String error = client.getReplyString();
rejected
.append("Server " )
.append(smtpHost)
.append(" rejected recipient " )
.append(addr)
.append(": " )
.append(error);
}
}
try (Writer messageDataWriter = client.sendMessageData()) {
if (messageDataWriter == null ) {
throw new EmailException(
rejected
.append("Server " )
.append(smtpHost)
.append(" rejected DATA command: " )
.append(client.getReplyString())
.toString());
}
render(messageDataWriter, callerHeaders, textBody, htmlBody);
if (!client.completePendingCommand()) {
throw new EmailException(
"Server " + smtpHost + " rejected message body: " + client.getReplyString());
}
client.logout();
if (rejected.length() > 0 ) {
throw new EmailException(rejected.toString());
}
}
} finally {
client.disconnect();
}
} catch (IOException e) {
throw new EmailException("Cannot send outgoing email" , e);
}
}
private void render(
Writer out,
Map callerHeaders,
String textBody,
@Nullable String htmlBody)
throws IOException, EmailException {
final Map hdrs = new LinkedHashMap<>(callerHeaders);
setMissingHeader(hdrs, "MIME-Version" , "1.0" );
setMissingHeader(hdrs, "Content-Transfer-Encoding" , "8bit" );
setMissingHeader(hdrs, "Content-Disposition" , "inline" );
setMissingHeader(hdrs, "User-Agent" , "Gerrit/" + Version.getVersion());
if (importance != null ) {
setMissingHeader(hdrs, "Importance" , importance);
}
if (expiryDays > 0 ) {
Date expiry = new Date (TimeUtil.nowMs() + expiryDays * 24 * 60 * 60 * 1000 L);
setMissingHeader(
hdrs, "Expiry-Date" , new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z" ).format(expiry));
}
String encodedBody;
if (htmlBody == null ) {
setMissingHeader(hdrs, "Content-Type" , "text/plain; charset=UTF-8" );
encodedBody = textBody;
} else {
String boundary = generateMultipartBoundary(textBody, htmlBody);
setMissingHeader(
hdrs,
"Content-Type" ,
"multipart/alternative; boundary=\"" + boundary + "\"; charset=UTF-8" );
encodedBody = buildMultipartBody(boundary, textBody, htmlBody);
}
try (Writer w = new BufferedWriter(out)) {
for (Map .Entry h : hdrs.entrySet()) {
if (!h.getValue().isEmpty()) {
w.write(h.getKey());
w.write(": " );
h.getValue().write(w);
w.write("\r\n" );
}
}
w.write("\r\n" );
w.write(encodedBody);
w.flush();
}
}
public static String generateMultipartBoundary(String textBody, String htmlBody)
throws EmailException {
byte[] bytes = new byte[8 ];
ThreadLocalRandom rng = ThreadLocalRandom.current();
for (int i = 0 ; i < 2 ; i++) {
rng.nextBytes(bytes);
String boundary = BaseEncoding.base64().encode(bytes);
String encBoundary = "--" + boundary;
if (textBody.contains(encBoundary) || htmlBody.contains(encBoundary)) {
continue ;
}
return boundary;
}
throw new EmailException("Gave up generating unique MIME boundary" );
}
protected String buildMultipartBody(String boundary, String textPart, String htmlPart)
throws IOException {
String encodedTextPart = quotedPrintableEncode(textPart);
String encodedHtmlPart = quotedPrintableEncode(htmlPart);
String textTransferEncoding = textPart.equals(encodedTextPart) ? "7bit" : "quoted-printable" ;
String htmlTransferEncoding = htmlPart.equals(encodedHtmlPart) ? "7bit" : "quoted-printable" ;
return
"--"
+ boundary
+ "\r\n"
+ "Content-Type: text/plain; charset=UTF-8\r\n"
+ "Content-Transfer-Encoding: "
+ textTransferEncoding
+ "\r\n"
+ "\r\n"
+ encodedTextPart
+ "\r\n"
+ "--"
+ boundary
+ "\r\n"
+ "Content-Type: text/html; charset=UTF-8\r\n"
+ "Content-Transfer-Encoding: "
+ htmlTransferEncoding
+ "\r\n"
+ "\r\n"
+ encodedHtmlPart
+ "\r\n"
+ "--"
+ boundary
+ "--\r\n" ;
}
protected String quotedPrintableEncode(String input) throws IOException {
ByteArrayOutputStream s = new ByteArrayOutputStream();
try (QuotedPrintableOutputStream qp = new QuotedPrintableOutputStream(s, false )) {
qp.write(input.getBytes(UTF_8));
}
return s.toString();
}
private static void setMissingHeader(Map hdrs, String name, String value) {
if (!hdrs.containsKey(name) || hdrs.get(name).isEmpty()) {
hdrs.put(name, new EmailHeader.String(value));
}
}
private SMTPClient open() throws EmailException {
final AuthSMTPClient client = new AuthSMTPClient(smtpEncryption == Encryption.SSL, sslVerify);
client.setConnectTimeout(connectTimeout);
try {
client.connect(smtpHost, smtpPort);
int replyCode = client.getReplyCode();
String replyString = client.getReplyString();
if (!SMTPReply.isPositiveCompletion(replyCode)) {
throw new EmailException(
String .format("SMTP server rejected connection: %d: %s" , replyCode, replyString));
}
if (!client.login()) {
throw new EmailException("SMTP server rejected HELO/EHLO greeting: " + replyString);
}
if (smtpEncryption == Encryption.TLS) {
if (!client.execTLS()) {
throw new EmailException("SMTP server does not support TLS" );
}
if (!client.login()) {
throw new EmailException("SMTP server rejected login: " + replyString);
}
}
if (smtpUser != null && !client.auth(smtpUser, smtpPass)) {
throw new EmailException("SMTP server rejected auth: " + replyString);
}
return client;
} catch (IOException | EmailException e) {
if (client.isConnected()) {
try {
client.disconnect();
} catch (IOException e2) {
}
}
if (e instanceof EmailException) {
throw (EmailException) e;
}
throw new EmailException(e.getMessage(), e);
}
}
}