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

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 2010-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 java.io.StringReader;
import java.io.StringWriter;
import java.util.Collection;
import java.util.List;

import javax.mail.MessagingException;

import mitm.application.djigzo.james.DjigzoMailAttributes;
import mitm.application.djigzo.james.DjigzoMailAttributesImpl;
import mitm.application.djigzo.service.SystemServices;
import mitm.common.dlp.MimeMessageTextExtractor;
import mitm.common.dlp.PolicyChecker;
import mitm.common.dlp.PolicyCheckerContext;
import mitm.common.dlp.PolicyPattern;
import mitm.common.dlp.PolicyViolation;
import mitm.common.dlp.PolicyViolationException;
import mitm.common.dlp.TextNormalizer;
import mitm.common.dlp.impl.PolicyCheckerContextImpl;
import mitm.common.extractor.ExtractedPart;
import mitm.common.extractor.TextExtractor;
import mitm.common.extractor.impl.TextExtractorUtils;
import mitm.common.util.SizeUtils;
import mitm.common.util.StringIterator;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.lang.text.StrBuilder;
import org.apache.mailet.Mail;

/**
 * Abstract base class for RegExpPolicyChecker mailets.
 * 
 * @author Martijn Brinkers
 *
 */
public abstract class AbstractRegExpPolicyChecker extends AbstractDjigzoMailet {
    /*
     * As a sanity measure, the length of the error message stored in Mail attributes
     * will be limited to this length.
     */
    private final static int MAX_POLICY_VIOLATION_ERROR_MESSAGE_LENGTH = 1024;

    /*
     * The mailet initialization parameters used by this mailet.
     */
    private enum Parameter {
        WARN_PROCESSOR("warnProcessor"), MUST_ENCRYPT_PROCESSOR("mustEncryptProcessor"), QUARANTINE_PROCESSOR(
                "quarantineProcessor"), BLOCK_PROCESSOR("blockProcessor"), ERROR_PROCESSOR(
                        "errorProcessor"), MAX_STRING_LENGTH(
                                "maxStringLength"), MAX_INIT_BUFFER_SIZE("maxInitBufferSize");

        private String name;

        private Parameter(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return name;
        }
    };

    /*
     * is used to normalize the extracted text (lowercase, remove words on skip list etc.)
     */
    private TextNormalizer textNormalizer;

    /*
     * The policy checker pipeline
     */
    private PolicyChecker policyChecker;

    /*
     * Used to extract the text from the email message.
     */
    private MimeMessageTextExtractor textExtractor;

    /*
     * The next processor when the policy was violated with WARN action. 
     */
    private String warnProcessor;

    /*
     * The next processor when the policy was violated with MUST_ENCRYPT action. 
     */
    private String mustEncryptProcessor;

    /*
     * The next processor when the policy was violated with QUARANTINE action. 
     */
    private String quarantineProcessor;

    /*
     * The next processor when the policy was violated with BLOCK action. 
     */
    private String blockProcessor;

    /*
     * The next processor when an error occurred when checking the policy 
     */
    private String errorProcessor;

    /*
     * The max length of the a string returned by the string iterator
     */
    private int maxStringLength = SizeUtils.KB * 512;

    /*
     * The max initial size of the buffer used by the string iterator
     */
    private int maxInitBufferSize = SizeUtils.KB * 64;

    private String getMandatoryInitParameter(String name) {
        String value = getInitParameter(name);

        if (StringUtils.isBlank(value)) {
            throw new IllegalArgumentException("Init parameter " + name + " is missing.");
        }

        return value;
    }

    @Override
    public void initMailet() {
        getLogger().info("Initializing mailet: " + getMailetName());

        warnProcessor = getMandatoryInitParameter(Parameter.WARN_PROCESSOR.name);
        mustEncryptProcessor = getMandatoryInitParameter(Parameter.MUST_ENCRYPT_PROCESSOR.name);
        quarantineProcessor = getMandatoryInitParameter(Parameter.QUARANTINE_PROCESSOR.name);
        blockProcessor = getMandatoryInitParameter(Parameter.BLOCK_PROCESSOR.name);
        errorProcessor = getMandatoryInitParameter(Parameter.ERROR_PROCESSOR.name);

        maxStringLength = getIntegerInitParameter(Parameter.MAX_STRING_LENGTH.name, maxStringLength);
        maxInitBufferSize = getIntegerInitParameter(Parameter.MAX_INIT_BUFFER_SIZE.name, maxInitBufferSize);

        textNormalizer = SystemServices.getTextNormalizer();
        policyChecker = SystemServices.getPolicyCheckerPipeline();
        textExtractor = SystemServices.getMimeMessageTextExtractor();

        StrBuilder sb = new StrBuilder();

        sb.append("warnProcessor: ").append(warnProcessor).append("; mustEncryptProcessor: ")
                .append(mustEncryptProcessor).append("; quarantineProcessor: ").append(quarantineProcessor)
                .append("; blockProcessor: ").append(blockProcessor).append("; errorProcessor: ")
                .append(errorProcessor).append("; maxStringLength: ").append(maxStringLength)
                .append("; maxInitBufferSize: ").append(maxInitBufferSize);

        getLogger().info(sb.toString());
    }

