KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jsmtpd > core > send > DeliveryHandler


1 /*
2  *
3  * Jsmtpd, Java SMTP daemon
4  * Copyright (C) 2005 Jean-Francois POUX, jf.poux@laposte.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  *
20  */

21 package org.jsmtpd.core.send;
22
23 import java.io.ByteArrayInputStream JavaDoc;
24 import java.io.ByteArrayOutputStream JavaDoc;
25 import java.util.ArrayList JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import java.util.LinkedList JavaDoc;
29 import java.util.List JavaDoc;
30 import java.util.Map JavaDoc;
31
32 import javax.mail.Multipart JavaDoc;
33 import javax.mail.internet.MimeBodyPart JavaDoc;
34 import javax.mail.internet.MimeMessage JavaDoc;
35 import javax.mail.internet.MimeMultipart JavaDoc;
36
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39 import org.jsmtpd.config.ReadConfig;
40 import org.jsmtpd.core.common.PluginStore;
41 import org.jsmtpd.core.common.acl.IACL;
42 import org.jsmtpd.core.common.delivery.IDeliveryService;
43 import org.jsmtpd.core.common.filter.FilterTreeFailureException;
44 import org.jsmtpd.core.common.filter.FilterTreeNode;
45 import org.jsmtpd.core.common.filter.FilterTreeSuccesException;
46 import org.jsmtpd.core.mail.Email;
47 import org.jsmtpd.core.mail.Rcpt;
48 import org.jsmtpd.tools.ByteArrayTool;
49
50 /**
51  * Handles the delivery process of a message:<br>
52  * -Filtering it throught the filter tree<br>
53  * -Determining if it is local or not<br>
54  * -Delivering it for each rcpt using the right plugin.
55  * @author Jean-Francois POUX
56  */

