mitm.application.djigzo.james.mailets.BlackberrySMIMEAdapter.java Source code

Java tutorial

Introduction

Here is the source code for mitm.application.djigzo.james.mailets.BlackberrySMIMEAdapter.java

Source

/*
 * 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);
        }
    }
}