Java tutorial
/* * Copyright (c) 2009-2011, 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.IOException; import javax.activation.DataHandler; import javax.mail.MessagingException; import javax.mail.Part; import javax.mail.internet.MimeMessage; import javax.mail.util.ByteArrayDataSource; import mitm.application.djigzo.DjigzoHeader; import mitm.common.mail.HeaderUtils; import mitm.common.mail.PartException; import mitm.common.mail.PartScanner; import mitm.common.mail.PartScanner.PartListener; import mitm.common.mail.matcher.ContentHeaderNameMatcher; import mitm.common.mail.matcher.HeaderMatcher; import mitm.common.mail.matcher.NotHeaderNameMatcher; import mitm.common.security.smime.SMIMEType; import mitm.common.security.smime.SMIMEUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.lang.math.NumberUtils; import org.apache.commons.lang.mutable.MutableObject; import org.apache.commons.lang.text.StrBuilder; import org.apache.mailet.Mail; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import freemarker.template.SimpleHash; import freemarker.template.TemplateException; /** * By default the Blackberry BIS server does not forward a S/MIME message to a Blackberry because the BIS * server strips the smime.p7m attachment. You can stop the BIS server from stripping the attachment by * renaming the attachment. SMIMEAdapter modifies an S/MIME encrypted or opaque signed message to make * sure that the BIS server does not strip the smime.p7m attachment. The smime.p7m will be added as * an attachment to the provided template. * * Supported mailet parameters: * * 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) * catchRuntimeExceptions : if true all RunTimeExceptions are caught (default: true) * catchErrors : if true all Errors are caught (default: true) * * @author Martijn Brinkers * */ public class BlackberrySMIMEAdapter extends SenderTemplatePropertySendMail { private final static Logger logger = LoggerFactory.getLogger(BlackberrySMIMEAdapter.class); /* * Maximum depth of a mime structure we will scan */ private final int MAX_DEPTH = 8; /* * The extension to use for the attachment that must be downloaded */ private final String DOWNLOAD_ATTACHMENT_EXTENSION = ".smime"; /* * The message limit after which the s/mime message is sent as an attachment that must * be manually downloaded (handled on the BB using a content handler) */ private long directSizeLimit = 32 * FileUtils.ONE_KB; /* * The new name of the attachment if the message size is larger than directSizeLimit */ private String downloadAttachmentName = "attachment"; @Override protected Logger getLogger() { return logger; } /* * The mailet initialization parameters used by this mailet. */ private enum Parameter { DIRECT_SIZE_LIMIT("directSizeLimit"), DOWNLOAD_ATTACHMENT_NAME("downloadAttachmentName"); private String name; private Parameter(String name) { this.name = name; } }; @Override public void initMailet() throws MessagingException { super.initMailet(); String param = getInitParameter(Parameter.DIRECT_SIZE_LIMIT.name); if (param != null) { directSizeLimit = NumberUtils.toLong(param, directSizeLimit); } param = getInitParameter(Parameter.DOWNLOAD_ATTACHMENT_NAME.name); if (StringUtils.isNotBlank(param)) { downloadAttachmentName = param.trim(); } StrBuilder sb = new StrBuilder(); sb.append("directSizeLimit: "); sb.append(directSizeLimit); sb.appendSeparator("; "); sb.append("downloadAttachmentName: "); sb.append(downloadAttachmentName); getLogger().info(sb.toString()); } private Part findAttachment(MimeMessage containerMessage) throws MessagingException, IOException, PartException { final MutableObject smimePart = new MutableObject(); final MutableObject octetPart = new MutableObject(); PartListener partListener = new PartScanner.PartListener() { @Override public boolean onPart(Part parent, Part part, Object context) throws PartException { try { if (ArrayUtils.contains(part.getHeader(DjigzoHeader.MARKER), DjigzoHeader.ATTACHMENT_MARKER_VALUE)) { smimePart.setValue(part); /* * we found the part with the marker so we can stop scanning */ return false; } /* * Fallback scanning for octet-stream in case the template does not contain a DjigzoHeader.MARKER */ if (part.isMimeType("application/octet-stream")) { octetPart.setValue(part); } return true; } catch (MessagingException e) { throw new PartException(e); } } }; PartScanner partScanner = new PartScanner(partListener, MAX_DEPTH); partScanner.scanPart(containerMessage); Part result = (Part) smimePart.getValue(); if (result == null) { result = (Part) octetPart.getValue(); if (result != null) { logger.debug("Marker not found. Using octet-stream instead."); } else { throw new MessagingException("Unable to find the attachment part in the template."); } } return result; } private void replaceSMIMEAttachment(MimeMessage containerMessage, MimeMessage sourceMessage) throws MessagingException, IOException, PartException { Part smimePart = findAttachment(containerMessage); if (sourceMessage.getSize() > directSizeLimit) { /* * The message is too large to be sent to the BB directly so we should * sent it as an attachment that can be downloaded and handled using * a content handler on the BB */ String filename = downloadAttachmentName + DOWNLOAD_ATTACHMENT_EXTENSION; smimePart.setFileName(filename); } smimePart.setDataHandler(new DataHandler( new ByteArrayDataSource(sourceMessage.getInputStream(), "application/octet-stream"))); } private void createMessageFromTemplate(Mail mail) throws MessagingException, MissingRecipientsException, TemplateException, IOException, PartException { SimpleHash root = new SimpleHash(); /* * Create a message from a template */ MimeMessage containerMessage = createMessage(mail, root); /* * Copy all non content headers from source to destination */ HeaderMatcher nonContentMatcher = new NotHeaderNameMatcher(new ContentHeaderNameMatcher()); HeaderUtils.copyHeaders(mail.getMessage(), containerMessage, nonContentMatcher); /* * We now need to replace the s/mime attachment from the template with the real attachment */ replaceSMIMEAttachment(containerMessage, mail.getMessage()); containerMessage.saveChanges(); /* * A new MimeMessage instance will be created. This makes ure that the * MimeMessage can be written by James (using writeTo()). */ mail.setMessage(new MimeMessage(containerMessage)); } @Override public void internalServiceMail(Mail mail) { try { MimeMessage sourceMessage = mail.getMessage(); /* * We only need to check application/* mime types because all other, like text cannot * be S/MIME enveloped messages. We won't convert clear signed so we don't have to * check multipart messages. */ if (sourceMessage.isMimeType("application/*")) { SMIMEType messageType = SMIMEUtils.getSMIMEType(sourceMessage); if (messageType == SMIMEType.ENCRYPTED || messageType == SMIMEType.SIGNED) { logger.debug("Message is encrypted or opaque signed."); createMessageFromTemplate(mail); } } } catch (MissingRecipientsException e) { getLogger().warn("MissingRecipientsException. " + ExceptionUtils.getRootCauseMessage(e)); if (getLogger().isDebugEnabled()) { logger.debug("Missing recipients.", e); } } catch (MessagingException e) { getLogger().error("Unhandled exception.", e); } catch (PartException e) { getLogger().error("Error scanning message.", e); } catch (TemplateException e) { getLogger().error("Error processing template.", e); } catch (IOException e) { getLogger().error("Error processing template.", e); } } }