/*
* LinShare is an open source filesharing software, part of the LinPKI software
* suite, developed by Linagora.
*
* Copyright (C) 2015 LINAGORA
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version, provided you comply with the Additional Terms applicable for
* LinShare software by Linagora pursuant to Section 7 of the GNU Affero General
* Public License, subsections (b), (c), and (e), pursuant to which you must
* notably (i) retain the display of the “LinShare™” trademark/logo at the top
* of the interface window, the display of the “You are using the Open Source
* and free version of LinShare™, powered by Linagora © 2009–2015. Contribute to
* Linshare R&D by subscribing to an Enterprise offer!” infobox and in the
* e-mails sent with the Program, (ii) retain all hypertext links between
* LinShare and linshare.org, between linagora.com and Linagora, and (iii)
* refrain from infringing Linagora intellectual property rights over its
* trademarks and commercial brands. Other Additional Terms apply, see
* <http://www.linagora.com/licenses/> for more details.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License and
* its applicable Additional Terms for LinShare along with this program. If not,
* see <http://www.gnu.org/licenses/> for the GNU Affero General Public License
* version 3 and <http://www.linagora.com/licenses/> for the Additional Terms
* applicable to LinShare software.
*/
package org.linagora.linshare.core.service.impl;
import java.io.File;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Properties;
import javax.activation.DataHandler;
import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.SendFailedException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;
import org.apache.commons.collections.CollectionUtils;
import org.linagora.linshare.core.domain.objects.MailContainer;
import org.linagora.linshare.core.domain.objects.MailContainerWithRecipient;
import org.linagora.linshare.core.exception.BusinessErrorCode;
import org.linagora.linshare.core.exception.BusinessException;
import org.linagora.linshare.core.exception.TechnicalErrorCode;
import org.linagora.linshare.core.exception.TechnicalException;
import org.linagora.linshare.core.service.NotifierService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
/**
* This class builds an email notification and sends the email.
*
* 0.8.2 : now the sender is always linshare email address and
* the email address of the human acting in LinShare is in the
* REPLY-TO address (avoid spam exclusion).
*/
public class MailNotifierServiceImpl implements NotifierService {
/** The smtpServer that will send the email. */
private String smtpServer;
/** The smtp user. */
private final String smtpUser;
/** The smtp password. */
private final String smtpPassword;
/** The smtp port. */
private Integer smtpPort;
/** Is the server needing authentification. */
private final boolean needsAuth;
/** Mail charset. */
private final String charset;
/** Display LinShare logo ? */
private final boolean displayLogo;
/** Display LinShare logo ? */
private final boolean displayLicenceLogo;
/** External logo to display */
private final String externalLogo;
/** Class logger */
private static final Logger logger = LoggerFactory.getLogger(MailNotifierServiceImpl.class);
private static final CharsetEncoder asciiEncoder = Charset.forName("US-ASCII").newEncoder();
/**
* see http://java.sun.com/developer/EJTechTips/2004/tt0625.html for
* multipart/alternative
*/
public MailNotifierServiceImpl(String smtpServer, int smtpPort,
String smtpUser, String smtpPassword, boolean needsAuth,
String charset, boolean displayLogo, boolean displayLicenceLogo, String externalLogo) {
this.smtpServer = smtpServer;
this.smtpPort = smtpPort;
this.smtpUser = smtpUser;
this.smtpPassword = smtpPassword;
this.needsAuth = needsAuth;
this.charset = charset;
this.displayLogo = displayLogo;
this.displayLicenceLogo = displayLicenceLogo;
this.externalLogo = externalLogo;
}
public static boolean isPureAscii(String v) {
return asciiEncoder.canEncode(v);
}
@Override
public void sendNotification(String smtpSender, String replyTo, String recipient, String subject, String htmlContent, String textContent, String inReplyTo, String references) throws SendFailedException {
if (smtpServer.equals("")) {
logger.warn("Mail notifications are disabled.");
return;
}
// get the mail session
Session session = getMailSession();
// Define message
MimeMessage messageMim = new MimeMessage(session);
try {
messageMim.setFrom(new InternetAddress(smtpSender));
if (replyTo != null) {
InternetAddress reply[] = new InternetAddress[1];
reply[0] = new InternetAddress(replyTo);
messageMim.setReplyTo(reply);
}
messageMim.addRecipient(javax.mail.Message.RecipientType.TO,
new InternetAddress(recipient));
if (inReplyTo != null && inReplyTo != "") {
// This field should contain only ASCCI character (RFC 822)
if(isPureAscii(inReplyTo)) {
messageMim.setHeader("In-Reply-To", inReplyTo);
}
}
if (references != null && references != "") {
// This field should contain only ASCCI character (RFC 822)
if(isPureAscii(references)) {
messageMim.setHeader("References", references);
}
}
messageMim.setSubject(subject, charset);
// Create a "related" Multipart message
// content type is multipart/alternative
// it will contain two part BodyPart 1 and 2
Multipart mp = new MimeMultipart("alternative");
// BodyPart 2
// content type is multipart/related
// A multipart/related is used to indicate that message parts should
// not be considered individually but rather
// as parts of an aggregate whole. The message consists of a root
// part (by default, the first) which reference other parts inline,
// which may in turn reference other parts.
Multipart html_mp = new MimeMultipart("related");
// Include an HTML message with images.
// BodyParts: the HTML file and an image
// Get the HTML file
BodyPart rel_bph = new MimeBodyPart();
rel_bph.setDataHandler(new DataHandler(new ByteArrayDataSource(
htmlContent, "text/html; charset=" + charset)));
html_mp.addBodyPart(rel_bph);
// inline image ?
if (displayLogo || displayLicenceLogo ) {
String cid = "image.part.1@linshare.org";
MimeBodyPart rel_bpi = new MimeBodyPart();
// Initialize and add the image file to the html body part
rel_bpi.setFileName("mail_logo.png");
rel_bpi.setText("linshare");
URL resource = null;
if (displayLicenceLogo) {
resource = getClass().getResource("/org/linagora/linshare/core/service/mail_logo_licence.png");
} else {
if (externalLogo != null && !"".equals(externalLogo)){
File file = new File(externalLogo);
if (file.canRead()) {
resource = file.toURI().toURL();
} else {
logger.error("Can not read your personal logo.");
}
}
if (resource == null) {
resource = getClass().getResource("/org/linagora/linshare/core/service/mail_logo.png");
}
}
if(resource == null) {
logger.error("Embedded logo was not found.");
throw new TechnicalException(TechnicalErrorCode.MAIL_EXCEPTION, "Error sending notification : embedded logo was not found.");
}
rel_bpi.setDataHandler(new DataHandler(resource));
rel_bpi.setHeader("Content-ID", "<" + cid + ">");
rel_bpi.setDisposition("inline");
html_mp.addBodyPart(rel_bpi);
}
// Create the second BodyPart of the multipart/alternative,
// set its content to the html multipart, and add the
// second bodypart to the main multipart.
BodyPart alt_bp2 = new MimeBodyPart();
alt_bp2.setContent(html_mp);
mp.addBodyPart(alt_bp2);
messageMim.setContent(mp);
// RFC 822 "Date" header field
// Indicates that the message is complete and ready for delivery
messageMim.setSentDate(new GregorianCalendar().getTime());
// Since we used html tags, the content must be marker as text/html
// messageMim.setContent(content,"text/html; charset="+charset);
Transport tr = session.getTransport("smtp");
// Connect to smtp server, if needed
if (needsAuth) {
tr.connect(smtpServer, smtpPort, smtpUser, smtpPassword);
messageMim.saveChanges();
tr.sendMessage(messageMim, messageMim.getAllRecipients());
tr.close();
} else {
// Send message
Transport.send(messageMim);
}
} catch (SendFailedException e) {
logger.error("Error sending notification on " + smtpServer + " port " + smtpPort +" to "+ recipient, e);
throw e;
} catch (MessagingException e) {
logger.error("Error sending notification on " + smtpServer + " port " + smtpPort, e);
throw new TechnicalException(TechnicalErrorCode.MAIL_EXCEPTION, "Error sending notification", e);
} catch (Exception e) {
logger.error("Error sending notification on " + smtpServer + " port " + smtpPort, e);
throw new TechnicalException(TechnicalErrorCode.MAIL_EXCEPTION, "Error sending notification", e);
}
}
/**
* Create some properties and get the default Session
*/
private Session getMailSession() {
// Set the host smtp address
Properties props = new Properties();
props.put("mail.smtp.host", smtpServer);
props.put("mail.smtp.port", smtpPort + "");
if (needsAuth) {
props.put("mail.smtp.auth", "true");
} else {
props.put("mail.smtp.auth", "false");
}
// create some properties and get the default Session
Session session = Session.getInstance(props, null);
if (logger.isDebugEnabled()) {
session.setDebug(true);
} else {
session.setDebug(false);
}
return session;
}
/**
* Send notification giving a mailContainer object.
*/
@Override
public void sendNotification(String smtpSender, String replyTo, String recipient, MailContainer mailContainer) throws SendFailedException{
sendNotification(smtpSender, replyTo, recipient, mailContainer.getSubject(), mailContainer.getContentHTML(), mailContainer.getContentTXT(), mailContainer.getInReplyTo(), mailContainer.getReferences());
}
/**
* Send multiple notifications giving a mailContainerWithRecipient object.
*/
@Override
public void sendNotification(List<MailContainerWithRecipient> mailContainerWithRecipient) throws BusinessException {
if(CollectionUtils.isNotEmpty(mailContainerWithRecipient)) {
List<String> unknownRecipients = Lists.newArrayList();
for (MailContainerWithRecipient mailContainer : mailContainerWithRecipient) {
if (mailContainer == null) {
continue;
}
try {
if (mailContainer.getRecipient() == null) {
logger.error("can not send mails, no recipient");
} else {
sendNotification(mailContainer.getFrom(), mailContainer.getReplyTo(), mailContainer.getRecipient(), mailContainer);
}
} catch (SendFailedException e) {
unknownRecipients.add(mailContainer.getRecipient());
logger.debug(e.toString());
}
}
if(!unknownRecipients.isEmpty()){
logger.error("Addresses unreachables : " + unknownRecipients.toString());
throw new BusinessException(BusinessErrorCode.RELAY_HOST_NOT_ENABLE, "Address Unreachable", unknownRecipients);
}
} else {
logger.debug("can not send mails, input list empty");
}
}
@Override
public void sendNotification(MailContainerWithRecipient mailContainer) throws BusinessException {
this.sendNotification(Lists.newArrayList(mailContainer));
}
@Override
public String getHost() {
return smtpServer;
}
@Override
public void setHost(String host) {
logger.warn("Reconfiguring Smtp current server ...");
synchronized (smtpServer) {
try {
smtpServer = host;
logger.warn("Smtp current server reconfigured to " + smtpServer);
} catch (Exception e) {
e.printStackTrace();
logger.error("Smtp reconfiguration failed ! ");
}
}
}
@Override
public Integer getPort() {
return smtpPort;
}
@Override
public void setPort(Integer port) throws Exception {
logger.warn("Reconfiguring Smtp current port ...");
if (port.equals(0)) {
throw new Exception("invalid port value : " + port);
}
synchronized (smtpPort) {
try {
smtpPort = port;
logger.warn("Smtp current port reconfigured to " + smtpPort);
} catch (Exception e) {
e.printStackTrace();
logger.error("Smtp reconfiguration failed ! ");
}
}
}
}