57 public class DeliveryHandler {
58
59     private Email e = null;
60     private Log log=LogFactory.getLog(DeliveryHandler.class);
61     private IACL acl;
62     private IDeliveryService local;
63     private IDeliveryService remote;
64     private QueueService dsvc;
65     private String JavaDoc runningHost;
66     private int maxRetries;
67     private int retryDelay;
68     
69     public DeliveryHandler() {
70         ReadConfig cfg = ReadConfig.getInstance();
71         acl = PluginStore.getInstance().getAcl();
72         local = PluginStore.getInstance().getLocalDeliveryService();
73         remote = PluginStore.getInstance().getRemoteDeliveryService();
74         dsvc = QueueService.getInstance();
75         runningHost=cfg.getLocalDomain();
76         maxRetries = cfg.getMaxRetries();
77         retryDelay = cfg.getDelayRetry();
78     }
79
80     public void clearMail() {
81         e = null;
82     }
83
84     /**
85      * trys to send a mail
86      * @param in mail to send
87      */

88     public void processMessage(Email in) {
89         this.e = in;
90         String JavaDoc message = e.getDiskName();
91         log.debug("Begin process " + e.getDiskName());
92
93         // apply filter
94
if (!e.isFiltered()) {
95             if (!applyFilterChain()) {
96                 log.info("Message " + message + " was dropped during filtering chain");
97                 return;
98             }
99             e.setFiltered(true);
100         }
101         log.info("Message " + message + " has passed filtering tree");
102
103         List JavaDoc<Rcpt> rcpt = e.getRcpt();
104         Map JavaDoc<String JavaDoc,List JavaDoc<Rcpt>> batch = prepareBatch(in);
105
106         for (Iterator JavaDoc iter = batch.keySet().iterator(); iter.hasNext();) {
107             String JavaDoc domain = (String JavaDoc) iter.next();
108             List JavaDoc<Rcpt> domainBatch = batch.get(domain);
109             //Get the first recipient and use it to determine if it's local or remote domain
110
Rcpt tmp = (Rcpt) domainBatch.get(0);
111             if (acl.isValidAddress(tmp.getEmailAddress())) {
112                 try {
113                     local.doDelivery(e, domainBatch);
114                 } catch (RuntimeException JavaDoc runtimeException) {
115                     log.fatal("DeliveryHandler has detected a problem in the loaded local delivery plugin: ",runtimeException);
116                 }
117             } else {
118                 try {
119                     remote.doDelivery(e, domainBatch);
120                 } catch (RuntimeException JavaDoc runtimeException) {
121                     log.fatal("DeliveryHandler has detected a problem in the loaded remote delivery plugin: ",runtimeException);
122                 }
123             }
124         }
125         // the delivery plugins will modify the status of each recipient (success, temporary failure, fatal failure).
126
// We need to check if retries does not exceed a threshold.
127
for (Iterator JavaDoc iter = rcpt.iterator(); iter.hasNext();) {
128             Rcpt element = (Rcpt) iter.next();
129             if (element.getDeliveryAttempts() > maxRetries) {
130                 element.setDelivered(Rcpt.STATUS_ERROR_FATAL); //change to fatal
131
element.setLastError("Exceeding delivery attemps ("+maxRetries+"+)");
132                 log.warn("Maxmimum retry for RCPT " + element.getEmailAddress().toString() + " in mail " + e.getDiskName());
133             }
134         }
135
136         // are all rcpt delivered ?
137
if (e.isDelivered()) {
138             log.info("Message " + message + " has been fully delivered");
139             sendErrorMail();
140             e = null;
141
142         } else {
143             if (!dsvc.requeueMail(e))
144                 log.error("Message " + message + " can't be requeued for delivery, message is lost");
145             else {
146                 log.info("Message " + message + " is requeued for delivery");
147                 if (e.getAttemps()==1)
148                     sendNotification(); // This is the first time mail is sucessfully requeued. So it has non faltal errors. Send bounce mail to inform
149
}
150         }
151         e = null; // don't keep a reference to the mail, so if requeued by delivery svc it can be gc'ed
152
log.debug("End process " + message);
153     }
154
155     /**
156      * Prepares a map of grouped domains, each entry is a list of recipient to deliver to the domain (key of the map)
157      * @param in the mail to process
158      * @return the batch map
159      */

160     private Map JavaDoc<String JavaDoc,List JavaDoc<Rcpt>> prepareBatch(Email in) {
161         Map JavaDoc<String JavaDoc,List JavaDoc<Rcpt>> batch = new HashMap JavaDoc<String JavaDoc,List JavaDoc<Rcpt>>();
162         List JavaDoc<Rcpt> rawRcpt = in.getRcpt();
163
164         for (Iterator JavaDoc iter = rawRcpt.iterator(); iter.hasNext();) {
165             Rcpt rcpt = (Rcpt) iter.next();
166             if (!rcpt.isDelivered()) { // skip already delivered
167
log.debug("Delivery handler adding "+rcpt.getEmailAddress().toString()+" to batch (num retry="+rcpt.getDeliveryAttempts()+")");
168                 String JavaDoc domain = rcpt.getEmailAddress().getHost();
169                 if (!batch.containsKey(domain)) {
170                     List JavaDoc<Rcpt> domainBatch = new ArrayList JavaDoc<Rcpt>();
171                     domainBatch.add(rcpt);
172                     batch.put(domain, domainBatch);
173                 } else {
174                     List JavaDoc<Rcpt> domainBatch = batch.get(domain);
175                     domainBatch.add(rcpt);
176                 }
177             }
178         }
179         return batch;
180     }
181
182     /**
183      * Applies the filter tree to a mail
184      * @return true if mail can be sent, false if it is to be dropped
185      */

186     private boolean applyFilterChain() {
187         FilterTreeNode rootFilter = PluginStore.getInstance().getRootFilter();
188         if (rootFilter == null)//no filter, auto succes
189
return true;
190         try {
191             rootFilter.doFilter(e);
192         } catch (FilterTreeFailureException e) {
193             return false;
194         } catch (FilterTreeSuccesException e) {
195             return true;
196         } catch (Throwable JavaDoc e) {
197             log.fatal("A plugin in the filter tree throwed an uncaught exception: ", e);
198             return true;
199         }
200         return true;
201
202     }
203     private void sendErrorMail(){
204         // If there is a fatal error, notify sender.
205
boolean bounce=false;
206         for (Iterator JavaDoc iter = e.getRcpt().iterator(); iter.hasNext();) {
207             Rcpt element = (Rcpt) iter.next();
208             if (element.getStatus()==Rcpt.STATUS_ERROR_FATAL) {
209                 bounce=true;
210                 break;
211             }
212         }
213         //notice send of failure for each RCPT failed, except if bounce mail
214
if ( (!e.getFrom().toString().equals("<>")) && bounce) {
215             LinkedList JavaDoc<String JavaDoc> messages = new LinkedList JavaDoc<String JavaDoc>();
216             messages.add("");
217             messages.add("Hello, this is the Jsmtpd mailer daemon, running on system "+runningHost);
218             messages.add("");
219             messages.add("I'm affraid I can't deliver your email to : ");
220             for (Iterator JavaDoc iter = e.getRcpt().iterator(); iter.hasNext();) {
221                 Rcpt element = (Rcpt) iter.next();
222                 if (element.getStatus() == Rcpt.STATUS_ERROR_FATAL)
223                     messages.add("\t Recipent: " + element.getEmailAddress().toString() + ", fatal error: "+element.getLastError());
224             }
225             messages.add("");
226             messages.add("This is a fatal error, giving up");
227             Email error = Email.createInternalMail(e.getFrom(), "Mailer-daemon, Error processing mail", messages, e);
228             attachMail(error,this.e);
229             log.warn("Mail "+e.getDiskName()+" has fatal delivery errors, sending errors back to sender");
230             dsvc.queueMail(error);
231         }
232     }
233     
234     private void sendNotification() {
235         boolean bounce=false;
236         for (Iterator JavaDoc iter = e.getRcpt().iterator(); iter.hasNext();) {
237             Rcpt element = (Rcpt) iter.next();
238             if (element.getStatus()==Rcpt.STATUS_ERROR_NOT_FATAL) {
239                 bounce=true;
240                 break;
241             }
242         }
243         
244         if ( (!e.getFrom().toString().equals("<>")) && bounce) {
245             LinkedList JavaDoc<String JavaDoc> messages = new LinkedList JavaDoc<String JavaDoc>();
246             messages.add("");
247             messages.add("Hello, this is the Jsmtpd mailer daemon, running on system "+runningHost);
248             messages.add("");
249             messages.add("You wanted me to deliver your mail, but I could not do immediatly for : ");
250             for (Iterator JavaDoc iter = e.getRcpt().iterator(); iter.hasNext();) {
251                 Rcpt element = (Rcpt) iter.next();
252                 if (element.getStatus() == Rcpt.STATUS_ERROR_FATAL)
253                     messages.add("\t Recipent: " + element.getEmailAddress().toString() + ", fatal error (I give up) : "+element.getLastError());
254                 if (element.getStatus() == Rcpt.STATUS_ERROR_NOT_FATAL) {
255                     messages.add("\t Recipent: " + element.getEmailAddress().toString() + ", temporary error (I'll retry): "+element.getLastError());
256                 }
257             }
258             messages.add("");
259             messages.add("I'll try to send your mail to temporary unavailable recipients, up to "+maxRetries+" times, trying each "+retryDelay+" minute(s)");
260             Email error = Email.createInternalMail(e.getFrom(), "Jsmtpd warning, Problem sending your mail", messages, e);
261             attachMail(error,this.e);
262             log.warn("Mail "+e.getDiskName()+" has temporary delivery errors, notifying sender");
263             dsvc.queueMail(error);
264         }
265     }
266     
267
268     private void attachMail (Email target, Email join) {
269         try {
270             log.debug("Attaching original mail to error report mail");
271             MimeMessage JavaDoc targetMime = new MimeMessage JavaDoc(null,new ByteArrayInputStream JavaDoc(target.getDataAsByte()));
272             MimeMessage JavaDoc joinMime = new MimeMessage JavaDoc (null,new ByteArrayInputStream JavaDoc(join.getDataAsByte()));
273             
274             MimeBodyPart JavaDoc targetPart = new MimeBodyPart JavaDoc();
275             targetPart.setDataHandler(targetMime.getDataHandler());
276             
277             MimeBodyPart JavaDoc joinedPart = new MimeBodyPart JavaDoc();
278             joinedPart.setDataHandler(joinMime.getDataHandler());
279             joinedPart.setFileName("Original mail");
280             
281             Multipart JavaDoc multipart = new MimeMultipart JavaDoc ();
282             multipart.addBodyPart(targetPart);
283             multipart.addBodyPart(joinedPart);
284             
285             targetMime.setContent(multipart);
286             targetMime.saveChanges();
287             
288             ByteArrayOutputStream JavaDoc bos = new ByteArrayOutputStream JavaDoc();
289             targetMime.writeTo(bos);
290             
291             target.setDataBuffer(ByteArrayTool.crlfFix(bos.toByteArray()));
292             log.debug("New error mail has original mail attached");
293         } catch (Exception JavaDoc e) {
294             log.error("Unable to attach original mail: ",e);
295         }
296     }
297 }
Popular Tags