View Javadoc

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