| 1 21 package org.jsmtpd.core.send; 22 23 import java.io.ByteArrayInputStream ; 24 import java.io.ByteArrayOutputStream ; 25 import java.util.ArrayList ; 26 import java.util.HashMap ; 27 import java.util.Iterator ; 28 import java.util.LinkedList ; 29 import java.util.List ; 30 import java.util.Map ; 31 32 import javax.mail.Multipart ; 33 import javax.mail.internet.MimeBodyPart ; 34 import javax.mail.internet.MimeMessage ; 35 import javax.mail.internet.MimeMultipart ; 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 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 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 88 public void processMessage(Email in) { 89 this.e = in; 90 String message = e.getDiskName(); 91 log.debug("Begin process " + e.getDiskName()); 92 93 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 <Rcpt> rcpt = e.getRcpt(); 104 Map <String ,List <Rcpt>> batch = prepareBatch(in); 105 106 for (Iterator iter = batch.keySet().iterator(); iter.hasNext();) { 107 String domain = (String ) iter.next(); 108 List <Rcpt> domainBatch = batch.get(domain); 109 Rcpt tmp = (Rcpt) domainBatch.get(0); 111 if (acl.isValidAddress(tmp.getEmailAddress())) { 112 try { 113 local.doDelivery(e, domainBatch); 114 } catch (RuntimeException 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 runtimeException) { 121 log.fatal("DeliveryHandler has detected a problem in the loaded remote delivery plugin: ",runtimeException); 122 } 123 } 124 } 125 for (Iterator iter = rcpt.iterator(); iter.hasNext();) { 128 Rcpt element = (Rcpt) iter.next(); 129 if (element.getDeliveryAttempts() > maxRetries) { 130 element.setDelivered(Rcpt.STATUS_ERROR_FATAL); element.setLastError("Exceeding delivery attemps ("+maxRetries+"+)"); 132 log.warn("Maxmimum retry for RCPT " + element.getEmailAddress().toString() + " in mail " + e.getDiskName()); 133 } 134 } 135 136 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(); } 150 } 151 e = null; log.debug("End process " + message); 153 } 154 155 160 private Map <String ,List <Rcpt>> prepareBatch(Email in) { 161 Map <String ,List <Rcpt>> batch = new HashMap <String ,List <Rcpt>>(); 162 List <Rcpt> rawRcpt = in.getRcpt(); 163 164 for (Iterator iter = rawRcpt.iterator(); iter.hasNext();) { 165 Rcpt rcpt = (Rcpt) iter.next(); 166 if (!rcpt.isDelivered()) { log.debug("Delivery handler adding "+rcpt.getEmailAddress().toString()+" to batch (num retry="+rcpt.getDeliveryAttempts()+")"); 168 String domain = rcpt.getEmailAddress().getHost(); 169 if (!batch.containsKey(domain)) { 170 List <Rcpt> domainBatch = new ArrayList <Rcpt>(); 171 domainBatch.add(rcpt); 172 batch.put(domain, domainBatch); 173 } else { 174 List <Rcpt> domainBatch = batch.get(domain); 175 domainBatch.add(rcpt); 176 } 177 } 178 } 179 return batch; 180 } 181 182 186 private boolean applyFilterChain() { 187 FilterTreeNode rootFilter = PluginStore.getInstance().getRootFilter(); 188 if (rootFilter == null) 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 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 boolean bounce=false; 206 for (Iterator 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 if ( (!e.getFrom().toString().equals("<>")) && bounce) { 215 LinkedList <String > messages = new LinkedList <String >(); 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 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 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 <String > messages = new LinkedList <String >(); 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 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 targetMime = new MimeMessage (null,new ByteArrayInputStream (target.getDataAsByte())); 272 MimeMessage joinMime = new MimeMessage (null,new ByteArrayInputStream (join.getDataAsByte())); 273 274 MimeBodyPart targetPart = new MimeBodyPart (); 275 targetPart.setDataHandler(targetMime.getDataHandler()); 276 277 MimeBodyPart joinedPart = new MimeBodyPart (); 278 joinedPart.setDataHandler(joinMime.getDataHandler()); 279 joinedPart.setFileName("Original mail"); 280 281 Multipart multipart = new MimeMultipart (); 282 multipart.addBodyPart(targetPart); 283 multipart.addBodyPart(joinedPart); 284 285 targetMime.setContent(multipart); 286 targetMime.saveChanges(); 287 288 ByteArrayOutputStream bos = new ByteArrayOutputStream (); 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 e) { 294 log.error("Unable to attach original mail: ",e); 295 } 296 } 297 } | Popular Tags |