blob: 4ffa52b91859b645cf8a3cfed846fdbeddd4ac64 [file] [log] [blame]
/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
package org.apache.jsieve.mailet;
import java.io.InputStream;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Vector;
import javax.mail.Header;
import javax.mail.MessagingException;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeMessage;
import org.apache.commons.logging.Log;
import org.apache.jsieve.ConfigurationManager;
import org.apache.jsieve.SieveConfigurationException;
import org.apache.jsieve.SieveFactory;
import org.apache.mailet.Mail;
import org.apache.mailet.MailAddress;
import org.apache.mailet.MailetConfig;
import org.apache.mailet.MailetException;
import org.apache.mailet.base.GenericMailet;
import org.apache.mailet.base.RFC2822Headers;
/**
* <p>Executes a <a href='http://www.rfc-editor.org/rfc/rfc3028.txt'>Sieve</a>
* script against incoming mail. The script applied is based on the recipient.</p>
* <h4>Init Parameters</h4>
* <table>
* <thead><tr><th>Name</th><th>Required</th><th>Values</th><th>Role</th></thead>
* <tr><td>verbose</td><td>No - defaults to false</td><td>true (ignoring case) to enable, otherwise disable</td>
* <td>
* Enables verbose logging.
* </td></tr>
* </table>
*/
public class SieveMailboxMailet extends GenericMailet {
/**
* The delivery header
*/
private String deliveryHeader;
/**
* resetReturnPath
*/
private boolean resetReturnPath;
/** Experimental */
private Poster poster;
/** Experimental */
private ResourceLocator locator;
/** Indicates whether this mailet should log verbosely */
private boolean verbose = false;
private boolean consume = true;
/** Indicates whether this mailet should log minimal information */
private boolean quiet = true;
private SieveFactory factory;
private ActionDispatcher actionDispatcher;
private Log log;
/**
* For SDI
*/
public SieveMailboxMailet() {}
/**
* CDI
* @param poster not null
*/
public SieveMailboxMailet(Poster poster, ResourceLocator locator) {
this();
this.poster = poster;
this.locator = locator;
}
public ResourceLocator getLocator() {
return locator;
}
/**
* For SDI
* @param locator not null
*/
public void setLocator(ResourceLocator locator) {
this.locator = locator;
}
public Poster getPoster() {
return poster;
}
/**
* For SDI
* @param poster not null
*/
public void setPoster(Poster poster) {
this.poster = poster;
}
/**
* Is this mailet GHOSTing all mail it processes?
* @return true when mailet consumes all mail, false otherwise
*/
public boolean isConsume() {
return consume;
}
/**
* Sets whether this mailet should GHOST all mail.
* @param consume true when the mailet should consume all mail,
* false otherwise
*/
public void setConsume(boolean consume) {
this.consume = consume;
}
/**
* Is this mailet logging verbosely?
* This property is set by init parameters.
* @return true if logging should be verbose, false otherwise
*/
public boolean isVerbose() {
return verbose;
}
/**
* Sets whether logging should be verbose for this mailet.
* This property is set by init parameters.
* This setting overrides {@link #isQuiet()}.
* @param verbose true when logging should be verbose,
* false otherwise
*/
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
/**
* Is the logging for this mailet set to minimal?
* @return true
*/
public boolean isQuiet() {
return quiet;
}
/**
* Sets the logging for this mailet to minimal.
* This is overriden by {@link #setVerbose(boolean)}.
* @param quiet true for minimal logging, false otherwise
*/
public void setQuiet(boolean quiet) {
this.quiet = quiet;
}
/**
* Is informational logging turned on?
* @return true when minimal logging is off,
* false when logging is minimal
*/
public boolean isInfoLoggingOn() {
return verbose || !quiet;
}
@Override
public void init(MailetConfig config) throws MessagingException {
super.init(config);
try {
final ConfigurationManager configurationManager = new ConfigurationManager();
final int logLevel;
if (verbose) {
logLevel = CommonsLoggingAdapter.TRACE;
} else if (quiet) {
logLevel = CommonsLoggingAdapter.FATAL;
} else {
logLevel = CommonsLoggingAdapter.WARN;
}
log = new CommonsLoggingAdapter(this, logLevel);
configurationManager.setLog(log);
factory = configurationManager.build();
} catch (SieveConfigurationException e) {
throw new MessagingException("Failed to load standard Sieve configuration.", e);
}
}
/**
* Delivers a mail to a local mailbox.
*
* @param mail
* the mail being processed
*
* @throws MessagingException
* if an error occurs while storing the mail
*/
@SuppressWarnings("unchecked")
@Override
public void service(Mail mail) throws MessagingException {
Collection<MailAddress> recipients = mail.getRecipients();
Collection<MailAddress> errors = new Vector<MailAddress>();
MimeMessage message = null;
if (deliveryHeader != null || resetReturnPath) {
message = mail.getMessage();
}
if (resetReturnPath) {
// Set Return-Path and remove all other Return-Path headers from the
// message
// This only works because there is a placeholder inserted by
// MimeMessageWrapper
message.setHeader(RFC2822Headers.RETURN_PATH,
(mail.getSender() == null ? "<>" : "<" + mail.getSender()
+ ">"));
}
Enumeration headers;
InternetHeaders deliveredTo = new InternetHeaders();
if (deliveryHeader != null) {
// Copy any Delivered-To headers from the message
headers = message
.getMatchingHeaders(new String[] { deliveryHeader });
while (headers.hasMoreElements()) {
Header header = (Header) headers.nextElement();
deliveredTo.addHeader(header.getName(), header.getValue());
}
}
for (Iterator<MailAddress> i = recipients.iterator(); i.hasNext();) {
MailAddress recipient = i.next();
try {
if (deliveryHeader != null) {
// Add qmail's de facto standard Delivered-To header
message.addHeader(deliveryHeader, recipient.toString());
}
storeMail(mail.getSender(), recipient, mail);
if (deliveryHeader != null) {
if (i.hasNext()) {
// Remove headers but leave all placeholders
message.removeHeader(deliveryHeader);
headers = deliveredTo.getAllHeaders();
// And restore any original Delivered-To headers
while (headers.hasMoreElements()) {
Header header = (Header) headers.nextElement();
message.addHeader(header.getName(), header
.getValue());
}
}
}
} catch (Exception ex) {
log("Error while storing mail.", ex);
errors.add(recipient);
}
}
if (!errors.isEmpty()) {
// If there were errors, we redirect the email to the ERROR
// processor.
// In order for this server to meet the requirements of the SMTP
// specification, mails on the ERROR processor must be returned to
// the sender. Note that this email doesn't include any details
// regarding the details of the failure(s).
// In the future we may wish to address this.
getMailetContext().sendMail(mail.getSender(), errors,
mail.getMessage(), Mail.ERROR);
}
if (consume) {
// Consume this message
mail.setState(Mail.GHOST);
}
}
/**
* Return a string describing this mailet.
*
* @return a string describing this mailet
*/
@Override
public String getMailetInfo() {
return "Sieve Mailbox Mailet";
}
/**
*
* @param sender
* @param recipient
* @param mail
* @throws MessagingException
*/
@SuppressWarnings("deprecation")
public void storeMail(MailAddress sender, MailAddress recipient,
Mail mail) throws MessagingException {
if (recipient == null) {
throw new IllegalArgumentException(
"Recipient for mail to be spooled cannot be null.");
}
if (mail.getMessage() == null) {
throw new IllegalArgumentException(
"Mail message to be spooled cannot be null.");
}
sieveMessage(recipient, mail);
}
void sieveMessage(MailAddress recpient, Mail aMail) throws MessagingException {
String username = getUsername(recpient);
// Evaluate the script against the mail
String relativeUri = "//" + username +"/sieve";
try
{
final InputStream ins = locator.get(relativeUri);
SieveMailAdapter aMailAdapter = new SieveMailAdapter(aMail,
getMailetContext(), actionDispatcher, poster);
aMailAdapter.setLog(log);
// This logging operation is potentially costly
if (verbose) {
log("Evaluating " + aMailAdapter.toString() + "against \""
+ relativeUri + "\"");
}
factory.evaluate(aMailAdapter, factory.parse(ins));
}
catch (Exception ex)
{
//
// SLIEVE is a mail filtering protocol.
// Rejecting the mail because it cannot be filtered
// seems very unfriendly.
// So just log and store in INBOX.
//
if (isInfoLoggingOn()) {
log("Cannot evaluate Sieve script. Storing mail in user INBOX.", ex);
}
storeMessageInbox(username, aMail);
}
}
void storeMessageInbox(String username, Mail mail) throws MessagingException {
String url = "mailbox://" + username + "/";
poster.post(url, mail.getMessage());
}
/**
* @see org.apache.mailet.base.GenericMailet#init()
*/
@Override
public void init() throws MessagingException {
super.init();
if (poster == null || locator == null) {
throw new MailetException("Not initialised. Please ensure that the mailet container supports either" +
" setter or constructor injection");
}
this.deliveryHeader = getInitParameter("addDeliveryHeader");
this.resetReturnPath = getInitParameter("resetReturnPath", true);
this.consume = getInitParameter("consume", true);
this.verbose = getInitParameter("verbose", false);
this.quiet = getInitParameter("quiet", false);
actionDispatcher = new ActionDispatcher();
}
/**
* Return the username to use for sieve processing for the given MailAddress
*
* @param m
* @return username
*/
protected String getUsername(MailAddress m) {
return m.getLocalPart() + "@localhost";
}
}