View Javadoc

1   /************************************************************************
2    * Copyright (c) 2000-2006 The Apache Software Foundation.             *
3    * All rights reserved.                                                *
4    * ------------------------------------------------------------------- *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you *
6    * may not use this file except in compliance with the License. You    *
7    * may obtain a copy of the License at:                                *
8    *                                                                     *
9    *     http://www.apache.org/licenses/LICENSE-2.0                      *
10   *                                                                     *
11   * Unless required by applicable law or agreed to in writing, software *
12   * distributed under the License is distributed on an "AS IS" BASIS,   *
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or     *
14   * implied.  See the License for the specific language governing       *
15   * permissions and limitations under the License.                      *
16   ***********************************************************************/
17  
18  package org.apache.james.transport.mailets;
19  
20  import java.io.PrintWriter;
21  import java.io.StringWriter;
22  
23  import java.util.Collection;
24  import java.util.Date;
25  import java.util.Enumeration;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.Locale;
29  import java.util.ArrayList;
30  
31  
32  import javax.mail.Message;
33  import javax.mail.MessagingException;
34  import javax.mail.internet.ParseException;
35  import javax.mail.Session;
36  import javax.mail.internet.InternetAddress;
37  import javax.mail.internet.MimeBodyPart;
38  import javax.mail.internet.MimeMessage;
39  import javax.mail.internet.MimeMultipart;
40  
41  import org.apache.mailet.RFC2822Headers;
42  import org.apache.mailet.dates.RFC822DateFormat;
43  import org.apache.james.core.MailImpl;
44  import org.apache.james.core.MimeMessageUtil;
45  
46  import org.apache.mailet.GenericMailet;
47  import org.apache.mailet.Mail;
48  import org.apache.mailet.MailAddress;
49  
50  
51  /***
52   * <P>Abstract mailet providing configurable redirection services.<BR>
53   * This mailet can be subclassed to make authoring redirection mailets simple.<BR>
54   * By extending it and overriding one or more of these methods new behaviour can
55   * be quickly created without the author having to address any other issue than
56   * the relevant one:</P>
57   * <UL>
58   * <LI>attachError() , should error messages be appended to the message</LI>
59   * <LI>getAttachmentType(), what should be attached to the message</LI>
60   * <LI>getInLineType(), what should be included in the message</LI>
61   * <LI>getMessage(), The text of the message itself</LI>
62   * <LI>getRecipients(), the recipients the mail is sent to</LI>
63   * <LI>getReplyTo(), where replies to this message will be sent</LI>
64   * <LI>getReversePath(), what to set the reverse-path to</LI>
65   * <LI>getSender(), who the mail is from</LI>
66   * <LI>getSubject(), a string to replace the message subject</LI>
67   * <LI>getSubjectPrefix(), a prefix to be added to the message subject, possibly already replaced by a new subject</LI>
68   * <LI>getTo(), a list of people to whom the mail is *apparently* sent</LI>
69   * <LI>isReply(), should this mailet set the IN_REPLY_TO header to the id of the current message</LI>
70   * <LI>getPassThrough(), should this mailet allow the original message to continue processing or GHOST it.</LI>
71   * <LI>getFakeDomainCheck(), should this mailet check if the sender domain address is valid.</LI>
72   * <LI>isStatic(), should this mailet run the get methods for every mail, or just once.</LI>
73   * </UL>
74   * <P>For each of the methods above (generically called "getX()" methods in this class
75   * and its subclasses), there is an associated "getX(Mail)" method and most times
76   * a "setX(Mail, Tx, Mail)" method.<BR>
77   * The roles are the following:</P>
78   * <UL>
79   * <LI>a "getX()" method returns the correspondent "X" value that can be evaluated "statically"
80   * once at init time and then stored in a variable and made available for later use by a
81   * "getX(Mail)" method;</LI>
82   * <LI>a "getX(Mail)" method is the one called to return the correspondent "X" value
83   * that can be evaluated "dynamically", tipically based on the currently serviced mail;
84   * the default behaviour is to return the value of getX();</LI>
85   * <LI>a "setX(Mail, Tx, Mail)" method is called to change the correspondent "X" value
86   * of the redirected Mail object, using the value returned by "gexX(Mail)";
87   * if such value is null, it does nothing.</LI>
88   * </UL>
89   * <P>Here follows the typical pattern of those methods:</P>
90   * <PRE><CODE>
91   *    ...
92   *    Tx x;
93   *    ...
94   *    protected boolean getX(Mail originalMail) throws MessagingException {
95   *        boolean x = (isStatic()) ? this.x : getX();
96   *        ...
97   *        return x;
98   *    }
99   *    ...
100  *    public void init() throws MessagingException {
101  *        ...
102  *        isStatic = (getInitParameter("static") == null) ? false : new Boolean(getInitParameter("static")).booleanValue();
103  *        if(isStatic()) {
104  *            ...
105  *            X  = getX();
106  *            ...
107  *        }
108  *    ...
109  *    public void service(Mail originalMail) throws MessagingException {
110  *    ...
111  *    setX(newMail, getX(originalMail), originalMail);
112  *    ...
113  *    }
114  *    ...
115  * </CODE></PRE>
116  * <P>The <I>isStatic</I> variable and method is used to allow for the situations
117  * (deprecated since version 2.2, but possibly used by previoulsy written extensions
118  * to {@link Redirect}) in which the getX() methods are non static: in this case
119  * {@link #isStatic()} must return false.<BR>
120  * Finally, a "getX()" method may return a "special address" (see {@link SpecialAddress}),
121  * that later will be resolved ("late bound") by a "getX(Mail)" or "setX(Mail, Tx, Mail)":
122  * it is a dynamic value that does not require <CODE>isStatic</CODE> to be false.</P>
123  *
124  * <P>Supports by default the <CODE>passThrough</CODE> init parameter (false if missing).
125  * Subclasses can override this behaviour overriding {@link #getPassThrough()}.</P>
126  *
127  * @version CVS $Revision: 428992 $ $Date: 2006-08-05 12:26:23 +0000 (sab, 05 ago 2006) $
128  * @since 2.2.0
129  */
130 
131 public abstract class AbstractRedirect extends GenericMailet {
132     
133     /***
134      * Gets the expected init parameters.
135      *
136      * @return null meaning no check
137      */
138     protected  String[] getAllowedInitParameters() {
139         return null;
140     }
141     
142     /***
143      * Controls certain log messages.
144      */
145     protected boolean isDebug = false;
146 
147     /***
148      * Holds the value of the <CODE>static</CODE> init parameter.
149      */
150     protected boolean isStatic = false;
151 
152     private static class AddressMarker {
153         public static MailAddress SENDER;
154         public static MailAddress REVERSE_PATH;
155         public static MailAddress FROM;
156         public static MailAddress REPLY_TO;
157         public static MailAddress TO;
158         public static MailAddress RECIPIENTS;
159         public static MailAddress DELETE;
160         public static MailAddress UNALTERED;
161         public static MailAddress NULL;
162 
163         static {
164             try {
165                 SENDER          = new MailAddress("sender","address.marker");
166                 REVERSE_PATH    = new MailAddress("reverse.path","address.marker");
167                 FROM            = new MailAddress("from","address.marker");
168                 REPLY_TO        = new MailAddress("reply.to","address.marker");
169                 TO              = new MailAddress("to","address.marker");
170                 RECIPIENTS      = new MailAddress("recipients","address.marker");
171                 DELETE          = new MailAddress("delete","address.marker");
172                 UNALTERED       = new MailAddress("unaltered","address.marker");
173                 NULL            = new MailAddress("null","address.marker");
174 
175             } catch (Exception _) {}
176         }
177     }
178 
179     /***
180      * Class containing "special addresses" constants.
181      * Such addresses mean dynamic values that later will be resolved ("late bound")
182      * by a "getX(Mail)" or "setX(Mail, Tx, Mail)".
183      */
184     protected static class SpecialAddress {
185         public static final MailAddress SENDER          = AddressMarker.SENDER;
186         public static final MailAddress REVERSE_PATH    = AddressMarker.REVERSE_PATH;
187         public static final MailAddress FROM            = AddressMarker.FROM;
188         public static final MailAddress REPLY_TO        = AddressMarker.REPLY_TO;
189         public static final MailAddress TO              = AddressMarker.TO;
190         public static final MailAddress RECIPIENTS      = AddressMarker.RECIPIENTS;
191         public static final MailAddress DELETE          = AddressMarker.DELETE;
192         public static final MailAddress UNALTERED       = AddressMarker.UNALTERED;
193         public static final MailAddress NULL            = AddressMarker.NULL;
194     }
195 
196     // The values that indicate how to attach the original mail
197     // to the new mail.
198 
199     protected static final int UNALTERED        = 0;
200 
201     protected static final int HEADS            = 1;
202 
203     protected static final int BODY             = 2;
204 
205     protected static final int ALL              = 3;
206 
207     protected static final int NONE             = 4;
208 
209     protected static final int MESSAGE          = 5;
210 
211     private boolean passThrough = false;
212     private boolean fakeDomainCheck = true;
213     private int attachmentType = NONE;
214     private int inLineType = BODY;
215     private String messageText;
216     private Collection recipients;
217     private MailAddress replyTo;
218     private MailAddress reversePath;
219     private MailAddress sender;
220     private String subject;
221     private String subjectPrefix;
222     private InternetAddress[] apparentlyTo;
223     private boolean attachError = false;
224     private boolean isReply = false;
225 
226     private RFC822DateFormat rfc822DateFormat = new RFC822DateFormat();
227 
228     /* ******************************************************************** */
229     /* ****************** Begin of getX and setX methods ****************** */
230     /* ******************************************************************** */
231 
232     /***
233      * <P>Gets the <CODE>static</CODE> property.</P>
234      * <P>Return true to reduce calls to getTo, getSender, getRecipients, getReplyTo, getReversePath amd getMessage
235      * where these values don't change (eg hard coded, or got at startup from the mailet config);
236      * return false where any of these methods generate their results dynamically eg in response to the message being processed,
237      * or by reference to a repository of users.</P>
238      * <P>It is now (from version 2.2) somehow obsolete, as should be always true because the "good practice"
239      * is to use "getX()" methods statically, and use instead "getX(Mail)" methods for dynamic situations.
240      * A false value is now meaningful only for subclasses of {@link Redirect} older than version 2.2
241      * that were relying on this.</P>
242      *
243      * <P>Is a "getX()" method.</P>
244      *
245      * @return true, as normally "getX()" methods shouls be static
246      */
247     protected boolean isStatic() {
248         return true;
249     }
250 
251     /***
252      * Gets the <CODE>passThrough</CODE> property.
253      * Return true to allow the original message to continue through the processor, false to GHOST it.
254      * Is a "getX()" method.
255      *
256      * @return the <CODE>passThrough</CODE> init parameter, or false if missing
257      */
258     protected boolean getPassThrough() throws MessagingException {
259         return new Boolean(getInitParameter("passThrough")).booleanValue();
260     }
261 
262     /***
263      * Gets the <CODE>passThrough</CODE> property,
264      * built dynamically using the original Mail object.
265      * Is a "getX(Mail)" method.
266      *
267      * @return {@link #getPassThrough()}
268      */
269     protected boolean getPassThrough(Mail originalMail) throws MessagingException {
270         return (isStatic()) ? this.passThrough : getPassThrough();
271     }
272 
273     /***
274      * Gets the <CODE>fakeDomainCheck</CODE> property.
275      * Return true to check if the sender domain is valid.
276      * Is a "getX()" method.
277      *
278      * @return the <CODE>fakeDomainCheck</CODE> init parameter, or true if missing
279      */
280     protected boolean getFakeDomainCheck() throws MessagingException {
281         return new Boolean(getInitParameter("fakeDomainCheck")).booleanValue();
282     }
283 
284     /***
285      * Gets the <CODE>fakeDomainCheck</CODE> property,
286      * built dynamically using the original Mail object.
287      * Is a "getX(Mail)" method.
288      *
289      * @return {@link #getFakeDomainCheck()}
290      */
291     protected boolean getFakeDomainCheck(Mail originalMail) throws MessagingException {
292         return (isStatic()) ? this.fakeDomainCheck : getFakeDomainCheck();
293     }
294 
295     /***
296      * Gets the <CODE>inline</CODE> property.
297      * May return one of the following values to indicate how to append the original message
298      * to build the new message:
299      * <ul>
300      *    <li><CODE>UNALTERED</CODE> : original message is the new message body</li>
301      *    <li><CODE>BODY</CODE> : original message body is appended to the new message</li>
302      *    <li><CODE>HEADS</CODE> : original message headers are appended to the new message</li>
303      *    <li><CODE>ALL</CODE> : original is appended with all headers</li>
304      *    <li><CODE>NONE</CODE> : original is not appended</li>
305      * </ul>
306      * Is a "getX()" method.
307      *
308      * @return the <CODE>inline</CODE> init parameter, or <CODE>UNALTERED</CODE> if missing
309      */
310     protected int getInLineType() throws MessagingException {
311         return getTypeCode(getInitParameter("inline","unaltered"));
312     }
313     
314     /***
315      * Gets the <CODE>inline</CODE> property,
316      * built dynamically using the original Mail object.
317      * Is a "getX(Mail)" method.
318      *
319      * @return {@link #getInLineType()}
320      */
321     protected int getInLineType(Mail originalMail) throws MessagingException {
322         return (isStatic()) ? this.inLineType : getInLineType();
323     }
324 
325     /*** Gets the <CODE>attachment</CODE> property.
326      * May return one of the following values to indicate how to attach the original message
327      * to the new message:
328      * <ul>
329      *    <li><CODE>BODY</CODE> : original message body is attached as plain text to the new message</li>
330      *    <li><CODE>HEADS</CODE> : original message headers are attached as plain text to the new message</li>
331      *    <li><CODE>ALL</CODE> : original is attached as plain text with all headers</li>
332      *    <li><CODE>MESSAGE</CODE> : original message is attached as type message/rfc822, a complete mail message.</li>
333      *    <li><CODE>NONE</CODE> : original is not attached</li>
334      * </ul>
335      * Is a "getX()" method.
336      *
337      * @return the <CODE>attachment</CODE> init parameter, or <CODE>NONE</CODE> if missing
338      */
339     protected int getAttachmentType() throws MessagingException {
340         return getTypeCode(getInitParameter("attachment","none"));
341     }
342 
343     /***
344      * Gets the <CODE>attachment</CODE> property,
345      * built dynamically using the original Mail object.
346      * Is a "getX(Mail)" method.
347      *
348      * @return {@link #getAttachmentType()}
349      */
350     protected int getAttachmentType(Mail originalMail) throws MessagingException {
351         return (isStatic()) ? this.attachmentType : getAttachmentType();
352     }
353 
354     /***
355      * Gets the <CODE>message</CODE> property.
356      * Returns a message to which the original message can be attached/appended
357      * to build the new message.
358      * Is a "getX()" method.
359      *
360      * @return the <CODE>message</CODE> init parameter or an empty string if missing
361      */
362     protected String getMessage() throws MessagingException {
363         return getInitParameter("message","");
364     }
365 
366     /***
367      * Gets the <CODE>message</CODE> property,
368      * built dynamically using the original Mail object.
369      * Is a "getX(Mail)" method.
370      *
371      * @return {@link #getMessage()}
372      */
373     protected String getMessage(Mail originalMail) throws MessagingException {
374         return (isStatic()) ? this.messageText : getMessage();
375     }
376 
377     /***
378      * Gets the <CODE>recipients</CODE> property.
379      * Returns the collection of recipients of the new message,
380      * or null if no change is requested.
381      * Is a "getX()" method.
382      *
383      * @return the <CODE>recipients</CODE> init parameter
384      * or the postmaster address
385      * or <CODE>SpecialAddress.SENDER</CODE>
386      * or <CODE>SpecialAddress.FROM</CODE>
387      * or <CODE>SpecialAddress.REPLY_TO</CODE>
388      * or <CODE>SpecialAddress.REVERSE_PATH</CODE>
389      * or <CODE>SpecialAddress.UNALTERED</CODE>
390      * or <CODE>SpecialAddress.RECIPIENTS</CODE>
391      * or <CODE>null</CODE> if missing
392      */
393     protected Collection getRecipients() throws MessagingException {
394         Collection newRecipients = new HashSet();
395         String addressList = getInitParameter("recipients");
396         
397         // if nothing was specified, return <CODE>null</CODE> meaning no change
398         if (addressList == null) {
399             return null;
400         }
401 
402         try {
403             InternetAddress[] iaarray = InternetAddress.parse(addressList, false);
404             for (int i = 0; i < iaarray.length; i++) {
405                 String addressString = iaarray[i].getAddress();
406                 MailAddress specialAddress = getSpecialAddress(addressString,
407                 new String[] {"postmaster", "sender", "from", "replyTo", "reversePath", "unaltered", "recipients", "to", "null"});
408                 if (specialAddress != null) {
409                     newRecipients.add(specialAddress);
410                 } else {
411                     newRecipients.add(new MailAddress(iaarray[i]));
412                 }
413             }
414         } catch (Exception e) {
415             throw new MessagingException("Exception thrown in getRecipients() parsing: " + addressList, e);
416         }
417         if (newRecipients.size() == 0) {
418             throw new MessagingException("Failed to initialize \"recipients\" list; empty <recipients> init parameter found.");
419         }
420 
421         return newRecipients;
422     }
423 
424     /***
425      * Gets the <CODE>recipients</CODE> property,
426      * built dynamically using the original Mail object.
427      * Is a "getX(Mail)" method.
428      *
429      * @return {@link #replaceMailAddresses} on {@link #getRecipients()},
430      */
431     protected Collection getRecipients(Mail originalMail) throws MessagingException {
432         Collection recipients = (isStatic()) ? this.recipients : getRecipients();
433         if (recipients != null) {
434             if (recipients.size() == 1 && (recipients.contains(SpecialAddress.UNALTERED) || recipients.contains(SpecialAddress.RECIPIENTS))) {
435                 recipients = null;
436             } else {
437                 recipients = replaceMailAddresses(originalMail, recipients);
438             }
439         }
440         return recipients;
441     }
442 
443     /***
444      * Sets the recipients of <I>newMail</I> to <I>recipients</I>.
445      * If the requested value is null does nothing.
446      * Is a "setX(Mail, Tx, Mail)" method.
447      */
448     protected void setRecipients(Mail newMail, Collection recipients, Mail originalMail) throws MessagingException {
449         if (recipients != null) {
450             newMail.setRecipients(recipients);
451             if (isDebug) {
452                 log("recipients set to: " + arrayToString(recipients.toArray()));
453             }
454         }
455     }
456 
457     /***
458      * Gets the <CODE>to</CODE> property.
459      * Returns the "To:" recipients of the new message.
460      * or null if no change is requested.
461      * Is a "getX()" method.
462      *
463      * @return the <CODE>to</CODE> init parameter
464      * or the postmaster address
465      * or <CODE>SpecialAddress.SENDER</CODE>
466      * or <CODE>SpecialAddress.REVERSE_PATH</CODE>
467      * or <CODE>SpecialAddress.FROM</CODE>
468      * or <CODE>SpecialAddress.REPLY_TO</CODE>
469      * or <CODE>SpecialAddress.UNALTERED</CODE>
470      * or <CODE>SpecialAddress.TO</CODE>
471      * or <CODE>null</CODE> if missing
472      */
473     protected InternetAddress[] getTo() throws MessagingException {
474         InternetAddress[] iaarray = null;
475         String addressList = getInitParameter("to");
476         
477         // if nothing was specified, return null meaning no change
478         if (addressList == null) {
479             return null;
480         }
481 
482         try {
483             iaarray = InternetAddress.parse(addressList, false);
484             for(int i = 0; i < iaarray.length; ++i) {
485                 String addressString = iaarray[i].getAddress();
486                 MailAddress specialAddress = getSpecialAddress(addressString,
487                                                 new String[] {"postmaster", "sender", "from", "replyTo", "reversePath", "unaltered", "recipients", "to", "null"});
488                 if (specialAddress != null) {
489                     iaarray[i] = specialAddress.toInternetAddress();
490                 }
491             }
492         } catch (Exception e) {
493             throw new MessagingException("Exception thrown in getTo() parsing: " + addressList, e);
494         }
495         if (iaarray.length == 0) {
496             throw new MessagingException("Failed to initialize \"to\" list; empty <to> init parameter found.");
497         }
498 
499         return iaarray;
500     }
501 
502     /***
503      * Gets the <CODE>to</CODE> property,
504      * built dynamically using the original Mail object.
505      * Its outcome will be the the value the <I>TO:</I> header will be set to,
506      * that could be different from the real recipient (see {@link #getRecipients}).
507      * Is a "getX(Mail)" method.
508      *
509      * @return {@link #replaceInternetAddresses} on {@link #getRecipients()},
510      */
511     protected InternetAddress[] getTo(Mail originalMail) throws MessagingException {
512         InternetAddress[] apparentlyTo = (isStatic()) ? this.apparentlyTo : getTo();
513         if (apparentlyTo != null) {
514             if (   apparentlyTo.length == 1
515                 && (   apparentlyTo[0].equals(SpecialAddress.UNALTERED.toInternetAddress())
516                     || apparentlyTo[0].equals(SpecialAddress.TO.toInternetAddress())
517                     )) {
518                 apparentlyTo = null;
519             } else {
520                 Collection toList = new ArrayList(apparentlyTo.length);
521                 for (int i = 0; i < apparentlyTo.length; i++) {
522                     toList.add(apparentlyTo[i]);
523                 }
524                 /* IMPORTANT: setTo() treats null differently from a zero length array,
525                   so it's ok to get a zero length array from replaceSpecialAddresses
526                  */
527                 apparentlyTo = (InternetAddress[]) replaceInternetAddresses(originalMail, toList).toArray(new InternetAddress[0]);
528             }
529         }
530         
531         return apparentlyTo;
532     }
533 
534     /***
535      * Sets the "To:" header of <I>newMail</I> to <I>to</I>.
536      * If the requested value is null does nothing.
537      * Is a "setX(Mail, Tx, Mail)" method.
538      */
539     protected void setTo(Mail newMail, InternetAddress[] to, Mail originalMail) throws MessagingException {
540         if (to != null) {
541             newMail.getMessage().setRecipients(Message.RecipientType.TO, to);
542             if (isDebug) {
543                 log("apparentlyTo set to: " + arrayToString(to));
544             }
545         }
546     }
547 
548     /***
549      * Gets the <CODE>replyto</CODE> property.
550      * Returns the Reply-To address of the new message,
551      * or null if no change is requested.
552      * Is a "getX()" method.
553      *
554      * @return the <CODE>replyto</CODE> init parameter
555      * or the postmaster address
556      * or <CODE>SpecialAddress.SENDER</CODE>
557      * or <CODE>SpecialAddress.UNALTERED</CODE>
558      * or <CODE>SpecialAddress.NULL</CODE>
559      * or <CODE>null</CODE> if missing
560      */
561     protected MailAddress getReplyTo() throws MessagingException {
562         String addressString = getInitParameter("replyTo",getInitParameter("replyto"));
563 
564         if(addressString != null) {
565             MailAddress specialAddress = getSpecialAddress(addressString,
566                                             new String[] {"postmaster", "sender", "null", "unaltered"});
567             if (specialAddress != null) {
568                 return specialAddress;
569             }
570 
571             try {
572                 return new MailAddress(addressString);
573             } catch(Exception e) {
574                 throw new MessagingException("Exception thrown in getReplyTo() parsing: " + addressString, e);
575             }
576         }
577 
578         return null;
579     }
580 
581     /***
582      * Gets the <CODE>replyTo</CODE> property,
583      * built dynamically using the original Mail object.
584      * Is a "getX(Mail)" method.
585      *
586      * @return {@link #getReplyTo()}
587      * replacing <CODE>SpecialAddress.UNALTERED</CODE> if applicable with null
588      * and <CODE>SpecialAddress.SENDER</CODE> with the original mail sender
589      */
590     protected MailAddress getReplyTo(Mail originalMail) throws MessagingException {
591         MailAddress replyTo = (isStatic()) ? this.replyTo : getReplyTo();
592         if (replyTo != null) {
593             if (replyTo == SpecialAddress.UNALTERED) {
594                 replyTo = null;
595             } else if (replyTo == SpecialAddress.SENDER) {
596                 replyTo = originalMail.getSender();
597             }
598         }
599         return replyTo;
600     }
601 
602     /***
603      * <P>Sets the "Reply-To:" header of <I>newMail</I> to <I>replyTo</I>.</P>
604      * If the requested value is <CODE>SpecialAddress.NULL</CODE> will remove the "Reply-To:" header.
605      * If the requested value is null does nothing.</P>
606      * Is a "setX(Mail, Tx, Mail)" method.
607      */
608     protected void setReplyTo(Mail newMail, MailAddress replyTo, Mail originalMail) throws MessagingException {
609         if(replyTo != null) {
610             InternetAddress[] iart = null;
611             if (replyTo != SpecialAddress.NULL) {
612                 iart = new InternetAddress[1];
613                 iart[0] = replyTo.toInternetAddress();
614             }
615             
616             // Note: if iart is null will remove the header
617             newMail.getMessage().setReplyTo(iart);
618             
619             if (isDebug) {
620                 log("replyTo set to: " + replyTo);
621             }
622         }
623     }
624 
625     /***
626      * Gets the <CODE>reversePath</CODE> property.
627      * Returns the reverse-path of the new message,
628      * or null if no change is requested.
629      * Is a "getX()" method.
630      *
631      * @return the <CODE>reversePath</CODE> init parameter 
632      * or the postmaster address
633      * or <CODE>SpecialAddress.SENDER</CODE>
634      * or <CODE>SpecialAddress.NULL</CODE>
635      * or <CODE>SpecialAddress.UNALTERED</CODE>
636      * or <CODE>null</CODE> if missing
637      */
638     protected MailAddress getReversePath() throws MessagingException {
639         String addressString = getInitParameter("reversePath");
640         if(addressString != null) {
641             MailAddress specialAddress = getSpecialAddress(addressString,
642                                             new String[] {"postmaster", "sender", "null", "unaltered"});
643             if (specialAddress != null) {
644                 return specialAddress;
645             }
646 
647             try {
648                 return new MailAddress(addressString);
649             } catch(Exception e) {
650                 throw new MessagingException("Exception thrown in getReversePath() parsing: " + addressString, e);
651             }
652         }
653 
654         return null;
655     }
656 
657     /***
658      * Gets the <CODE>reversePath</CODE> property,
659      * built dynamically using the original Mail object.
660      * Is a "getX(Mail)" method.
661      *
662      * @return {@link #getReversePath()},
663      * replacing <CODE>SpecialAddress.SENDER</CODE> if applicable with null,
664      * replacing <CODE>SpecialAddress.UNALTERED</CODE>
665      * and <CODE>SpecialAddress.REVERSE_PATH</CODE> if applicable with null,
666      * but not replacing <CODE>SpecialAddress.NULL</CODE>
667      * that will be handled by {@link #setReversePath}
668      */
669     protected MailAddress getReversePath(Mail originalMail) throws MessagingException {
670         MailAddress reversePath = (isStatic()) ? this.reversePath : getReversePath();
671         if (reversePath != null) {
672             if (reversePath == SpecialAddress.UNALTERED || reversePath == SpecialAddress.REVERSE_PATH) {
673                 reversePath = null;
674             }
675             else if (reversePath == SpecialAddress.SENDER) {
676                 reversePath = null;
677             }
678         }
679         return reversePath;
680     }
681 
682     /***
683      * Sets the "reverse-path" of <I>newMail</I> to <I>reversePath</I>.
684      * If the requested value is <CODE>SpecialAddress.NULL</CODE> sets it to "<>".
685      * If the requested value is null does nothing.
686      * Is a "setX(Mail, Tx, Mail)" method.
687      */
688     protected void setReversePath(MailImpl newMail, MailAddress reversePath, Mail originalMail) throws MessagingException {
689         if(reversePath != null) {
690             if (reversePath == SpecialAddress.NULL) {
691                 reversePath = null;
692             }
693             newMail.setSender(reversePath);
694             if (isDebug) {
695                 log("reversePath set to: " + reversePath);
696             }
697         }
698     }
699 
700     /***
701      * Gets the <CODE>sender</CODE> property.
702      * Returns the new sender as a MailAddress,
703      * or null if no change is requested.
704      * Is a "getX()" method.
705      *
706      * @return the <CODE>sender</CODE> init parameter
707      * or the postmaster address
708      * or <CODE>SpecialAddress.SENDER</CODE>
709      * or <CODE>SpecialAddress.UNALTERED</CODE>
710      * or <CODE>null</CODE> if missing
711      */
712     protected MailAddress getSender() throws MessagingException {
713         String addressString = getInitParameter("sender");
714         if(addressString != null) {
715             MailAddress specialAddress = getSpecialAddress(addressString,
716                                             new String[] {"postmaster", "sender", "unaltered"});
717             if (specialAddress != null) {
718                 return specialAddress;
719             }
720 
721             try {
722                 return new MailAddress(addressString);
723             } catch(Exception e) {
724                 throw new MessagingException("Exception thrown in getSender() parsing: " + addressString, e);
725             }
726         }
727 
728         return null;
729     }
730 
731     /***
732      * Gets the <CODE>sender</CODE> property,
733      * built dynamically using the original Mail object.
734      * Is a "getX(Mail)" method.
735      *
736      * @return {@link #getSender()}
737      * replacing <CODE>SpecialAddress.UNALTERED</CODE>
738      * and <CODE>SpecialAddress.SENDER</CODE> if applicable with null
739      */
740     protected MailAddress getSender(Mail originalMail) throws MessagingException {
741         MailAddress sender = (isStatic()) ? this.sender : getSender();
742         if (sender != null) {
743             if (sender == SpecialAddress.UNALTERED || sender == SpecialAddress.SENDER) {
744                 sender = null;
745             }
746         }
747         return sender;
748     }
749 
750     /***
751      * Sets the "From:" header of <I>newMail</I> to <I>sender</I>.
752      * If the requested value is null does nothing.
753      * Is a "setX(Mail, Tx, Mail)" method.
754      */
755     protected void setSender(Mail newMail, MailAddress sender, Mail originalMail) throws MessagingException {
756         if (sender != null) {
757             newMail.getMessage().setFrom(sender.toInternetAddress());
758             
759             if (isDebug) {
760                 log("sender set to: " + sender);
761             }
762         }
763     }
764     
765     /***
766      * Gets the <CODE>subject</CODE> property.
767      * Returns a string for the new message subject.
768      * Is a "getX()" method.
769      *
770      * @return the <CODE>subject</CODE> init parameter or null if missing
771      */
772     protected String getSubject() throws MessagingException {
773         return getInitParameter("subject");
774     }
775 
776     /***
777      * Gets the <CODE>subject</CODE> property,
778      * built dynamically using the original Mail object.
779      * Is a "getX(Mail)" method.
780      *
781      * @return {@link #getSubject()}
782      */
783     protected String getSubject(Mail originalMail) throws MessagingException {
784         return (isStatic()) ? this.subject : getSubject();
785     }
786 
787     /***
788      * Gets the <CODE>prefix</CODE> property.
789      * Returns a prefix for the new message subject.
790      * Is a "getX()" method.
791      *
792      * @return the <CODE>prefix</CODE> init parameter or an empty string if missing
793      */
794     protected String getSubjectPrefix() throws MessagingException {
795         return getInitParameter("prefix");
796     }
797 
798     /***
799      * Gets the <CODE>subjectPrefix</CODE> property,
800      * built dynamically using the original Mail object.
801      * Is a "getX(Mail)" method.
802      *
803      * @return {@link #getSubjectPrefix()}
804      */
805     protected String getSubjectPrefix(Mail originalMail) throws MessagingException {
806         return (isStatic()) ? this.subjectPrefix : getSubjectPrefix();
807     }
808 
809     /***
810      * Builds the subject of <I>newMail</I> appending the subject
811      * of <I>originalMail</I> to <I>subjectPrefix</I>.
812      * Is a "setX(Mail, Tx, Mail)" method.
813      */
814     protected void setSubjectPrefix(Mail newMail, String subjectPrefix, Mail originalMail) throws MessagingException {
815         String subject = getSubject(originalMail);
816         if ((subjectPrefix != null && subjectPrefix.length() > 0) || subject != null) {
817             if (subject == null) {
818                 subject = originalMail.getMessage().getSubject();
819             } else {
820                 // replacing the subject
821                 if (isDebug) {
822                     log("subject set to: " + subject);
823                 }
824             }
825             // Was null in original?
826             if (subject == null) {
827                 subject = "";
828             }
829             
830             if (subjectPrefix != null) {
831                 subject = subjectPrefix + subject;
832                 // adding a prefix
833                 if (isDebug) {
834                     log("subjectPrefix set to: " + subjectPrefix);
835                 }
836             }
837 //            newMail.getMessage().setSubject(subject);
838             changeSubject(newMail.getMessage(), subject);
839         }
840     }
841 
842     /***
843      * Gets the <CODE>attachError</CODE> property.
844      * Returns a boolean indicating whether to append a description of any error to the main body part
845      * of the new message, if getInlineType does not return "UNALTERED".
846      * Is a "getX()" method.
847      *
848      * @return the <CODE>attachError</CODE> init parameter; false if missing
849      */
850     protected boolean attachError() throws MessagingException {
851         return new Boolean(getInitParameter("attachError")).booleanValue();
852     }
853 
854     /***
855      * Gets the <CODE>attachError</CODE> property,
856      * built dynamically using the original Mail object.
857      * Is a "getX(Mail)" method.
858      *
859      * @return {@link #attachError()}
860      */
861     protected boolean attachError(Mail originalMail) throws MessagingException {
862         return (isStatic()) ? this.attachError : attachError();
863     }
864 
865     /***
866      * Gets the <CODE>isReply</CODE> property.
867      * Returns a boolean indicating whether the new message must be considered
868      * a reply to the original message, setting the IN_REPLY_TO header of the new
869      * message to the id of the original message.
870      * Is a "getX()" method.
871      *
872      * @return the <CODE>isReply</CODE> init parameter; false if missing
873      */
874     protected boolean isReply() throws MessagingException {
875         return new Boolean(getInitParameter("isReply")).booleanValue();
876     }
877 
878     /***
879      * Gets the <CODE>isReply</CODE> property,
880      * built dynamically using the original Mail object.
881      * Is a "getX(Mail)" method.
882      *
883      * @return {@link #isReply()}
884      */
885     protected boolean isReply(Mail originalMail) throws MessagingException {
886         return (isStatic()) ? this.isReply : isReply();
887     }
888 
889     /***
890      * Sets the "In-Reply-To:" header of <I>newMail</I> to the "Message-Id:" of
891      * <I>originalMail</I>, if <I>isReply</I> is true.
892      */
893     protected void setIsReply(Mail newMail, boolean isReply, Mail originalMail) throws MessagingException {
894         if (isReply) {
895             String messageId = originalMail.getMessage().getMessageID();
896             if (messageId != null) {
897                 newMail.getMessage().setHeader(RFC2822Headers.IN_REPLY_TO, messageId);
898                 if (isDebug) {
899                     log("IN_REPLY_TO set to: " + messageId);
900                 }
901             }
902         }
903     }
904 
905     /* ******************************************************************** */
906     /* ******************* End of getX and setX methods ******************* */
907     /* ******************************************************************** */
908 
909     /***
910      * Mailet initialization routine.
911      * Will setup static values for each "x" initialization parameter in config.xml,
912      * using getX(), if {@link #isStatic()} returns true.
913      */
914     public void init() throws MessagingException {
915         isDebug = new Boolean(getInitParameter("debug","false")).booleanValue();
916 
917         isStatic = new Boolean(getInitParameter("static","false")).booleanValue();
918 
919         if (isDebug) {
920             log("Initializing");
921         }
922         
923         // check that all init parameters have been declared in allowedInitParameters
924         checkInitParameters(getAllowedInitParameters());
925         
926         if(isStatic()) {
927             passThrough         = getPassThrough();
928             fakeDomainCheck     = getFakeDomainCheck();
929             attachmentType      = getAttachmentType();
930             inLineType          = getInLineType();
931             messageText         = getMessage();
932             recipients          = getRecipients();
933             replyTo             = getReplyTo();
934             reversePath         = getReversePath();
935             sender              = getSender();
936             subject             = getSubject();
937             subjectPrefix       = getSubjectPrefix();
938             apparentlyTo        = getTo();
939             attachError         = attachError();
940             isReply             = isReply();
941             if (isDebug) {
942                 StringBuffer logBuffer =
943                     new StringBuffer(1024)
944                             .append("static")
945                             .append(", passThrough=").append(passThrough)
946                             .append(", fakeDomainCheck=").append(fakeDomainCheck)
947                             .append(", sender=").append(sender)
948                             .append(", replyTo=").append(replyTo)
949                             .append(", reversePath=").append(reversePath)
950                             .append(", message=").append(messageText)
951                             .append(", recipients=").append(arrayToString(recipients == null ? null : recipients.toArray()))
952                             .append(", subject=").append(subject)
953                             .append(", subjectPrefix=").append(subjectPrefix)
954                             .append(", apparentlyTo=").append(arrayToString(apparentlyTo))
955                             .append(", attachError=").append(attachError)
956                             .append(", isReply=").append(isReply)
957                             .append(", attachmentType=").append(attachmentType)
958                             .append(", inLineType=").append(inLineType)
959                             .append(" ");
960                 log(logBuffer.toString());
961             }
962         }
963     }
964 
965     /***
966      * Service does the hard work,and redirects the originalMail in the form specified.
967      *
968      * @param originalMail the mail to process and redirect
969      * @throws MessagingException if a problem arises formulating the redirected mail
970      */
971     public void service(Mail originalMail) throws MessagingException {
972 
973         boolean keepMessageId = false;
974 
975         // duplicates the Mail object, to be able to modify the new mail keeping the original untouched
976         MailImpl newMail = new MailImpl(originalMail,newName(originalMail));
977         try {
978             // We don't need to use the original Remote Address and Host,
979             // and doing so would likely cause a loop with spam detecting
980             // matchers.
981             try {
982                 newMail.setRemoteAddr(java.net.InetAddress.getLocalHost().getHostAddress());
983                 newMail.setRemoteHost(java.net.InetAddress.getLocalHost().getHostName());
984             } catch (java.net.UnknownHostException _) {
985                 newMail.setRemoteAddr("127.0.0.1");
986                 newMail.setRemoteHost("localhost");
987             }
988     
989             if (isDebug) {
990                 log("New mail - sender: " + newMail.getSender()
991                            + ", recipients: " + arrayToString(newMail.getRecipients().toArray())
992                            + ", name: " + newMail.getName()
993                            + ", remoteHost: " + newMail.getRemoteHost()
994                            + ", remoteAddr: " + newMail.getRemoteAddr()
995                            + ", state: " + newMail.getState()
996                            + ", lastUpdated: " + newMail.getLastUpdated()
997                            + ", errorMessage: " + newMail.getErrorMessage());
998             }
999     
1000             //Create the message
1001             if(getInLineType(originalMail) != UNALTERED) {
1002                 if (isDebug) {
1003                     log("Alter message");
1004                 }
1005                 newMail.setMessage(new MimeMessage(Session.getDefaultInstance(System.getProperties(),
1006                                                                    null)));
1007     
1008                 // handle the new message if altered
1009                 buildAlteredMessage(newMail, originalMail);
1010     
1011             } else {
1012                 // if we need the original, create a copy of this message to redirect
1013                 if (getPassThrough(originalMail)) {
1014                     newMail.setMessage(new MimeMessage(originalMail.getMessage()) {
1015                         protected void updateHeaders() throws MessagingException {
1016                             if (getMessageID() == null) super.updateHeaders();
1017                             else {
1018                                 modified = false;
1019                             }
1020                         }
1021                     });
1022                 }
1023                 if (isDebug) {
1024                     log("Message resent unaltered.");
1025                 }
1026                 keepMessageId = true;
1027             }
1028     
1029             //Set additional headers
1030     
1031             setRecipients(newMail, getRecipients(originalMail), originalMail);
1032     
1033             setTo(newMail, getTo(originalMail), originalMail);
1034     
1035             setSubjectPrefix(newMail, getSubjectPrefix(originalMail), originalMail);
1036     
1037             if(newMail.getMessage().getHeader(RFC2822Headers.DATE) == null) {
1038                 newMail.getMessage().setHeader(RFC2822Headers.DATE, rfc822DateFormat.format(new Date()));
1039             }
1040     
1041             setReplyTo(newMail, getReplyTo(originalMail), originalMail);
1042     
1043             setReversePath(newMail, getReversePath(originalMail), originalMail);
1044     
1045             setSender(newMail, getSender(originalMail), originalMail);
1046     
1047             setIsReply(newMail, isReply(originalMail), originalMail);
1048     
1049             newMail.getMessage().saveChanges();
1050     
1051             if (keepMessageId) {
1052                 setMessageId(newMail, originalMail);
1053             }
1054     
1055             if (senderDomainIsValid(newMail)) {
1056                 //Send it off...
1057                 getMailetContext().sendMail(newMail);
1058             } else {
1059                 StringBuffer logBuffer = new StringBuffer(256)
1060                                         .append(getMailetName())
1061                                         .append(" mailet cannot forward ")
1062                                         .append(originalMail.getName())
1063                                         .append(". Invalid sender domain for ")
1064                                         .append(newMail.getSender())
1065                                         .append(". Consider using the Resend mailet ")
1066                                         .append("using a different sender.");
1067                 throw new MessagingException(logBuffer.toString());
1068             }
1069     
1070         } finally {
1071             newMail.dispose();
1072         }
1073         
1074         if(!getPassThrough(originalMail)) {
1075             originalMail.setState(Mail.GHOST);
1076         }
1077     }
1078 
1079     private static final java.util.Random random = new java.util.Random();  // Used to generate new mail names
1080 
1081     /***
1082      * Create a unique new primary key name.
1083      *
1084      * @param mail the mail to use as the basis for the new mail name
1085      * @return a new name
1086      */
1087     private String newName(Mail mail) throws MessagingException {
1088         String oldName = mail.getName();
1089         
1090         // Checking if the original mail name is too long, perhaps because of a
1091         // loop caused by a configuration error.
1092         // it could cause a "null pointer exception" in AvalonMailRepository much
1093         // harder to understand.
1094         if (oldName.length() > 76) {
1095             int count = 0;
1096             int index = 0;
1097             while ((index = oldName.indexOf('!', index + 1)) >= 0) {
1098                 count++;
1099             }
1100             // It looks like a configuration loop. It's better to stop.
1101             if (count > 7) {
1102                 throw new MessagingException("Unable to create a new message name: too long."
1103                                              + " Possible loop in config.xml.");
1104             }
1105             else {
1106                 oldName = oldName.substring(0, 76);
1107             }
1108         }
1109         
1110         StringBuffer nameBuffer =
1111                                  new StringBuffer(64)
1112                                  .append(oldName)
1113                                  .append("-!")
1114                                  .append(random.nextInt(1048576));
1115         return nameBuffer.toString();
1116     }
1117 
1118     /***
1119      * A private method to convert types from string to int.
1120      *
1121      * @param param the string type
1122      * @return the corresponding int enumeration
1123      */
1124     protected int getTypeCode(String param) {
1125         param = param.toLowerCase(Locale.US);
1126         if(param.compareTo("unaltered") == 0) {
1127             return UNALTERED;
1128         }
1129         if(param.compareTo("heads") == 0) {
1130             return HEADS;
1131         }
1132         if(param.compareTo("body") == 0) {
1133             return BODY;
1134         }
1135         if(param.compareTo("all") == 0) {
1136             return ALL;
1137         }
1138         if(param.compareTo("none") == 0) {
1139             return NONE;
1140         }
1141         if(param.compareTo("message") == 0) {
1142             return MESSAGE;
1143         }
1144         return NONE;
1145     }
1146 
1147     /***
1148      * Utility method for obtaining a string representation of an array of Objects.
1149      */
1150     private String arrayToString(Object[] array) {
1151         if (array == null) {
1152             return "null";
1153         }
1154         StringBuffer sb = new StringBuffer(1024);
1155         sb.append("[");
1156         for (int i = 0; i < array.length; i++) {
1157             if (i > 0) {
1158                 sb.append(",");
1159             }
1160             sb.append(array[i]);
1161         }
1162         sb.append("]");
1163         return sb.toString();
1164     }
1165 
1166     /***
1167      * Utility method for obtaining a string representation of a
1168      * Message's headers
1169      */
1170     protected String getMessageHeaders(MimeMessage message) throws MessagingException {
1171         Enumeration heads = message.getAllHeaderLines();
1172         StringBuffer headBuffer = new StringBuffer(1024);
1173         while(heads.hasMoreElements()) {
1174             headBuffer.append(heads.nextElement().toString()).append("\r\n");
1175         }
1176         return headBuffer.toString();
1177     }
1178 
1179     /***
1180      * Utility method for obtaining a string representation of a
1181      * Message's body
1182      */
1183     private String getMessageBody(MimeMessage message) throws Exception {
1184         java.io.ByteArrayOutputStream bodyOs = new java.io.ByteArrayOutputStream();
1185         MimeMessageUtil.writeMessageBodyTo(message,bodyOs);
1186         return bodyOs.toString();
1187     }
1188 
1189     /***
1190      * Builds the message of the newMail in case it has to be altered.
1191      *
1192      * @param originalMail the original Mail object
1193      * @param newMail the Mail object to build
1194      */
1195     protected void buildAlteredMessage(Mail newMail, Mail originalMail) throws MessagingException {
1196 
1197         MimeMessage originalMessage = originalMail.getMessage();
1198         MimeMessage newMessage = newMail.getMessage();
1199 
1200         // Copy the relevant headers
1201         String[] relevantHeaderNames =
1202             {RFC2822Headers.DATE,
1203              RFC2822Headers.FROM,
1204              RFC2822Headers.REPLY_TO,
1205              RFC2822Headers.TO,
1206              RFC2822Headers.SUBJECT,
1207              RFC2822Headers.RETURN_PATH};
1208         Enumeration headerEnum = originalMessage.getMatchingHeaderLines(relevantHeaderNames);
1209         while (headerEnum.hasMoreElements()) {
1210             newMessage.addHeaderLine((String) headerEnum.nextElement());
1211         }
1212 
1213         StringWriter sout = new StringWriter();
1214         PrintWriter out   = new PrintWriter(sout, true);
1215         String head = getMessageHeaders(originalMessage);
1216         boolean all = false;
1217 
1218         String messageText = getMessage(originalMail);
1219         if(messageText != null) {
1220             out.println(messageText);
1221         }
1222 
1223         if (isDebug) {
1224             log("inline:" + getInLineType(originalMail));
1225         }
1226         switch(getInLineType(originalMail)) {
1227             case ALL: //ALL:
1228                 all = true;
1229             case HEADS: //HEADS:
1230                 out.println("Message Headers:");
1231                 out.println(head);
1232                 if(!all) {
1233                     break;
1234                 }
1235             case BODY: //BODY:
1236                 out.println("Message:");
1237                 try {
1238                     out.println(getMessageBody(originalMessage));
1239                 } catch(Exception e) {
1240                     out.println("body unavailable");
1241                 }
1242                 break;
1243             default:
1244             case NONE: //NONE:
1245                 break;
1246         }
1247 
1248         try {
1249             //Create the message body
1250             MimeMultipart multipart = new MimeMultipart("mixed");
1251 
1252             // Create the message
1253             MimeMultipart mpContent = new MimeMultipart("alternative");
1254             MimeBodyPart contentPartRoot = new MimeBodyPart();
1255             contentPartRoot.setContent(mpContent);
1256 
1257             multipart.addBodyPart(contentPartRoot);
1258 
1259             MimeBodyPart part = new MimeBodyPart();
1260             part.setText(sout.toString());
1261             part.setDisposition("inline");
1262             mpContent.addBodyPart(part);
1263             if (isDebug) {
1264                 log("attachmentType:" + getAttachmentType(originalMail));
1265             }
1266             if(getAttachmentType(originalMail) != NONE) {
1267                 part = new MimeBodyPart();
1268                 switch(getAttachmentType(originalMail)) {
1269                     case HEADS: //HEADS:
1270                         part.setText(head);
1271                         break;
1272                     case BODY: //BODY:
1273                         try {
1274                             part.setText(getMessageBody(originalMessage));
1275                         } catch(Exception e) {
1276                             part.setText("body unavailable");
1277                         }
1278                         break;
1279                     case ALL: //ALL:
1280                         StringBuffer textBuffer =
1281                             new StringBuffer(1024)
1282                                 .append(head)
1283                                 .append("\r\nMessage:\r\n")
1284                                 .append(getMessageBody(originalMessage));
1285                         part.setText(textBuffer.toString());
1286                         break;
1287                     case MESSAGE: //MESSAGE:
1288                         part.setContent(originalMessage, "message/rfc822");
1289                         break;
1290                 }
1291                 if ((originalMessage.getSubject() != null) && (originalMessage.getSubject().trim().length() > 0)) {
1292                     part.setFileName(originalMessage.getSubject().trim());
1293                 } else {
1294                     part.setFileName("No Subject");
1295                 }
1296                 part.setDisposition("Attachment");
1297                 multipart.addBodyPart(part);
1298             }
1299             //if set, attach the original mail's error message
1300             if (attachError(originalMail) && originalMail.getErrorMessage() != null) {
1301                 part = new MimeBodyPart();
1302                 part.setContent(originalMail.getErrorMessage(), "text/plain");
1303                 part.setHeader(RFC2822Headers.CONTENT_TYPE, "text/plain");
1304                 part.setFileName("Reasons");
1305                 part.setDisposition(javax.mail.Part.ATTACHMENT);
1306                 multipart.addBodyPart(part);
1307             }
1308             newMail.getMessage().setContent(multipart);
1309             newMail.getMessage().setHeader(RFC2822Headers.CONTENT_TYPE, multipart.getContentType());
1310 
1311         } catch (Exception ioe) {
1312             throw new MessagingException("Unable to create multipart body", ioe);
1313         }
1314     }
1315 
1316     /***
1317      * Sets the message id of originalMail into newMail.
1318      */
1319     private void setMessageId(Mail newMail, Mail originalMail) throws MessagingException {
1320         String messageId = originalMail.getMessage().getMessageID();
1321         if (messageId != null) {
1322             newMail.getMessage().setHeader(RFC2822Headers.MESSAGE_ID, messageId);
1323             if (isDebug) {
1324                 log("MESSAGE_ID restored to: " + messageId);
1325             }
1326         }
1327     }
1328 
1329     /***
1330      * Returns the {@link SpecialAddress} that corresponds to an init parameter value.
1331      * The init parameter value is checked against a String[] of allowed values.
1332      * The checks are case insensitive.
1333      *
1334      * @param addressString the string to check if is a special address
1335      * @param allowedSpecials a String[] with the allowed special addresses
1336      * @return a SpecialAddress if found, null if not found or addressString is null
1337      * @throws MessagingException if is a special address not in the allowedSpecials array
1338      */
1339     protected final MailAddress getSpecialAddress(String addressString, String[] allowedSpecials) throws MessagingException {
1340         if (addressString == null) {
1341             return null;
1342         }
1343 
1344         addressString = addressString.toLowerCase(Locale.US);
1345         addressString = addressString.trim();
1346 
1347         MailAddress specialAddress = null;
1348 
1349         if(addressString.compareTo("postmaster") == 0) {
1350             specialAddress = getMailetContext().getPostmaster();
1351         }
1352         if(addressString.compareTo("sender") == 0) {
1353             specialAddress = SpecialAddress.SENDER;
1354         }
1355         if(addressString.compareTo("reversepath") == 0) {
1356             specialAddress = SpecialAddress.REVERSE_PATH;
1357         }
1358         if(addressString.compareTo("from") == 0) {
1359             specialAddress = SpecialAddress.FROM;
1360         }
1361         if(addressString.compareTo("replyto") == 0) {
1362             specialAddress = SpecialAddress.REPLY_TO;
1363         }
1364         if(addressString.compareTo("to") == 0) {
1365             specialAddress = SpecialAddress.TO;
1366         }
1367         if(addressString.compareTo("recipients") == 0) {
1368             specialAddress = SpecialAddress.RECIPIENTS;
1369         }
1370         if(addressString.compareTo("delete") == 0) {
1371             specialAddress = SpecialAddress.DELETE;
1372         }
1373         if(addressString.compareTo("unaltered") == 0) {
1374             specialAddress = SpecialAddress.UNALTERED;
1375         }
1376         if(addressString.compareTo("null") == 0) {
1377             specialAddress = SpecialAddress.NULL;
1378         }
1379 
1380         // if is a special address, must be in the allowedSpecials array
1381         if (specialAddress != null) {
1382             // check if is an allowed special
1383             boolean allowed = false;
1384             for (int i = 0; i < allowedSpecials.length; i++) {
1385                 String allowedSpecial = allowedSpecials[i];
1386                 allowedSpecial = allowedSpecial.toLowerCase(Locale.US);
1387                 allowedSpecial = allowedSpecial.trim();
1388                 if(addressString.compareTo(al