Java tutorial
/* * Copyright (c) 2008-2012, Martijn Brinkers, Djigzo. * * This file is part of Djigzo email encryption. * * Djigzo is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License * version 3, 19 November 2007 as published by the Free Software * Foundation. * * Djigzo 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 along with Djigzo. If not, see <http://www.gnu.org/licenses/> * * Additional permission under GNU AGPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, * wsdl4j-1.6.1.jar (or modified versions of these libraries), * containing parts covered by the terms of Eclipse Public License, * tyrex license, freemarker license, dom4j license, mx4j license, * Spice Software License, Common Development and Distribution License * (CDDL), Common Public License (CPL) the licensors of this Program grant * you additional permission to convey the resulting work. */ package mitm.application.djigzo.james.mailets; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.activation.DataHandler; import javax.mail.BodyPart; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.internet.AddressException; import javax.mail.internet.MimeMessage; import javax.mail.util.ByteArrayDataSource; import mitm.application.djigzo.DjigzoHeader; import mitm.application.djigzo.User; import mitm.application.djigzo.UserProperties; import mitm.application.djigzo.james.DjigzoMailAttributes; import mitm.application.djigzo.james.DjigzoMailAttributesImpl; import mitm.application.djigzo.james.MailetUtils; import mitm.application.djigzo.james.PasswordContainer; import mitm.application.djigzo.service.SystemServices; import mitm.application.djigzo.workflow.UserWorkflow; import mitm.common.mail.EmailAddressUtils; import mitm.common.mail.HeaderUtils; import mitm.common.mail.MessageIDCreator; import mitm.common.mail.MimeMessageWithID; import mitm.common.mail.matcher.ContentHeaderNameMatcher; import mitm.common.mail.matcher.HeaderMatcher; import mitm.common.mail.matcher.NotHeaderNameMatcher; import mitm.common.pdf.FontProvider; import mitm.common.pdf.MessagePDFBuilder; import mitm.common.pdf.OpenPermission; import mitm.common.pdf.ViewerPreference; import mitm.common.properties.HierarchicalPropertiesException; import mitm.common.security.SecurityFactoryFactoryException; import mitm.common.security.crypto.EncryptorException; import mitm.common.security.password.PasswordGenerator; import mitm.common.security.password.PasswordGeneratorImpl; import mitm.common.util.Check; import mitm.common.util.URLBuilder; import mitm.common.util.URLBuilderException; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.apache.commons.lang.text.StrBuilder; import org.apache.james.core.MailImpl; import org.apache.mailet.Mail; import org.apache.mailet.MailAddress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.lowagie.text.DocumentException; import com.lowagie.text.pdf.PdfEncryptor; import com.lowagie.text.pdf.PdfReader; import com.lowagie.text.pdf.PdfWriter; import freemarker.template.SimpleHash; import freemarker.template.Template; import freemarker.template.TemplateException; /** * Mailet that creates and sends new message(s) with the source message converted to PDF, encrypted with a password * and attached to the new message. The PDF password(s) to use are extracted from the mail attributes. The PDF will * be encrypted using AES 128. If the ownerPasswordMode is random a 16 bytes random number will be generated which * is used for the owner password. * * Supported mailet parameters: * * encryptedProcessor : the next processor for the PDF encrypted message * notEncryptedProcessor : the next processor if the message could not be encrypted * passwordMode : single or multiple. In 'single' mode one PDF will be created and encrypted with just one * password. All recipients will receive the same encrypted PDF. In 'multiple' mode each * recipients should have a password which will be used to encrypt a PDF with. In 'multiple' * mode mutiple messages will be sent (one for each * recipient) (default: single) * ownerPasswordMode : sets the password for the owner of the PDF (sameAsUser or random) (default: sameAsUser) * viewerPreference : the viewer preferences of the PDF. See {@link ViewerPreference} (default: null) * templateProperty : the user property from which the template should be read * template : path to the Freemarker template file (only used if templateProperty cannot be found) * openPermission : the open permission of the PDF. See {@link OpenPermission} (default: null) * passThrough : if false the source message will be removed (ghost'ed) * maxSubjectLength : the maximum length of the subject * catchRuntimeExceptions : if true all RunTimeExceptions are caught (default: true) * catchErrors : if true all Errors are caught (default: true) * * @author Martijn Brinkers * */ public class PDFEncrypt extends SenderTemplatePropertySendMail { private final static Logger logger = LoggerFactory.getLogger(PDFEncrypt.class); /* * PDFEncrypt Freemarker template parameter names */ protected static final String REPLY_URL_TEMPLATE_PARAM = "replyURL"; protected static final String PASSWORD_CONTAINER_TEMPLATE_PARAM = "passwordContainer"; protected static final String PASSWORD_ID_TEMPLATE_PARAM = "passwordID"; /* * The key under which the ReplySettings will be stored in the activation context */ private final static String REPLY_SETTINGS_ACTIVATION_CONTEXT_KEY = "replySettings"; /* * Number of bytes we will add to the buffer for the encrypted PDF to try to * prevent buffer growth. */ private final static int ENCRYPTION_OVERHEAD = 4096; /* * PDF Encryption type and strength. */ private int pdfEncryptionType = PdfWriter.ENCRYPTION_AES_128; /* * Processor to use for the encrypted pdf message. */ private String encryptedProcessor; /* * Processor to use for the non encrypted pdf message. */ private String notEncryptedProcessor; /* * Password mode determines whether a single encrypted pdf will be created or that * multiple encrypted pdf's will be created. */ private PasswordMode passwordMode = PasswordMode.SINGLE; /* * Determines what the owner password should be */ private OwnerPasswordMode ownerPasswordMode = OwnerPasswordMode.SAME_AS_USER; /* * Number of bytes used for the randomly generated user password. Only applicable with OwnerPasswordMode.RANDOM. */ private int ownerPasswordLength = 16; /* * Used for generating the owner password. Only applicable with OwnerPasswordMode.RANDOM. */ private PasswordGenerator passwordGenerator; /* * The viewer preferences for the pdf document. */ private Set<ViewerPreference> viewerPreferences; /* * Permissions of the PDF when the PDF is opened by the end user. */ private Set<OpenPermission> openPermissions = new HashSet<OpenPermission>(); /* * Maximum length of the subject for the reply URL. If size exceeds this maximum size the subject * will be truncated. * */ private int maxSubjectLength = 80; /* * If true the original Message-ID will be used for the encrypted message. * * Note: be very carefull when retaining the message-id. Every message should have a unique message-id * See https://jira.djigzo.com/browse/GATEWAY-38 */ private boolean retainMessageID = false; /* * If true and a reply-to header is available and valid, the reply to the PDF will be * sent to the reply-to */ private boolean useReplyTo = true; /* * Service that provides extra fonts to use with the PDF */ private FontProvider fontProvider; /* * The mailet initialization parameters used by this mailet. */ private enum Parameter { ENCRYPTED_PROCESSOR("encryptedProcessor"), NOT_ENCRYPTED_PROCESSOR("notEncryptedProcessor"), PASSWORD_MODE( "passwordMode"), OWNER_PASSWORD_MODE("ownerPasswordMode"), VIEWER_PREFERENCE( "viewerPreference"), OPEN_PERMISSION("openPermission"), MAX_SUBJECT_LENGTH( "maxSubjectLength"), RETAIN_MESSAGE_ID( "retainMessageID"), USE_REPLY_TO("useReplyTo"); private String name; private Parameter(String name) { this.name = name; } }; private enum PasswordMode { SINGLE("single"), MULTIPLE("multiple"); private String name; private PasswordMode(String name) { this.name = name; } public static PasswordMode fromName(String name) { for (PasswordMode mode : PasswordMode.values()) { if (mode.name.equalsIgnoreCase(name)) { return mode; } } return null; } }; private enum OwnerPasswordMode { SAME_AS_USER("sameAsUser"), RANDOM("random"); private String name; private OwnerPasswordMode(String name) { this.name = name; } public static OwnerPasswordMode fromName(String name) { for (OwnerPasswordMode mode : OwnerPasswordMode.values()) { if (mode.name.equalsIgnoreCase(name)) { return mode; } } return null; } }; /* * Helper class for storing some settings in the activation context */ private static class ReplySettings { /* * The subject of the reply (in parameter) */ private final String subject; /* * The recipients of the reply (in parameter) */ private final Collection<MailAddress> recipients; /* * The reply URL (out parameter) */ private String replyURL; /* * The reply-to of the source email. Null if no reply-to header was set. */ private String replyTo; public ReplySettings(String subject, Collection<MailAddress> recipients, String replyTo) { this.subject = subject; this.recipients = recipients; this.replyTo = replyTo; } public String getReplyURL() { return replyURL; } public void setReplyURL(String replyURL) { this.replyURL = replyURL; } public String getSubject() { return subject; } public Collection<MailAddress> getRecipients() { return recipients; } public String getReplyTo() { return replyTo; } } @Override protected Logger getLogger() { return logger; } private void initOpenPermissions() { String param = StringUtils.trimToNull(getInitParameter(Parameter.OPEN_PERMISSION.name)); if (param != null) { String[] permissions = StringUtils.split(param, ','); for (String permissionName : permissions) { permissionName = StringUtils.trim(permissionName); OpenPermission openPermission = OpenPermission.fromName(permissionName); if (openPermission == null) { throw new IllegalArgumentException(permissionName + " is not a valid OpenPermission."); } openPermissions.add(openPermission); } } /* * Set default permissions to all if nothing was set. */ if (openPermissions.size() == 0) { openPermissions.add(OpenPermission.ALL); } } private void initViewerPreferences() { String param = StringUtils.trimToNull(getInitParameter(Parameter.VIEWER_PREFERENCE.name)); if (param != null) { String[] preferences = StringUtils.split(param, ','); for (String preferenceName : preferences) { preferenceName = StringUtils.trim(preferenceName); ViewerPreference viewerPreference = ViewerPreference.fromName(preferenceName); if (viewerPreference == null) { throw new IllegalArgumentException(preferenceName + " is not a valid ViewerPreference."); } if (viewerPreferences == null) { viewerPreferences = new HashSet<ViewerPreference>(); } viewerPreferences.add(viewerPreference); } } } private void initPasswordMode() { String param = getInitParameter(Parameter.PASSWORD_MODE.name); if (param != null) { passwordMode = PasswordMode.fromName(param); if (passwordMode == null) { throw new IllegalArgumentException(param + " is not a valid PasswordMode."); } } } private void initOwnerPasswordMode() { String param = getInitParameter(Parameter.OWNER_PASSWORD_MODE.name); if (param != null) { ownerPasswordMode = OwnerPasswordMode.fromName(param); if (ownerPasswordMode == null) { throw new IllegalArgumentException(param + " is not a valid OwnerPasswordMode."); } } } private void initPasswordGenerator() { try { passwordGenerator = new PasswordGeneratorImpl(); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("passwordGenerator could not be initialized.", e); } catch (NoSuchProviderException e) { throw new IllegalStateException("passwordGenerator could not be initialized.", e); } catch (SecurityFactoryFactoryException e) { throw new IllegalStateException("passwordGenerator could not be initialized.", e); } } private void initRetainMessageID() { retainMessageID = getBooleanInitParameter(Parameter.RETAIN_MESSAGE_ID.name, retainMessageID); } private void initUseReplyTo() { useReplyTo = getBooleanInitParameter(Parameter.USE_REPLY_TO.name, useReplyTo); } @Override public void initMailet() throws MessagingException { super.initMailet(); fontProvider = SystemServices.getFontProvider(); encryptedProcessor = getInitParameter(Parameter.ENCRYPTED_PROCESSOR.name); if (encryptedProcessor == null) { throw new IllegalArgumentException("encryptedProcessor must be specified."); } notEncryptedProcessor = getInitParameter(Parameter.NOT_ENCRYPTED_PROCESSOR.name); if (notEncryptedProcessor == null) { throw new IllegalArgumentException("notEncryptedProcessor must be specified."); } String param = getInitParameter(Parameter.MAX_SUBJECT_LENGTH.name); if (param != null) { maxSubjectLength = NumberUtils.toInt(param, maxSubjectLength); } initPasswordGenerator(); initPasswordMode(); initOwnerPasswordMode(); initOpenPermissions(); initViewerPreferences(); initRetainMessageID(); initUseReplyTo(); StrBuilder sb = new StrBuilder(); sb.append("encryptedProcessor: "); sb.append(encryptedProcessor); sb.append("; "); sb.append("notEncryptedProcessor: "); sb.append(notEncryptedProcessor); sb.append("; "); sb.append("passwordMode: "); sb.append(passwordMode); sb.append("; "); sb.append("ownerPasswordMode: "); sb.append(ownerPasswordMode); sb.append("; "); sb.append("openPermissions: "); sb.append(openPermissions); sb.append("; "); sb.append("viewerPreferences: "); sb.append(viewerPreferences); sb.append("; "); sb.append("retainMessageID: "); sb.append(retainMessageID); sb.append("; "); sb.append("useReplyTo: "); sb.append(useReplyTo); getLogger().info(sb.toString()); } /* * Called with the originating user * * @see mitm.application.djigzo.james.mailets.SenderTemplatePropertySendMail#onHandleUserEvent(mitm.application.djigzo.User) */ @Override protected Template getTemplateFromUser(User user, SimpleHash root) throws MessagingException, HierarchicalPropertiesException, IOException { /* * Single password mode does not support PDF reply because with Single password mode only one PDF will be sent * to multiple recipients. */ if (passwordMode == PasswordMode.MULTIPLE) { try { UserProperties properties = user.getUserPreferences().getProperties(); String baseReplyURL = properties.getPdfReplyURL(); String serverSecret = properties.getServerSecret(); boolean senderAllowed = properties.isPdfReplyAllowed(); ReplySettings replySettings = getActivationContext().get(REPLY_SETTINGS_ACTIVATION_CONTEXT_KEY, ReplySettings.class); if (replySettings != null) { if (senderAllowed) { /* * Use the Reply-To of the message if available */ String replyTo = null; if (useReplyTo) { replyTo = replySettings.getReplyTo(); } /* * Fallback to the user's email address (i.e., the from) if the Reply-To is not * available or not valid. */ if (StringUtils.isEmpty(replyTo)) { replyTo = user.getEmail(); } String replyURL = createReplyURL(baseReplyURL, replySettings.getSubject(), replySettings.getRecipients(), user.getEmail(), replyTo, serverSecret); if (replyURL != null) { replySettings.setReplyURL(replyURL); /* * Set the replyURL in the Freemarker conext so it can be used in the * PDF encryption message templates */ root.put(REPLY_URL_TEMPLATE_PARAM, replyURL); } } else { getLogger().debug("Sender {} does not allow reply to PDF.", user.getEmail()); } } else { getLogger().warn("ReplySettings are not set"); } } catch (HierarchicalPropertiesException e) { throw new MessagingException("Error getting reply pdf properties", e); } } return super.getTemplateFromUser(user, root); } /* * Returns the pdf password embedded in the mail attributes. Null if password is not available. */ private PasswordContainer getPasswordContainer(Mail mail) throws EncryptorException { DjigzoMailAttributes mailAttributes = new DjigzoMailAttributesImpl(mail); byte[] encryptedPassword = mailAttributes.getEncryptedPassword(); String passwordID = mailAttributes.getPasswordID(); PasswordContainer passwordContainer = null; if (encryptedPassword != null) { passwordContainer = new PasswordContainer(encryptedPassword, passwordID); } return passwordContainer; } /* * Returns the pdf passwords embedded in the mail attributes. Null if passwords are not available. */ private Map<String, PasswordContainer> getPasswords(Mail mail) { DjigzoMailAttributes mailAttributes = new DjigzoMailAttributesImpl(mail); return mailAttributes.getPasswords(); } private void addEncryptedPDF(MimeMessage message, byte[] pdf) throws MessagingException { /* * Find the existing PDF. The expect that the message is a multipart/mixed. */ if (!message.isMimeType("multipart/mixed")) { throw new MessagingException("Content-type should have been multipart/mixed."); } Multipart mp; try { mp = (Multipart) message.getContent(); } catch (IOException e) { throw new MessagingException("Error getting message content.", e); } BodyPart pdfPart = null; /* * Fallback in case the template does not contain a DjigzoHeader.MARKER */ BodyPart fallbackPart = null; for (int i = 0; i < mp.getCount(); i++) { BodyPart part = mp.getBodyPart(i); if (ArrayUtils.contains(part.getHeader(DjigzoHeader.MARKER), DjigzoHeader.ATTACHMENT_MARKER_VALUE)) { pdfPart = part; break; } /* * Fallback scanning for application/pdf in case the template does not contain a DjigzoHeader.MARKER */ if (part.isMimeType("application/pdf")) { fallbackPart = part; } } if (pdfPart == null) { if (fallbackPart != null) { getLogger().info("Marker not found. Using ocet-stream instead."); /* * Use the octet-stream part */ pdfPart = fallbackPart; } else { throw new MessagingException("Unable to find the attachment part in the template."); } } pdfPart.setDataHandler(new DataHandler(new ByteArrayDataSource(pdf, "application/pdf"))); } private int getOpenPermissionsIntValue() { int intValue = 0; for (OpenPermission permission : openPermissions) { intValue = intValue | permission.intValue(); } return intValue; } private byte[] encryptPDF(byte[] unencryptedPDF, String userPassword, String ownerPassword) throws IOException, DocumentException { PdfReader pdfReader = new PdfReader(unencryptedPDF); ByteArrayOutputStream encryptedPDF = new ByteArrayOutputStream(unencryptedPDF.length + ENCRYPTION_OVERHEAD); PdfEncryptor.encrypt(pdfReader, encryptedPDF, pdfEncryptionType, userPassword, ownerPassword, getOpenPermissionsIntValue()); return encryptedPDF.toByteArray(); } private String getOwnerPassword(String userPassword) { switch (ownerPasswordMode) { case SAME_AS_USER: return userPassword; case RANDOM: return passwordGenerator.generatePassword(ownerPasswordLength); default: throw new IllegalArgumentException("Unknown OwnerPasswordMode."); } } /* * Checks if the user with email allows replies to PDF */ private boolean isReplyAllowed(String email) { boolean allowed = false; try { allowed = userWorkflow.getUser(email, UserWorkflow.GetUserMode.CREATE_IF_NOT_EXIST).getUserPreferences() .getProperties().isPdfReplyAllowed(); } catch (AddressException e) { getLogger().error("Error parsing email " + email, e); } catch (HierarchicalPropertiesException e) { getLogger().error("Error getting isReplyAllowed property for user " + email); } return allowed; } private String createReplyURL(String baseURL, String subject, Collection<MailAddress> recipients, String sender, String replyTo, String serverSecret) throws MessagingException { String replyURL = null; if (EmailAddressUtils.INVALID_EMAIL.equals(replyTo) || StringUtils.isEmpty(replyTo)) { /* * This happens if the sender of the email is an invalid sender. This will normally only happen if * the global settings allow PDF reply. */ getLogger().warn("Reply-To of the message is an invalid email address. " + "It's not possible to reply to the PDF."); } else if (baseURL != null && serverSecret != null) { /* * The from of the reply (is equal to the recipient of the encrypted PDF) */ String recipient = null; if (recipients.size() == 1) { String notYetValidatedRecipient = recipients.iterator().next().toString(); recipient = EmailAddressUtils.canonicalizeAndValidate(notYetValidatedRecipient, true); if (recipient == null) { getLogger().warn("{} is not a valid recipient.", notYetValidatedRecipient); } } else { getLogger().warn("The mail has multiple recipients. It's not possible to reply to the PDF."); } if (recipient != null && !isReplyAllowed(recipient)) { getLogger().debug("Recipient {} does not allow reply to PDF.", recipient); recipient = null; } if (sender != null && recipient != null) { /* * Make sure the subject is not too long */ subject = StringUtils.abbreviate(StringUtils.trimToEmpty(subject), maxSubjectLength); URLBuilder uRLBuilder = SystemServices.getURLBuilder(); PDFReplyURLBuilder replyBuilder = new PDFReplyURLBuilder(uRLBuilder); replyBuilder.setBaseURL(baseURL); replyBuilder.setUser(sender); replyBuilder.setRecipient(replyTo); /* * The from of the reply message is equal to the original recipient of the PDF (i.e, if the * recipient clicks reply, the sender, aka from, of the reply is set to the recipient of the pdf). */ replyBuilder.setFrom(recipient); replyBuilder.setSubject(subject); replyBuilder.setKey(serverSecret); try { replyURL = replyBuilder.buildURL(); } catch (URLBuilderException e) { throw new MessagingException("Building reply URL failed.", e); } } } return replyURL; } private MimeMessage createMessage(Mail mail, Collection<MailAddress> recipients, PasswordContainer passwordContainer) throws MessagingException, MissingRecipientsException, TemplateException, IOException { Check.notNull(passwordContainer, "passwordContainer"); SimpleHash root = new SimpleHash(); root.put(PASSWORD_CONTAINER_TEMPLATE_PARAM, passwordContainer); /* * Note: although passwordID can be retrieved from passwordContainer we keep passwordID to make sure * that existing templates that use passwordID are still working */ root.put(PASSWORD_ID_TEMPLATE_PARAM, passwordContainer.getPasswordID()); /* * We should put the real recipient(s) in the Freemarker root. bug https://jira.djigzo.com/browse/GATEWAY-38. */ root.put(RECIPIENTS_TEMPLATE_PARAM, recipients); /* * Get the Reply-To of the message and validate. * * Note: the Reply-To is not canonicalized because that can result in invalid email addresses. For example * "email with space"@example.com is only valid with the quotes. */ String replyTo = EmailAddressUtils.validate(EmailAddressUtils.getEmailAddress( EmailAddressUtils.getAddress(EmailAddressUtils.getReplyToQuietly(mail.getMessage())))); /* * The ReplySettings will be placed in the context since they are needed in #getTemplateFromUser to * get the reply URL */ ReplySettings replySettings = new ReplySettings(mail.getMessage().getSubject(), recipients, replyTo); getActivationContext().set(REPLY_SETTINGS_ACTIVATION_CONTEXT_KEY, replySettings); /* * Create a message from a template. * * Note: Calling createMessage will result in a call to #getTemplateFromUser */ MimeMessage containerMessage = createMessage(mail, root); /* * The call to createMessage should result in the reply URL to be set (if reply is allowed and all * required settings are set) */ String replyURL = replySettings.getReplyURL(); /* * Copy all non content headers from source to notificationMessage. */ HeaderMatcher nonContentMatcher = new NotHeaderNameMatcher(new ContentHeaderNameMatcher()); HeaderUtils.copyHeaders(mail.getMessage(), containerMessage, nonContentMatcher); /* * Create PDF from the source message */ ByteArrayOutputStream pdfStream = new ByteArrayOutputStream(); MessagePDFBuilder pdfBuilder = new MessagePDFBuilder(); pdfBuilder.setFontProvider(fontProvider); if (viewerPreferences != null) { pdfBuilder.setViewerPreference(viewerPreferences); } try { pdfBuilder.buildPDF(mail.getMessage(), replyURL, pdfStream); } catch (DocumentException e) { throw new MessagingException("Error building PDF.", e); } catch (IOException e) { throw new MessagingException("Error building PDF.", e); } byte[] unencryptedPDF = pdfStream.toByteArray(); byte[] encryptedPdf; try { String userPassword = passwordContainer.getPassword(); String ownerPassword = getOwnerPassword(userPassword); encryptedPdf = encryptPDF(unencryptedPDF, userPassword, ownerPassword); } catch (IOException e) { throw new MessagingException("Error encrypting PDF.", e); } catch (DocumentException e) { throw new MessagingException("Error encrypting PDF.", e); } catch (EncryptorException e) { throw new MessagingException("Unable to retrieve password.", e); } /* * Now find and replace the pdf inside the notificationMessage with the encrypted pdf */ addEncryptedPDF(containerMessage, encryptedPdf); containerMessage.saveChanges(); String messageID = null; if (retainMessageID) { messageID = StringUtils.trimToNull(mail.getMessage().getMessageID()); } if (messageID == null) { messageID = MessageIDCreator.getInstance().createUniqueMessageID(); } containerMessage = new MimeMessageWithID(containerMessage, messageID); return containerMessage; } private void sendNewMessage(Mail mail, Collection<MailAddress> recipients, MimeMessage message, String processor) throws MessagingException { /* * We need to create a new Mail object this way to make sure that all attributes are cloned. */ MailImpl newMail = new MailImpl(mail, MailetUtils.createUniqueMailName()); try { newMail.setRecipients(recipients); newMail.setMessage(message); newMail.setState(processor); getMailetContext().sendMail(newMail); } finally { newMail.dispose(); } } private void sendNotification(Mail mail, Collection<MailAddress> recipients, PasswordContainer passwordContainer) throws MessagingException, MissingRecipientsException, TemplateException, IOException { if (passwordContainer != null) { MimeMessage message = createMessage(mail, recipients, passwordContainer); this.sendNewMessage(mail, recipients, message, encryptedProcessor); } else { getLogger().warn("PDF Password not found. Sending message unencrypted."); this.sendNewMessage(mail, recipients, mail.getMessage(), notEncryptedProcessor); } } /* * One message for all recipients will be created */ private void sendNotificationSinglePasswordMode(Mail mail) throws MessagingException, MissingRecipientsException, TemplateException, IOException { PasswordContainer passwordContainer; try { passwordContainer = getPasswordContainer(mail); } catch (EncryptorException e) { throw new MessagingException("Unable to retrieve password.", e); } sendNotification(mail, getRecipients(mail), passwordContainer); } /* * For each recipient a new message with an encrypted PDF will be created. */ private void sendNotificationMultiplePasswordMode(Mail mail) throws MessagingException, MissingRecipientsException, TemplateException, IOException { Map<String, PasswordContainer> passwords = getPasswords(mail); if (passwords != null) { Collection<MailAddress> recipients = getRecipients(mail); for (MailAddress recipient : recipients) { /* * We will only accept valid email addresses. If an email address is invalid the message * will not be enrypted */ String validatedEmail = EmailAddressUtils .canonicalizeAndValidate(recipient.toInternetAddress().getAddress(), true); PasswordContainer passwordContainer = (validatedEmail != null ? passwords.get(validatedEmail) : null); sendNotification(mail, Collections.singleton(recipient), passwordContainer); } } else { getLogger().warn("PDF passwords not found. Sending message unencrypted."); sendNotification(mail, getRecipients(mail), null); } } @Override protected void sendMail(Mail mail) throws MessagingException, MissingRecipientsException, TemplateException, IOException { switch (passwordMode) { case SINGLE: sendNotificationSinglePasswordMode(mail); break; case MULTIPLE: sendNotificationMultiplePasswordMode(mail); break; default: throw new IllegalArgumentException("Unknown passwordMode."); } } }