Java tutorial
/* * Copyright (c) 2010-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.IOException; import java.security.KeyStoreException; import java.security.cert.CertStoreException; import java.util.Collections; import java.util.concurrent.Callable; import javax.mail.MessagingException; import javax.mail.internet.InternetAddress; import mitm.application.djigzo.User; import mitm.application.djigzo.james.MailAddressUtils; import mitm.application.djigzo.james.MessageOriginatorIdentifier; import mitm.application.djigzo.james.UserPersister; import mitm.application.djigzo.service.SystemServices; import mitm.application.djigzo.workflow.UserWorkflow; import mitm.common.hibernate.DatabaseActionExecutor; import mitm.common.hibernate.DatabaseActionExecutorBuilder; import mitm.common.hibernate.SessionManager; import mitm.common.mail.EmailAddressUtils; import mitm.common.properties.HierarchicalPropertiesException; import mitm.common.security.ca.CA; import mitm.common.security.ca.CAException; import mitm.common.security.ca.CASettings; import mitm.common.security.ca.CASettingsProvider; import mitm.common.security.ca.CancelCertificateRequestException; import mitm.common.security.ca.CertificateRequestStore; import mitm.common.security.ca.Match; import mitm.common.security.ca.RequestParameters; import mitm.common.security.ca.RequestUtils; import mitm.common.util.Check; import mitm.common.util.KeyedBarrier; import org.apache.commons.lang.ObjectUtils.Null; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.lang.text.StrBuilder; import org.apache.commons.lang.time.DateUtils; import org.apache.mailet.Mail; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Requests a certificate for the sender. * * @author Martijn Brinkers * * Supported mailet parameters: * * skipIfAvailable : If true a certificate won't be requested if the sender already has a signing certificate * or if the certificate request store already contains a pending request for the sender. * addUser : If true and a certificate is created, the user will be added * certificateRequestHandler : The certificate request handler to use. If not specified the certificate request handler * specified by the CASettings will be used * catchRuntimeExceptions : If true all RunTimeExceptions are caught * catchErrors : If true all Errors are caught */ public class RequestSenderCertificate extends AbstractDjigzoMailet { private final static Logger logger = LoggerFactory.getLogger(RequestSenderCertificate.class); /* * The activation context key */ private final static String ACTIVATION_CONTEXT_KEY = "activationContext"; /* * The number of times a database action should be retried when a ConstraintViolation occurs */ private final static int ACTION_RETRIES = 3; /* * manages database sessions */ private SessionManager sessionManager; /* * Used for adding and retrieving users */ private UserWorkflow userWorkflow; /* * Identifies the 'sender' of the message (default version uses from as the identifier) */ private MessageOriginatorIdentifier messageOriginatorIdentifier; /* * Used for requesting certificates */ private CA ca; /* * Store that stores pending certificate requests */ private CertificateRequestStore certificateRequestStore; /* * Used to get the CA settings */ private CASettingsProvider caSettingsProvider; /* * Used to execute database actions in a transaction */ private DatabaseActionExecutor actionExecutor; /* * If true a certificate won't be requested if the sender already has a signing certificate or if the certificate * request store already contains a pending request for the sender. */ private boolean skipIfAvailable = true; /* * If true and a certificate is created, the user will be added */ private boolean addUser = true; /* * If true, requests are synchronized using a KeyedBarrier */ private boolean synchronizeRequests = true; /* * Used for synchronizing certificate requests to make sure that no certificate is requested * multiple times when a message is sent by the same user in a short timeframe */ private final KeyedBarrier<InternetAddress, Null> keyedBarrier = new KeyedBarrier<InternetAddress, Null>(); /* * The maximum time (in msec) a request can be blocked by the KeyedBarrier */ private final long keyedBarrierTimeout = DateUtils.MILLIS_PER_SECOND * 60; /* * The certificate request handler to use. If not specified the certificate request handler * specified by the CASettings will be used */ private String certificateRequestHandler; /* * used to track user objects. */ private static class ActivationContext { private User user; public void setUser(User user) { this.user = user; } public User getUser() { return user; } } /* * The mailet initialization parameters used by this mailet. */ private enum Parameter { SKIP_IF_AVAILABLE("skipIfAvailable"), ADD_USER("addUser"), SYNCHRONIZE_REQUESTS( "synchronizeRequests"), CERTIFICATE_REQUEST_HANDLER("certificateRequestHandler"); private String name; private Parameter(String name) { this.name = name; } }; @Override protected Logger getLogger() { return logger; } @Override public void initMailet() throws MessagingException { super.initMailet(); skipIfAvailable = getBooleanInitParameter(Parameter.SKIP_IF_AVAILABLE.name, skipIfAvailable); addUser = getBooleanInitParameter(Parameter.ADD_USER.name, addUser); synchronizeRequests = getBooleanInitParameter(Parameter.SYNCHRONIZE_REQUESTS.name, synchronizeRequests); certificateRequestHandler = getInitParameter(Parameter.CERTIFICATE_REQUEST_HANDLER.name); sessionManager = SystemServices.getSessionManager(); userWorkflow = SystemServices.getUserWorkflow(); messageOriginatorIdentifier = SystemServices.getMessageOriginatorIdentifier(); ca = SystemServices.getCA(); certificateRequestStore = SystemServices.getCertificateRequestStore(); caSettingsProvider = SystemServices.getCASettingsProvider(); actionExecutor = DatabaseActionExecutorBuilder.createDatabaseActionExecutor(sessionManager); assert (actionExecutor != null); StrBuilder sb = new StrBuilder(); sb.append("skipIfAvailable: "); sb.append(skipIfAvailable); sb.append("; "); sb.append("addUser: "); sb.append(addUser); sb.append("; "); sb.append("synchronizeRequests: "); sb.append(synchronizeRequests); sb.append("; "); sb.append("certificateRequestHandler: "); sb.append(certificateRequestHandler); getLogger().info(sb.toString()); } private void makeNonPersistentUserPersistent(User user) { if (user != null && addUser) { UserPersister userPersister = new UserPersister(userWorkflow, sessionManager, actionExecutor); userPersister.tryToMakeNonPersistentUsersPersistent(Collections.singleton(user)); } } private void requestCertificate(User user) throws MessagingException { try { CASettings caSettings = caSettingsProvider.getSettings(); RequestParameters request = RequestUtils.createRequestParameters(user.getEmail(), caSettings); /* * Override the certificate request handler from the CASettings if this mailet overrides it */ if (StringUtils.isNotEmpty(certificateRequestHandler)) { request.setCertificateRequestHandler(certificateRequestHandler); } ca.requestCertificate(request); } catch (HierarchicalPropertiesException e) { throw new MessagingException("Error requesting certificate.", e); } catch (IOException e) { throw new MessagingException("Error requesting certificate.", e); } catch (CAException e) { throw new MessagingException("Error requesting certificate.", e); } } /* * Gets called by MailAddressHandler#handleRecipients to handle the user */ protected void onHandleUserEvent(User user) throws MessagingException { try { ActivationContext activationContext = getActivationContext().get(ACTIVATION_CONTEXT_KEY, ActivationContext.class); Check.notNull(activationContext, "activationContext"); /* * Reset in case there was a retry */ activationContext.setUser(null); boolean requestCertificate = true; if (skipIfAvailable) { if (user.getSigningKeyAndCertificate() != null) { logger.debug("The user " + user.getEmail() + " already has a valid signing certificate."); requestCertificate = false; } if (requestCertificate && certificateRequestStore.getSizeByEmail(user.getEmail(), Match.EXACT) > 0) { logger.debug("there is already a pending request for user " + user.getEmail()); requestCertificate = false; } } if (requestCertificate) { requestCertificate(user); activationContext.setUser(user); } } catch (KeyStoreException e) { throw new MessagingException("Error getting signing key for user: " + user.getEmail(), e); } catch (CertStoreException e) { throw new MessagingException("Error getting signing key for user: " + user.getEmail(), e); } catch (HierarchicalPropertiesException e) { throw new MessagingException("Error getting signing key for user: " + user.getEmail(), e); } } @Override public void serviceMail(Mail mail) { try { final ActivationContext activationContext = new ActivationContext(); /* * Place the activationContext in the context so we can use it in onHandleUserEvent */ getActivationContext().set(ACTIVATION_CONTEXT_KEY, activationContext); MailAddressHandler.HandleUserEventHandler eventHandler = new MailAddressHandler.HandleUserEventHandler() { @Override public void handleUser(User user) throws MessagingException { onHandleUserEvent(user); } }; final MailAddressHandler mailAddressHandler = new MailAddressHandler(sessionManager, userWorkflow, actionExecutor, eventHandler, ACTION_RETRIES); final InternetAddress originator = messageOriginatorIdentifier.getOriginator(mail); /* * If the from is an invalid email address invalid@invalid.tld will be returned. We don't want to request * a certificate for that address. */ if (originator != null && !EmailAddressUtils.isInvalidDummyAddress(originator)) { Callable<Null> callable = new Callable<Null>() { @Override public Null call() throws Exception { mailAddressHandler.handleMailAddresses( MailAddressUtils.fromAddressArrayToMailAddressList(originator)); /* * activationContext.getUser() will return null if there was no certificate * requested for the user */ makeNonPersistentUserPersistent(activationContext.getUser()); return null; } }; try { if (synchronizeRequests) { /* * Synchronize certificate requests to make sure only one certificate is requested for * a user even if multiple messages are sent by the same user at the same time */ keyedBarrier.execute(originator, callable, keyedBarrierTimeout); } else { callable.call(); } } catch (Exception e) { throw new MessagingException("Error requesting a certificate.", e); } } else { logger.debug( "Message originator is an invalid email address. Certificate request will be skipped."); } } catch (Exception e) { Throwable rootCause = ExceptionUtils.getRootCause(e); /* * If the MessagingException is caused by a CancelCertificateRequestException, do not log stacktrace */ if (rootCause instanceof CancelCertificateRequestException) { getLogger().warn("Certificate request was canceled. Message: " + rootCause.getMessage()); } else { getLogger().error("Error requesting certificates.", e); } } } }