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