View Javadoc

1   /****************************************************************
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   ****************************************************************/
19  
20  package org.apache.jsieve.mailet;
21  
22  import java.io.InputStream;
23  import java.util.Collection;
24  import java.util.Enumeration;
25  import java.util.Iterator;
26  import java.util.Vector;
27  
28  import javax.mail.Header;
29  import javax.mail.MessagingException;
30  import javax.mail.internet.InternetHeaders;
31  import javax.mail.internet.MimeMessage;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.jsieve.ConfigurationManager;
35  import org.apache.jsieve.SieveConfigurationException;
36  import org.apache.jsieve.SieveFactory;
37  import org.apache.mailet.Mail;
38  import org.apache.mailet.MailAddress;
39  import org.apache.mailet.MailetConfig;
40  import org.apache.mailet.MailetException;
41  import org.apache.mailet.base.GenericMailet;
42  import org.apache.mailet.base.RFC2822Headers;
43  
44  /**
45   * <p>Executes a <a href='http://www.rfc-editor.org/rfc/rfc3028.txt'>Sieve</a>
46   * script against incoming mail. The script applied is based on the recipient.</p>
47   * <h4>Init Parameters</h4>
48   * <table>
49   * <thead><tr><th>Name</th><th>Required</th><th>Values</th><th>Role</th></thead>
50   * <tr><td>verbose</td><td>No - defaults to false</td><td>true (ignoring case) to enable, otherwise disable</td>
51   * <td>
52   * Enables verbose logging.
53   * </td></tr>
54   * </table>
55   */
56  public class SieveMailboxMailet extends GenericMailet {
57  
58      /**
59       * The delivery header
60       */
61      private String deliveryHeader;
62  
63      /**
64       * resetReturnPath
65       */
66      private boolean resetReturnPath;
67      /** Experimental */
68      private Poster poster;
69      /** Experimental */
70      private ResourceLocator locator;
71      
72      /** Indicates whether this mailet should log verbosely */
73      private boolean verbose = false;
74      
75      private boolean consume = true;
76      /** Indicates whether this mailet should log minimal information */
77      private boolean quiet = true;
78  
79      private SieveFactory factory;
80  
81      private ActionDispatcher actionDispatcher;
82  
83      private Log log;
84  
85      /**
86       * For SDI
87       */
88      public SieveMailboxMailet() {}
89      
90      /**
91       * CDI
92       * @param poster not null
93       */
94      public SieveMailboxMailet(Poster poster, ResourceLocator locator) {
95          this();
96          this.poster = poster;
97          this.locator = locator;
98      }
99  
100     
101     public ResourceLocator getLocator() {
102         return locator;
103     }
104 
105     /**
106      * For SDI
107      * @param locator not null
108      */
109     public void setLocator(ResourceLocator locator) {
110         this.locator = locator;
111     }
112 
113     public Poster getPoster() {
114         return poster;
115     }
116     
117     /**
118      * For SDI
119      * @param poster not null
120      */
121     public void setPoster(Poster poster) {
122         this.poster = poster;
123     }
124 
125     /**
126      * Is this mailet GHOSTing all mail it processes?
127      * @return true when mailet consumes all mail, false otherwise
128      */
129     public boolean isConsume() {
130         return consume;
131     }
132 
133     /**
134      * Sets whether this mailet should GHOST all mail.
135      * @param consume true when the mailet should consume all mail, 
136      * false otherwise
137      */
138     public void setConsume(boolean consume) {
139         this.consume = consume;
140     }
141 
142     /**
143      * Is this mailet logging verbosely?
144      * This property is set by init parameters.
145      * @return true if logging should be verbose, false otherwise
146      */
147     public boolean isVerbose() {
148         return verbose;
149     }
150 
151 
152     /**
153      * Sets whether logging should be verbose for this mailet.
154      * This property is set by init parameters.
155      * This setting overrides {@link #isQuiet()}.
156      * @param verbose true when logging should be verbose,
157      * false otherwise
158      */
159     public void setVerbose(boolean verbose) {
160         this.verbose = verbose;
161     }
162 
163     /**
164      * Is the logging for this mailet set to minimal?
165      * @return true
166      */
167     public boolean isQuiet() {
168         return quiet;
169     }
170 
171     /**
172      * Sets the logging for this mailet to minimal.
173      * This is overriden by {@link #setVerbose(boolean)}.
174      * @param quiet true for minimal logging, false otherwise
175      */
176     public void setQuiet(boolean quiet) {
177         this.quiet = quiet;
178     }
179     
180     /**
181      * Is informational logging turned on? 
182      * @return true when minimal logging is off,
183      * false when logging is minimal
184      */
185     public boolean isInfoLoggingOn() {
186         return verbose || !quiet;
187     }
188 
189     //@Override
190     public void init(MailetConfig config) throws MessagingException {
191         
192         super.init(config);
193 
194         try {
195             final ConfigurationManager configurationManager = new ConfigurationManager();
196             final int logLevel;
197             if (verbose) {
198                 logLevel = CommonsLoggingAdapter.TRACE;
199             } else if (quiet) {
200                 logLevel = CommonsLoggingAdapter.FATAL;
201             } else {
202                 logLevel = CommonsLoggingAdapter.WARN;
203             }
204             log = new CommonsLoggingAdapter(this, logLevel);
205             configurationManager.setLog(log);
206             factory = configurationManager.build();
207         } catch (SieveConfigurationException e) {
208             throw new MessagingException("Failed to load standard Sieve configuration.", e);
209         }
210     }
211 
212     /**
213      * Delivers a mail to a local mailbox.
214      * 
215      * @param mail
216      *            the mail being processed
217      * 
218      * @throws MessagingException
219      *             if an error occurs while storing the mail
220      */
221     public void service(Mail mail) throws MessagingException {
222         Collection recipients = mail.getRecipients();
223         Collection errors = new Vector();
224 
225         MimeMessage message = null;
226         if (deliveryHeader != null || resetReturnPath) {
227             message = mail.getMessage();
228         }
229 
230         if (resetReturnPath) {
231             // Set Return-Path and remove all other Return-Path headers from the
232             // message
233             // This only works because there is a placeholder inserted by
234             // MimeMessageWrapper
235             message.setHeader(RFC2822Headers.RETURN_PATH,
236                     (mail.getSender() == null ? "<>" : "<" + mail.getSender()
237                             + ">"));
238         }
239 
240         Enumeration headers;
241         InternetHeaders deliveredTo = new InternetHeaders();
242         if (deliveryHeader != null) {
243             // Copy any Delivered-To headers from the message
244             headers = message
245                     .getMatchingHeaders(new String[] { deliveryHeader });
246             while (headers.hasMoreElements()) {
247                 Header header = (Header) headers.nextElement();
248                 deliveredTo.addHeader(header.getName(), header.getValue());
249             }
250         }
251 
252         for (Iterator i = recipients.iterator(); i.hasNext();) {
253             MailAddress recipient = (MailAddress) i.next();
254             try {
255                 if (deliveryHeader != null) {
256                     // Add qmail's de facto standard Delivered-To header
257                     message.addHeader(deliveryHeader, recipient.toString());
258                 }
259 
260                 storeMail(mail.getSender(), recipient, mail);
261 
262                 if (deliveryHeader != null) {
263                     if (i.hasNext()) {
264                         // Remove headers but leave all placeholders
265                         message.removeHeader(deliveryHeader);
266                         headers = deliveredTo.getAllHeaders();
267                         // And restore any original Delivered-To headers
268                         while (headers.hasMoreElements()) {
269                             Header header = (Header) headers.nextElement();
270                             message.addHeader(header.getName(), header
271                                     .getValue());
272                         }
273                     }
274                 }
275             } catch (Exception ex) {
276                 log("Error while storing mail.", ex);
277                 errors.add(recipient);
278             }
279         }
280 
281         if (!errors.isEmpty()) {
282             // If there were errors, we redirect the email to the ERROR
283             // processor.
284             // In order for this server to meet the requirements of the SMTP
285             // specification, mails on the ERROR processor must be returned to
286             // the sender. Note that this email doesn't include any details
287             // regarding the details of the failure(s).
288             // In the future we may wish to address this.
289             getMailetContext().sendMail(mail.getSender(), errors,
290                     mail.getMessage(), Mail.ERROR);
291         }
292         if (consume) {
293             // Consume this message
294             mail.setState(Mail.GHOST);
295         }
296     }
297 
298     /**
299      * Return a string describing this mailet.
300      * 
301      * @return a string describing this mailet
302      */
303     public String getMailetInfo() {
304         return "Sieve Mailbox Mailet";
305     }
306 
307     /**
308      * 
309      * @param sender
310      * @param recipient
311      * @param mail
312      * @throws MessagingException
313      */
314     public void storeMail(MailAddress sender, MailAddress recipient,
315             Mail mail) throws MessagingException {
316         String username;
317         if (recipient == null) {
318             throw new IllegalArgumentException(
319                     "Recipient for mail to be spooled cannot be null.");
320         }
321         if (mail.getMessage() == null) {
322             throw new IllegalArgumentException(
323                     "Mail message to be spooled cannot be null.");
324         }
325         // recipient.toString was used here (JD)
326         username = recipient.getUser();
327         
328         sieveMessage(username, mail);
329  
330     }
331     
332     void sieveMessage(String username, Mail aMail) throws MessagingException {
333         // Evaluate the script against the mail
334         String relativeUri = "//" + username + "@" + "localhost/sieve"; 
335         try
336         {
337             final InputStream ins = locator.get(relativeUri);
338             
339             SieveMailAdapter aMailAdapter = new SieveMailAdapter(aMail,
340                     getMailetContext(), actionDispatcher, poster);
341             aMailAdapter.setLog(log);
342             // This logging operation is potentially costly
343             if (verbose) {
344                 log("Evaluating " + aMailAdapter.toString() + "against \""
345                     + relativeUri + "\"");
346             }
347             factory.evaluate(aMailAdapter, factory.parse(ins));
348         }
349         catch (Exception ex)
350         {
351             //
352             // SLIEVE is a mail filtering protocol.
353             // Rejecting the mail because it cannot be filtered
354             // seems very unfriendly.
355             // So just log and store in INBOX.
356             //
357             if (isInfoLoggingOn()) {
358                 log("Cannot evaluate Sieve script. Storing mail in user INBOX.", ex);
359             }
360             storeMessageInbox(username, aMail);
361         }
362     }
363     
364     void storeMessageInbox(String username, Mail mail) throws MessagingException {
365         String url = "mailbox://" + username + "@localhost/";
366         poster.post(url, mail.getMessage());
367     }
368 
369     /**
370      * @see org.apache.mailet.base.GenericMailet#init()
371      */
372     public void init() throws MessagingException {
373         super.init();
374         if (poster == null || locator == null) {
375             throw new MailetException("Not initialised. Please ensure that the mailet container supports either" +
376                     " setter or constructor injection");
377         }
378         
379         this.deliveryHeader = getInitParameter("addDeliveryHeader");
380         this.resetReturnPath = getInitParameter("resetReturnPath", true);
381         this.consume = getInitParameter("consume", true);
382         this.verbose = getInitParameter("verbose", false);
383         this.quiet = getInitParameter("quiet", false);
384         
385         actionDispatcher = new ActionDispatcher();
386     }
387 }