    /**
     * Should return the PolicyPattern's which are used to check the message content with
     */
    protected abstract Collection<PolicyPattern> getPolicyPatterns(Mail mail)
            throws MessagingException, IOException;

    private void logException(Exception e) {
        if (getLogger().isDebugEnabled()) {
            getLogger().warn("Error extracting text from email.", e);
        } else {
            getLogger().warn("Error extracting text from email. Message: " + ExceptionUtils.getRootCauseMessage(e));
        }
    }

    private void handleError(Mail mail, Exception e) {
        logException(e);

        /*
         * Place the error message in the Mail attributes so the exact details
         * of the error can be retrieved by other Mailets
         */
        DjigzoMailAttributes mailAttributes = new DjigzoMailAttributesImpl(mail);

        /*
         * Get the root cause and add make sure the error message has some sane length
         */
        String errorMessage = StringUtils.abbreviate(ExceptionUtils.getRootCauseMessage(e),
                MAX_POLICY_VIOLATION_ERROR_MESSAGE_LENGTH);

        mailAttributes.setPolicyViolationErrorMessage(errorMessage);

        mail.setState(errorProcessor);
    }

    private void handlePolicyViolation(Mail mail, PolicyViolationException e) {
        PolicyViolation mainViolation = null;

        Collection<PolicyViolation> violations = e.getViolations();

        if (violations != null) {
            /*
             * We will place the violations in the Mail attributes so the exact details
             * of the violation can be retrieved by other Mailets
             */
            DjigzoMailAttributes mailAttributes = new DjigzoMailAttributesImpl(mail);

            mailAttributes.setPolicyViolations(violations);

            /*
             * Multiple policies might be violated. We need to check which policy is the
             * most important policy.
             */
            for (PolicyViolation violation : violations) {
                if (violation == null) {
                    continue;
                }

                if (mainViolation == null
                        || violation.getPriority().isHigherPriorityThen(mainViolation.getPriority())) {
                    mainViolation = violation;
                }
            }
        }

        if (mainViolation == null) {
            throw new IllegalStateException("No main violation found.");
        }

        String nextProcessor;

        switch (mainViolation.getPriority()) {
        case WARN:
            nextProcessor = warnProcessor;
            break;
        case MUST_ENCRYPT:
            nextProcessor = mustEncryptProcessor;
            break;
        case QUARANTINE:
            nextProcessor = quarantineProcessor;
            break;
        case BLOCK:
            nextProcessor = blockProcessor;
            break;
        default:
            throw new IllegalArgumentException("Unknown PolicyViolationAction: " + mainViolation.getPriority());
        }

        if (getLogger().isDebugEnabled()) {
            getLogger().warn("Policy was violated. Rule: " + mainViolation);
        } else {
            getLogger().warn("Policy was violated. Rule: " + mainViolation.getRule() + ", Priority: "
                    + mainViolation.getPriority());
        }

        mail.setState(nextProcessor);
    }

    @Override
    public void serviceMail(Mail mail) {
        try {
            List<ExtractedPart> extractedParts = null;

            try {
                PolicyCheckerContext context = new PolicyCheckerContextImpl();

                context.setPatterns(getPolicyPatterns(mail));

                extractedParts = textExtractor.extractText(mail.getMessage());

                policyChecker.init(context);

                if (extractedParts != null) {
                    for (ExtractedPart part : extractedParts) {
                        StringIterator contentIterator = new StringIterator(part.getContent(), maxStringLength,
                                TextExtractor.ENCODING, maxInitBufferSize);

                        /*
                         * We need to track whether the content of a part is too big to fit
                         * in one call. If so, partial will be true and the data top be 
                         * analyzed will contain some overlap between different partial updates
                         * to make it possible to analyze the boundaries. 
                         */
                        boolean partial = false;

                        String extractedText;

                        while ((extractedText = contentIterator.getNext()) != null) {
                            StringWriter normalized = new StringWriter(extractedText.length());

                            /*
                             * The extracted text should be normalized before checking the policy
                             */
                            textNormalizer.normalize(new StringReader(extractedText), normalized);

                            context.setContent(normalized.toString());
                            context.setPartial(partial);

                            policyChecker.update(context);

                            partial = true;
                        }
                    }

                    try {
                        policyChecker.finish(context);
                    } catch (PolicyViolationException e) {
                        handlePolicyViolation(mail, e);
                    }
                }
            } finally {
                TextExtractorUtils.closeQuitely(extractedParts);
            }
        } catch (Exception e) {
            handleError(mail, e);
        }
    }
}