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  
21  
22  package org.apache.james.transport.mailets;
23  
24  import org.apache.james.Constants;
25  import org.apache.james.core.MailImpl;
26  import org.apache.james.dsn.DSNStatus;
27  import org.apache.mailet.base.mail.MimeMultipartReport;
28  import org.apache.mailet.Mail;
29  import org.apache.mailet.MailAddress;
30  import org.apache.mailet.base.RFC2822Headers;
31  import org.apache.mailet.base.RFC822DateFormat;
32  import org.apache.oro.text.regex.MalformedPatternException;
33  import org.apache.oro.text.regex.MatchResult;
34  import org.apache.oro.text.regex.Pattern;
35  import org.apache.oro.text.regex.Perl5Compiler;
36  import org.apache.oro.text.regex.Perl5Matcher;
37  
38  import javax.mail.MessagingException;
39  import javax.mail.SendFailedException;
40  import javax.mail.Session;
41  import javax.mail.internet.InternetAddress;
42  import javax.mail.internet.MimeBodyPart;
43  import javax.mail.internet.MimeMessage;
44  
45  import java.io.PrintWriter;
46  import java.io.StringWriter;
47  import java.net.ConnectException;
48  import java.net.InetAddress;
49  import java.net.SocketException;
50  import java.net.UnknownHostException;
51  import java.util.Collection;
52  import java.util.Date;
53  import java.util.HashSet;
54  import java.util.Iterator;
55  
56  
57  
58  
59  /**
60   *
61   * <P>Generates a Delivery Status Notification (DSN)
62   * Note that this is different than a mail-client's
63   * reply, which would use the Reply-To or From header.</P>
64   * <P>Bounced messages are attached in their entirety (headers and
65   * content) and the resulting MIME part type is "message/rfc822".<BR>
66   * The reverse-path and the Return-Path header of the response is set to "null" ("<>"),
67   * meaning that no reply should be sent.</P>
68   * <P>A sender of the notification message can optionally be specified.
69   * If one is not specified, the postmaster's address will be used.<BR>
70   * <P>Supports the <CODE>passThrough</CODE> init parameter (true if missing).</P>
71   *
72   * <P>Sample configuration:</P>
73   * <PRE><CODE>
74   * &lt;mailet match="All" class="DSNBounce">
75   *   &lt;sender&gt;<I>an address or postmaster or sender or unaltered, 
76   default=postmaster</I>&lt;/sender&gt;
77   *   &lt;prefix&gt;<I>optional subject prefix prepended to the original 
78   message</I>&lt;/prefix&gt;
79   *   &lt;attachment&gt;<I>message, heads or none, default=message</I>&lt;/attachment&gt;
80   *   &lt;messageString&gt;<I>the message sent in the bounce, the first occurrence of the pattern [machine] is replaced with the name of the executing machine, default=Hi. This is the James mail server at [machine] ... </I>&lt;/messageString&gt;
81   *   &lt;passThrough&gt;<I>true or false, default=true</I>&lt;/passThrough&gt;
82   *   &lt;debug&gt;<I>true or false, default=false</I>&lt;/debug&gt;
83   * &lt;/mailet&gt;
84   * </CODE></PRE>
85   *
86   * @see org.apache.james.transport.mailets.AbstractNotify
87   */
88  
89  
90  
91  public class DSNBounce extends AbstractNotify {
92  
93  
94      private static final RFC822DateFormat rfc822DateFormat = new RFC822DateFormat();
95  
96      // regexp pattern for scaning status code from exception message
97      private static Pattern statusPattern;
98  
99      private static Pattern diagPattern;
100 
101     private static final String MACHINE_PATTERN = "[machine]";
102 
103     private String messageString = null;
104 
105     /*
106      * Static initializer.<p>
107      * Compiles patterns for processing exception messages.<p>
108      */
109     static {
110         Perl5Compiler compiler = new Perl5Compiler();
111         String status_pattern_string = ".*\\s*([245]\\.\\d{1,3}\\.\\d{1,3}).*\\s*";
112         String diag_pattern_string = "^\\d{3}\\s.*$";
113         try {
114             statusPattern = compiler.
115                 compile(status_pattern_string, Perl5Compiler.READ_ONLY_MASK);
116         } catch(MalformedPatternException mpe) {
117             //this should not happen as the pattern string is hardcoded.
118             System.err.println ("Malformed pattern: " + status_pattern_string);
119             mpe.printStackTrace (System.err);
120         }
121         try {
122             diagPattern = compiler.
123                 compile(diag_pattern_string, Perl5Compiler.READ_ONLY_MASK);
124         } catch(MalformedPatternException mpe) {
125             //this should not happen as the pattern string is hardcoded.
126             System.err.println ("Malformed pattern: " + diag_pattern_string);
127         }
128     }
129 
130     /**
131      * Initialize the mailet
132      */
133     public void init() throws MessagingException {
134         super.init();
135         messageString = getInitParameter("messageString","Hi. This is the James mail server at [machine].\nI'm afraid I wasn't able to deliver your message to the following addresses.\nThis is a permanent error; I've given up. Sorry it didn't work out.  Below\nI include the list of recipients and the reason why I was unable to deliver\nyour message.\n");
136     }
137 
138     /**
139      * Service does the hard work and bounces the originalMail in the format specified by RFC3464.
140      *
141      * @param originalMail the mail to bounce
142      * @throws MessagingException if a problem arises formulating the redirected mail
143      *
144      * @see org.apache.mailet.Mailet#service(org.apache.mailet.Mail)
145      */
146     public void service(Mail originalMail) throws MessagingException {
147 
148 
149         // duplicates the Mail object, to be able to modify the new mail keeping the original untouched
150         MailImpl newMail = new MailImpl(originalMail);
151         try {
152             // We don't need to use the original Remote Address and Host,
153             // and doing so would likely cause a loop with spam detecting
154             // matchers.
155             newMail.setRemoteAddr(getMailetContext().getAttribute(Constants.HOSTADDRESS).toString());
156             newMail.setRemoteHost(getMailetContext().getAttribute(Constants.HOSTNAME).toString());
157             
158             if (originalMail.getSender() == null) {
159                 if (isDebug)
160                     log("Processing a bounce request for a message with an empty reverse-path.  No bounce will be sent.");
161                 if(!getPassThrough(originalMail)) {
162                     originalMail.setState(Mail.GHOST);
163                 }
164                 return;
165             }
166     
167             MailAddress reversePath = originalMail.getSender();
168             if (isDebug)
169                 log("Processing a bounce request for a message with a reverse path.  The bounce will be sent to " + reversePath);
170     
171             Collection newRecipients = new HashSet();
172             newRecipients.add(reversePath);
173             newMail.setRecipients(newRecipients);
174     
175             if (isDebug) {
176                 log("New mail - sender: " + newMail.getSender()
177                     + ", recipients: " +
178                     arrayToString(newMail.getRecipients().toArray())
179                     + ", name: " + newMail.getName()
180                     + ", remoteHost: " + newMail.getRemoteHost()
181                     + ", remoteAddr: " + newMail.getRemoteAddr()
182                     + ", state: " + newMail.getState()
183                     + ", lastUpdated: " + newMail.getLastUpdated()
184                     + ", errorMessage: " + newMail.getErrorMessage());
185             }
186     
187             // create the bounce message
188             MimeMessage newMessage =
189                 new MimeMessage(Session.getDefaultInstance(System.getProperties(),
190                                                            null));
191     
192             MimeMultipartReport multipart = new MimeMultipartReport ();
193             multipart.setReportType ("delivery-status");
194             
195             // part 1: descripive text message
196             MimeBodyPart part1 = createTextMsg(originalMail);
197             multipart.addBodyPart(part1);
198     
199             // part 2: DSN
200             MimeBodyPart part2 = createDSN(originalMail);
201             multipart.addBodyPart(part2);
202     
203     
204             // part 3: original mail (optional)
205             if (getAttachmentType() != NONE) {
206                 MimeBodyPart part3 = createAttachedOriginal(originalMail,getAttachmentType());
207                 multipart.addBodyPart(part3);
208             }
209     
210     
211             // stuffing all together
212             newMessage.setContent(multipart);
213             newMessage.setHeader(RFC2822Headers.CONTENT_TYPE, multipart.getContentType());
214             newMail.setMessage(newMessage);
215     
216             //Set additional headers
217             setRecipients(newMail, getRecipients(originalMail), originalMail);
218             setTo(newMail, getTo(originalMail), originalMail);
219             setSubjectPrefix(newMail, getSubjectPrefix(originalMail), originalMail);
220             if(newMail.getMessage().getHeader(RFC2822Headers.DATE) == null) {
221                 newMail.getMessage().setHeader(RFC2822Headers.DATE,rfc822DateFormat.format(new Date()));
222             }
223             setReplyTo(newMail, getReplyTo(originalMail), originalMail);
224             setReversePath(newMail, getReversePath(originalMail), originalMail);
225             setSender(newMail, getSender(originalMail), originalMail);
226             setIsReply(newMail, isReply(originalMail), originalMail);
227     
228             newMail.getMessage().saveChanges();
229             getMailetContext().sendMail(newMail);
230         } finally {
231             newMail.dispose();
232         }
233 
234         // ghosting the original mail
235         if(!getPassThrough(originalMail)) {
236             originalMail.setState(Mail.GHOST);
237         }
238     }
239 
240     /**
241      * Create a MimeBodyPart with a textual description for human readers.
242      *
243      * @param originalMail
244      * @return MimeBodyPart
245      * @throws MessagingException
246      */
247     protected MimeBodyPart createTextMsg(Mail originalMail)
248         throws MessagingException {
249         MimeBodyPart part1 = new MimeBodyPart();
250         StringWriter sout = new StringWriter();
251         PrintWriter out = new PrintWriter(sout, true);
252         String machine = "[unknown]";
253         try {
254             InetAddress me = InetAddress.getLocalHost();
255             machine = me.getHostName();
256         } catch(Exception e){
257             machine = "[address unknown]";
258         }
259 
260         StringBuffer bounceBuffer =
261             new StringBuffer(128).append (messageString);
262         int m_idx_begin = messageString.indexOf(MACHINE_PATTERN);
263         if (m_idx_begin != -1) {
264             bounceBuffer.replace (m_idx_begin,
265                                   m_idx_begin+MACHINE_PATTERN.length(),
266                                   machine);
267         }
268         out.println(bounceBuffer.toString());
269         out.println("Failed recipient(s):");
270         for (Iterator i = originalMail.getRecipients().iterator(); i.hasNext(); ) {
271             out.println(i.next());
272         }
273         MessagingException ex = (MessagingException)originalMail.getAttribute("delivery-error");
274         out.println();
275         out.println("Error message:");
276         out.println(getErrorMsg(ex));
277         out.println();
278 
279         part1.setText(sout.toString());
280         return part1;
281     }
282 
283     /**
284      * creates the DSN-bodypart for automated processing
285      *
286      * @param originalMail
287      * @return MimeBodyPart dsn-bodypart
288      * @throws MessagingException
289      */
290     protected MimeBodyPart createDSN(Mail originalMail) throws MessagingException {
291         MimeBodyPart dsn = new MimeBodyPart();
292         StringWriter sout = new StringWriter();
293         PrintWriter out = new PrintWriter(sout, true);
294         String nameType = null;
295 
296 
297         ////////////////////////
298         // per message fields //
299         ////////////////////////
300 
301         //optional: envelope-id
302         // TODO: Envelope-Id
303         // The Original-Envelope-Id is NOT the same as the Message-Id from the header.
304         // The Message-Id identifies the content of the message, while the Original-Envelope-ID
305         // identifies the transaction in which the message is sent.  (see RFC3461)
306         // so do NOT out.println("Original-Envelope-Id:"+originalMail.getMessage().getMessageID());
307 
308 
309         //required: reporting MTA
310         // this is always us, since we do not translate non-internet-mail
311         // failure reports into DSNs
312         nameType = "dns";
313         try {
314             String myAddress =
315                 (String)getMailetContext().getAttribute(Constants.HELLO_NAME);
316             /*
317             String myAddress = InetAddress.getLocalHost().getCanonicalHostName();
318             */
319             out.println("Reporting-MTA: "+nameType+"; "+myAddress);
320         } catch(Exception e){
321             // we should always know our address, so we shouldn't get here
322             log("WARNING: sending DSN without required Reporting-MTA Address");
323         }
324 
325         //only for gateways to non-internet mail systems: dsn-gateway
326 
327         //optional: received from
328         out.println("Received-From-MTA: "+nameType+"; "+originalMail.getRemoteHost());
329 
330         //optional: Arrival-Date
331 
332         //////////////////////////
333         // per recipient fields //
334         //////////////////////////
335 
336         Iterator recipients = originalMail.getRecipients().iterator();
337         while (recipients.hasNext())
338             {
339                 MailAddress rec = (MailAddress)recipients.next();
340                 String addressType = "rfc822";
341 
342                 //required: blank line
343                 out.println();
344 
345                 //optional: original recipient (see RFC3461)
346                 //out.println("Original-Recipient: "+addressType+"; "+ ??? );
347 
348                 //required: final recipient
349                 out.println("Final-Recipient: "+addressType+"; "+rec.toString());
350 
351                 //required: action
352                 // alowed values: failed, delayed, delivered, relayed, expanded
353                 // TODO: until now, we do error-bounces only
354                 out.println("Action: failed");
355 
356                 //required: status
357                 // get Exception for getting status information
358                 // TODO: it would be nice if the SMTP-handler would set a status attribute we can use here
359                 MessagingException ex =
360                     (MessagingException) originalMail.getAttribute("delivery-error");
361                 out.println("Status: "+getStatus(ex));
362 
363                 //optional: remote MTA
364                 //to which MTA were we talking while the Error occured?
365 
366                 //optional: diagnostic-code
367                 String diagnosticType = null;
368                 // this typically is the return value received during smtp
369                 // (or other transport) communication
370                 // and should be stored as attribute by the smtp handler
371                 // but until now we only have error-messages.
372                 String diagnosticCode = getErrorMsg(ex);
373                 // Sometimes this is the smtp diagnostic code,
374                 // but James often gives us other messages
375                 Perl5Matcher diagMatcher = new Perl5Matcher();
376                 boolean smtpDiagCodeAvailable =
377                     diagMatcher.matches(diagnosticCode, diagPattern);
378                 if (smtpDiagCodeAvailable){
379                     diagnosticType = "smtp";
380                 } else {
381                     diagnosticType = "X-James";
382                 }
383                 out.println("Diagnostic-Code: "+diagnosticType+"; "+diagnosticCode);
384             
385                 //optional: last attempt
386                 out.println("Last-Attempt-Date: "+
387                             rfc822DateFormat.format(originalMail.getLastUpdated()));
388 
389                 //optional: retry until
390                 //only for 'delayed' reports .. but we don't report this (at least until now)
391 
392                 //optional: extension fields
393 
394             }
395 
396 
397         // Changed this from rfc822 handling to text/plain
398         // It should be handled correctly as delivery-status but it
399         // is better text/plain than rfc822 (rfc822 add message headers not
400         // allowed in the delivery-status.
401         // text/plain does not support rfc822 header encoding that we
402         // should support here.
403         dsn.setContent(sout.toString(), "text/plain");
404         dsn.setHeader("Content-Type","message/delivery-status");
405         dsn.setDescription("Delivery Status Notification");
406         dsn.setFileName("status.dat");
407         return dsn;
408     }
409 
410     /**
411      * Create a MimeBodyPart with the original Mail as Attachment
412      *
413      * @param originalMail
414      * @return MimeBodyPart
415      * @throws MessagingException
416      */
417     protected MimeBodyPart createAttachedOriginal(Mail originalMail, int attachmentType)
418         throws MessagingException {
419         MimeBodyPart part = new MimeBodyPart();
420         MimeMessage originalMessage = originalMail.getMessage();
421         
422         if (attachmentType == HEADS) {
423             part.setContent(getMessageHeaders(originalMessage), "text/plain");
424             part.setHeader("Content-Type","text/rfc822-headers");
425         } else {
426             part.setContent(originalMessage, "message/rfc822");
427         }
428         
429         if ((originalMessage.getSubject() != null) && 
430             (originalMessage.getSubject().trim().length() > 0)) {
431             part.setFileName(originalMessage.getSubject().trim());
432         } else {
433             part.setFileName("No Subject");
434         }
435         part.setDisposition("Attachment");
436         return part;
437     }
438 
439     /**
440      * Guessing status code by the exception provided.
441      * This method should use the status attribute when the
442      * SMTP-handler somewhen provides it
443      *
444      * @param me  the MessagingException of which the statusCode should be generated
445      * @return status the generated statusCode
446      */
447     protected String getStatus(MessagingException me) {
448         if (me.getNextException() == null) {
449             String mess = me.getMessage();
450             Perl5Matcher m = new Perl5Matcher();
451             StringBuffer sb = new StringBuffer();
452             if (m.matches(mess, statusPattern)) {
453                 MatchResult res = m.getMatch();
454                 sb.append(res.group(1));
455                 return sb.toString();
456             }
457             // bad destination system adress
458             if (mess.startsWith("There are no DNS entries for the hostname"))
459                 return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.ADDRESS_SYSTEM);
460 
461             // no answer from host (4.4.1) or
462             // system not accepting network messages (4.3.2), lets guess ...
463             if (mess.equals("No mail server(s) available at this time."))
464                 return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_NO_ANSWER);
465 
466             // other/unknown error
467             return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.UNDEFINED_STATUS);
468         } else {
469             Exception ex1 = me.getNextException();
470             Perl5Matcher m = new Perl5Matcher ();
471             StringBuffer sb = new StringBuffer();
472             if (m.matches(ex1.getMessage(), statusPattern)) {
473                 MatchResult res = m.getMatch();
474                 sb.append(res.group(1));
475                 return sb.toString();
476             } else if (ex1 instanceof SendFailedException) {
477                 // other/undefined protocol status
478                 int smtpCode = 0;
479                 try {
480                     smtpCode = Integer.parseInt(ex1.getMessage().substring(0,3));
481                 } catch(NumberFormatException e) {
482                 }
483 
484                 switch (smtpCode) {
485                 
486                     // Req mail action not taken: mailbox unavailable
487                     case 450: return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.MAILBOX_OTHER);
488                     // Req action aborted: local error in processing
489                     case 451: return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.SYSTEM_OTHER);
490                     // Req action not taken: insufficient sys storage
491                     case 452: return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.SYSTEM_FULL);
492                     // Syntax error, command unrecognized
493                     case 500: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_SYNTAX);
494                     // Syntax error in parameters or arguments
495                     case 501: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_INVALID_ARG);
496                     // Command not implemented
497                     case 502: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_INVALID_CMD);
498                     // Bad sequence of commands
499                     case 503: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_INVALID_CMD);
500                     // Command parameter not implemented
501                     case 504: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_INVALID_ARG);
502                     // Req mail action not taken: mailbox unavailable
503                     case 550: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.MAILBOX_OTHER);
504                     // User not local; please try <...>
505                     // 5.7.1 Select another host to act as your forwarder
506                     case 551: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.SECURITY_AUTH);
507                     // Req mail action aborted: exceeded storage alloc
508                     case 552: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.MAILBOX_FULL);
509                     // Req action not taken: mailbox name not allowed
510                     case 553: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.ADDRESS_SYNTAX);
511                     // Transaction failed
512                     case 554: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.UNDEFINED_STATUS);
513                     // Not authorized. This is not an SMTP code, but many server use it.
514                     case 571: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.SECURITY_AUTH);
515                     
516                     default:
517                         // if we get an smtp returncode starting with 4
518                         // it is an persistent transient error, else permanent
519                         if (ex1.getMessage().startsWith("4")) {
520                             return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.DELIVERY_OTHER);
521                         } else return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_OTHER);
522                 }
523 
524             } else if (ex1 instanceof UnknownHostException) {
525                 // bad destination system address
526                 return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.ADDRESS_SYSTEM);
527             } else if (ex1 instanceof ConnectException) {
528                 // bad connection
529                 return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_CONNECTION);
530             } else if (ex1 instanceof SocketException) {
531                 // bad connection
532                 return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_CONNECTION);
533             } else {
534                 // other/undefined/unknown error
535                 return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.UNDEFINED_STATUS);
536             }
537         }
538     }
539 
540     /**
541      * Utility method for getting the error message from the (nested) exception.
542      * 
543      * @param me MessagingException
544      * @return error message
545      */
546     protected String getErrorMsg(MessagingException me) {
547         if (me.getNextException() == null) {
548             return me.getMessage().trim();
549         } else {
550             Exception ex1 = me.getNextException();
551             return ex1.getMessage().trim();
552         }
553     }
554 
555 
556     public String getMailetInfo() {
557         return "DSNBounce Mailet";
558     }
559     /* ******************************************************************** */
560     /* ****************** Begin of getX and setX methods ****************** */
561     /* ******************************************************************** */
562 
563     /** Gets the expected init parameters.  */
564     protected  String[] getAllowedInitParameters() {
565         String[] allowedArray = {
566             "debug",
567             "passThrough",
568             "messageString",
569             "attachment",
570             "sender",
571             "prefix"
572         };
573         return allowedArray;
574     }
575 
576     /**
577      * @return the <CODE>attachment</CODE> init parameter, or <CODE>MESSAGE</CODE> if missing
578      */
579     protected int getAttachmentType() throws MessagingException {
580         return getTypeCode(getInitParameter("attachment","message"));
581     }
582 
583 
584     /**
585      * @return <CODE>SpecialAddress.REVERSE_PATH</CODE>
586      */
587     protected Collection getRecipients() {
588         Collection newRecipients = new HashSet();
589         newRecipients.add(SpecialAddress.REVERSE_PATH);
590         return newRecipients;
591     }
592 
593     /**
594      * @return <CODE>SpecialAddress.REVERSE_PATH</CODE>
595      */
596     protected InternetAddress[] getTo() {
597         InternetAddress[] apparentlyTo = new InternetAddress[1];
598         apparentlyTo[0] = SpecialAddress.REVERSE_PATH.toInternetAddress();
599         return apparentlyTo;
600     }
601 
602     /**
603      * @return <CODE>SpecialAddress.NULL</CODE> (the meaning of bounce)
604      */
605     protected MailAddress getReversePath(Mail originalMail) {
606         return SpecialAddress.NULL;
607     }
608 
609     /* ******************************************************************** */
610     /* ******************* End of getX and setX methods ******************* */
611     /* ******************************************************************** */
612 
613 }