001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.net.smtp; 020 021import java.io.FileNotFoundException; 022import java.io.Serializable; 023import java.io.UnsupportedEncodingException; 024import java.net.URL; 025import java.text.SimpleDateFormat; 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.Date; 029import java.util.Enumeration; 030import java.util.HashMap; 031import java.util.Iterator; 032import java.util.Locale; 033import java.util.Map; 034import java.util.Map.Entry; 035import java.util.Properties; 036import java.util.TimeZone; 037 038import javax.activation.DataHandler; 039import javax.mail.Authenticator; 040import javax.mail.BodyPart; 041import javax.mail.Message; 042import javax.mail.MessagingException; 043import javax.mail.Multipart; 044import javax.mail.NoSuchProviderException; 045import javax.mail.internet.InternetAddress; 046import javax.mail.internet.MimeBodyPart; 047import javax.mail.internet.MimeMessage; 048import javax.mail.internet.MimeMultipart; 049import javax.mail.internet.MimePart; 050 051import lucee.commons.activation.ResourceDataSource; 052import lucee.commons.digest.MD5; 053import lucee.commons.io.SystemUtil; 054import lucee.commons.io.log.Log; 055import lucee.commons.io.log.LogUtil; 056import lucee.commons.io.res.Resource; 057import lucee.commons.io.res.util.ResourceUtil; 058import lucee.commons.lang.ExceptionUtil; 059import lucee.commons.lang.SerializableObject; 060import lucee.commons.lang.StringUtil; 061import lucee.runtime.config.Config; 062import lucee.runtime.config.ConfigImpl; 063import lucee.runtime.config.ConfigWeb; 064import lucee.runtime.config.ConfigWebImpl; 065import lucee.runtime.engine.ThreadLocalPageContext; 066import lucee.runtime.exp.ExpressionException; 067import lucee.runtime.exp.PageException; 068import lucee.runtime.net.mail.MailException; 069import lucee.runtime.net.mail.MailPart; 070import lucee.runtime.net.mail.MailUtil; 071import lucee.runtime.net.mail.Server; 072import lucee.runtime.net.mail.ServerImpl; 073import lucee.runtime.net.proxy.Proxy; 074import lucee.runtime.net.proxy.ProxyData; 075import lucee.runtime.net.proxy.ProxyDataImpl; 076import lucee.runtime.net.smtp.SMTPConnectionPool.SessionAndTransport; 077import lucee.runtime.op.Caster; 078import lucee.runtime.spooler.mail.MailSpoolerTask; 079import lucee.runtime.type.util.ArrayUtil; 080import lucee.runtime.type.util.ListUtil; 081 082import org.apache.commons.collections.ReferenceMap; 083 084import com.sun.mail.smtp.SMTPMessage; 085 086public final class SMTPClient implements Serializable { 087 088 private static final long serialVersionUID = 5227282806519740328L; 089 090 private static final int SPOOL_UNDEFINED=0; 091 private static final int SPOOL_YES=1; 092 private static final int SPOOL_NO=2; 093 094 private static final int SSL_NONE=0; 095 private static final int SSL_YES=1; 096 private static final int SSL_NO=2; 097 098 private static final int TLS_NONE=0; 099 private static final int TLS_YES=1; 100 private static final int TLS_NO=2; 101 102 private static final String TEXT_HTML = "text/html"; 103 private static final String TEXT_PLAIN = "text/plain"; 104 //private static final SerializableObject LOCK = new SerializableObject(); 105 106 private static Map<TimeZone, SimpleDateFormat> formatters=new ReferenceMap(ReferenceMap.SOFT,ReferenceMap.SOFT); 107 //private static final int PORT = 25; 108 109 private int spool=SPOOL_UNDEFINED; 110 111 private int timeout=-1; 112 113 private String plainText; 114 private String plainTextCharset; 115 116 private String htmlText; 117 118 119 private String htmlTextCharset; 120 121 private Attachment[] attachmentz; 122 123 private String[] host; 124 private String charset="UTF-8"; 125 private InternetAddress from; 126 private InternetAddress[] tos; 127 private InternetAddress[] bccs; 128 private InternetAddress[] ccs; 129 private InternetAddress[] rts; 130 private InternetAddress[] fts; 131 private String subject=""; 132 private String xmailer="Lucee Mail"; 133 private Map<String,String> headers=new HashMap<String,String>(); 134 private int port=-1; 135 136 private String username; 137 private String password=""; 138 private long lifeTimespan=100*60*5; 139 private long idleTimespan=100*60*1; 140 141 142 143 144 145 private int ssl=SSL_NONE; 146 private int tls=TLS_NONE; 147 148 ProxyData proxyData=new ProxyDataImpl(); 149 private ArrayList<MailPart> parts; 150 151 private TimeZone timeZone; 152 153 154 public static String getNow(TimeZone tz){ 155 tz = ThreadLocalPageContext.getTimeZone(tz); 156 SimpleDateFormat df=formatters.get(tz); 157 if(df==null) { 158 df = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z (z)",Locale.US); 159 df.setTimeZone(tz); 160 formatters.put(tz, df); 161 } 162 return df.format(new Date()); 163 } 164 165 166 public void setSpoolenable(boolean spoolenable) { 167 spool=spoolenable?SPOOL_YES:SPOOL_NO; 168 } 169 170 /** 171 * set port of the mailserver 172 * @param port 173 */ 174 public void setPort(int port) { 175 this.port=port; 176 } 177 178 /** 179 * @param charset the charset to set 180 */ 181 public void setCharset(String charset) { 182 this.charset = charset; 183 } 184 185 186 public static ServerImpl toServerImpl(String server,int port, String usr,String pwd, long lifeTimespan, long idleTimespan) throws MailException { 187 int index; 188 189 // username/password 190 index=server.indexOf('@'); 191 if(index!=-1) { 192 usr=server.substring(0,index); 193 server=server.substring(index+1); 194 index=usr.indexOf(':'); 195 if(index!=-1) { 196 pwd=usr.substring(index+1); 197 usr=usr.substring(0,index); 198 } 199 } 200 201 // port 202 index=server.indexOf(':'); 203 if(index!=-1) { 204 try { 205 port=Caster.toIntValue(server.substring(index+1)); 206 } catch (ExpressionException e) { 207 throw new MailException(e.getMessage()); 208 } 209 server=server.substring(0,index); 210 } 211 212 213 ServerImpl srv = ServerImpl.getInstance(server, port, usr, pwd,lifeTimespan,idleTimespan, false, false); 214 return srv; 215 } 216 217 public void setHost(String host) throws PageException { 218 if(!StringUtil.isEmpty(host,true))this.host = ListUtil.toStringArray(ListUtil.listToArrayRemoveEmpty(host, ',')); 219 } 220 221 /** 222 * @param password the password to set 223 */ 224 public void setPassword(String password) { 225 this.password = password; 226 } 227 228 /** 229 * @param username the username to set 230 */ 231 public void setUsername(String username) { 232 this.username = username; 233 } 234 235 public void setLifeTimespan(long life) { 236 this.lifeTimespan = life; 237 } 238 239 public void setIdleTimespan(long idle) { 240 this.idleTimespan = idle; 241 } 242 243 244 public void addHeader(String name, String value) { 245 headers.put(name, value); 246 } 247 248 public void addTo(InternetAddress to) { 249 tos=add(tos,to); 250 } 251 252 public void addTo(Object to) throws UnsupportedEncodingException, PageException, MailException { 253 InternetAddress[] tmp = MailUtil.toInternetAddresses(to); 254 for(int i=0;i<tmp.length;i++) { 255 addTo(tmp[i]); 256 } 257 } 258 259 public void setFrom(InternetAddress from) { 260 this.from=from; 261 } 262 263 public void setFrom(Object from) throws UnsupportedEncodingException, MailException, PageException { 264 InternetAddress[] addrs = MailUtil.toInternetAddresses(from); 265 if(addrs.length==0) return; 266 setFrom(addrs[0]); 267 } 268 269 public void addBCC(InternetAddress bcc) { 270 bccs=add(bccs,bcc); 271 } 272 273 public void addBCC(Object bcc) throws UnsupportedEncodingException, MailException, PageException { 274 InternetAddress[] tmp = MailUtil.toInternetAddresses(bcc); 275 for(int i=0;i<tmp.length;i++) { 276 addBCC(tmp[i]); 277 } 278 } 279 280 public void addCC(InternetAddress cc) { 281 ccs=add(ccs,cc); 282 } 283 284 public void addCC(Object cc) throws UnsupportedEncodingException, MailException, PageException { 285 InternetAddress[] tmp = MailUtil.toInternetAddresses(cc); 286 for(int i=0;i<tmp.length;i++) { 287 addCC(tmp[i]); 288 } 289 } 290 291 public void addReplyTo(InternetAddress rt) { 292 rts=add(rts,rt); 293 } 294 295 public void addReplyTo(Object rt) throws UnsupportedEncodingException, MailException, PageException { 296 InternetAddress[] tmp = MailUtil.toInternetAddresses(rt); 297 for(int i=0;i<tmp.length;i++) { 298 addReplyTo(tmp[i]); 299 } 300 } 301 302 public void addFailTo(InternetAddress ft) { 303 fts=add(fts,ft); 304 } 305 306 public String getHTMLTextAsString() { 307 return htmlText; 308 } 309 public String getPlainTextAsString() { 310 return plainText; 311 } 312 313 public void addFailTo(Object ft) throws UnsupportedEncodingException, MailException, PageException { 314 InternetAddress[] tmp = MailUtil.toInternetAddresses(ft); 315 for(int i=0;i<tmp.length;i++) { 316 addFailTo(tmp[i]); 317 } 318 } 319 320 /** 321 * @param timeout the timeout to set 322 */ 323 public void setTimeout(int timeout) { 324 this.timeout = timeout; 325 } 326 327 public void setSubject(String subject) { 328 this.subject=subject; 329 } 330 331 public void setXMailer(String xmailer) { 332 this.xmailer=xmailer; 333 } 334 335 /** 336 * creates a new expanded array and return it; 337 * @param oldArr 338 * @param newValue 339 * @return new expanded array 340 */ 341 protected static InternetAddress[] add(InternetAddress[] oldArr, InternetAddress newValue) { 342 if(oldArr==null) return new InternetAddress[] {newValue}; 343 //else { 344 InternetAddress[] tmp=new InternetAddress[oldArr.length+1]; 345 for(int i=0;i<oldArr.length;i++) { 346 tmp[i]=oldArr[i]; 347 } 348 tmp[oldArr.length]=newValue; 349 return tmp; 350 //} 351 } 352 353 protected static Attachment[] add(Attachment[] oldArr, Attachment newValue) { 354 if(oldArr==null) return new Attachment[] {newValue}; 355 //else { 356 Attachment[] tmp=new Attachment[oldArr.length+1]; 357 for(int i=0;i<oldArr.length;i++) { 358 tmp[i]=oldArr[i]; 359 } 360 tmp[oldArr.length]=newValue; 361 return tmp; 362 //} 363 } 364 365 public static class MimeMessageAndSession { 366 public final MimeMessage message; 367 public final SessionAndTransport session; 368 369 public MimeMessageAndSession(MimeMessage message,SessionAndTransport session){ 370 this.message=message; 371 this.session=session; 372 } 373 } 374 375 public static SessionAndTransport getSessionAndTransport(lucee.runtime.config.Config config,String hostName, int port, 376 String username, String password, long lifeTimesan, long idleTimespan,int socketTimeout, 377 boolean tls,boolean ssl, boolean newConnection) throws NoSuchProviderException, MessagingException { 378 Properties props = createProperties(config,hostName,port,username,password,tls,ssl,socketTimeout); 379 Authenticator auth=null; 380 if(!StringUtil.isEmpty(username)) 381 auth=new SMTPAuthenticator( username, password ); 382 383 return newConnection?new SessionAndTransport(hash(props), props, auth,lifeTimesan,idleTimespan): 384 SMTPConnectionPool.getSessionAndTransport(props,hash(props),auth,lifeTimesan,idleTimespan); 385 386 } 387 private MimeMessageAndSession createMimeMessage(lucee.runtime.config.Config config,String hostName, int port, 388 String username, String password, long lifeTimesan, long idleTimespan, 389 boolean tls,boolean ssl, boolean newConnection) throws MessagingException { 390 391 SessionAndTransport sat = getSessionAndTransport(config, hostName, port, username, password, lifeTimesan, idleTimespan, timeout, tls, ssl, newConnection); 392 /*Properties props = createProperties(config,hostName,port,username,password,tls,ssl,timeout); 393 Authenticator auth=null; 394 if(!StringUtil.isEmpty(username)) 395 auth=new SMTPAuthenticator( username, password ); 396 397 398 SessionAndTransport sat = newConnection?new SessionAndTransport(hash(props), props, auth,lifeTimesan,idleTimespan): 399 SMTPConnectionPool.getSessionAndTransport(props,hash(props),auth,lifeTimesan,idleTimespan); 400 */ 401 // Contacts 402 SMTPMessage msg = new SMTPMessage(sat.session); 403 if(from==null)throw new MessagingException("you have do define the from for the mail"); 404 //if(tos==null)throw new MessagingException("you have do define the to for the mail"); 405 406 checkAddress(from,charset); 407 //checkAddress(tos,charset); 408 409 msg.setFrom(from); 410 //msg.setRecipients(Message.RecipientType.TO, tos); 411 412 if(tos!=null){ 413 checkAddress(tos,charset); 414 msg.setRecipients(Message.RecipientType.TO, tos); 415 } 416 if(ccs!=null){ 417 checkAddress(ccs,charset); 418 msg.setRecipients(Message.RecipientType.CC, ccs); 419 } 420 if(bccs!=null){ 421 checkAddress(bccs,charset); 422 msg.setRecipients(Message.RecipientType.BCC, bccs); 423 } 424 if(rts!=null){ 425 checkAddress(rts,charset); 426 msg.setReplyTo(rts); 427 } 428 if(fts!=null){ 429 checkAddress(fts,charset); 430 msg.setEnvelopeFrom(fts[0].toString()); 431 } 432 433 // Subject and headers 434 try { 435 msg.setSubject(MailUtil.encode(subject, charset)); 436 } catch (UnsupportedEncodingException e) { 437 throw new MessagingException("the encoding "+charset+" is not supported"); 438 } 439 msg.setHeader("X-Mailer", xmailer); 440 441 msg.setHeader("Date",getNow(timeZone)); 442 //msg.setSentDate(new Date()); 443 444 Multipart mp=null; 445 446 // Only HTML 447 if(plainText==null) { 448 if(ArrayUtil.isEmpty(attachmentz) && ArrayUtil.isEmpty(parts)){ 449 fillHTMLText(msg); 450 setHeaders(msg,headers); 451 return new MimeMessageAndSession(msg,sat); 452 } 453 mp = new MimeMultipart("related"); 454 mp.addBodyPart(getHTMLText()); 455 } 456 // only Plain 457 else if(htmlText==null) { 458 if(ArrayUtil.isEmpty(attachmentz) && ArrayUtil.isEmpty(parts)){ 459 fillPlainText(msg); 460 setHeaders(msg,headers); 461 return new MimeMessageAndSession(msg,sat); 462 } 463 mp = new MimeMultipart(); 464 mp.addBodyPart(getPlainText()); 465 } 466 // Plain and HTML 467 else { 468 mp=new MimeMultipart("alternative"); 469 mp.addBodyPart(getPlainText()); 470 mp.addBodyPart(getHTMLText()); 471 472 if(!ArrayUtil.isEmpty(attachmentz) || !ArrayUtil.isEmpty(parts)){ 473 MimeBodyPart content = new MimeBodyPart(); 474 content.setContent(mp); 475 mp = new MimeMultipart(); 476 mp.addBodyPart(content); 477 } 478 } 479 480 // parts 481 if(!ArrayUtil.isEmpty(parts)){ 482 Iterator<MailPart> it = parts.iterator(); 483 if(mp instanceof MimeMultipart) 484 ((MimeMultipart)mp).setSubType("alternative"); 485 while(it.hasNext()){ 486 mp.addBodyPart(toMimeBodyPart(it.next())); 487 } 488 } 489 490 // Attachments 491 if(!ArrayUtil.isEmpty(attachmentz)){ 492 for(int i=0;i<attachmentz.length;i++) { 493 mp.addBodyPart(toMimeBodyPart(mp,config,attachmentz[i])); 494 } 495 } 496 msg.setContent(mp); 497 setHeaders(msg,headers); 498 499 return new MimeMessageAndSession(msg,sat); 500 } 501 502 private static Properties createProperties(Config config, String hostName, int port, String username,String password, boolean tls, boolean ssl, int timeout) { 503 Properties props = (Properties) System.getProperties().clone(); 504 String strTimeout = Caster.toString(getTimeout(config,timeout)); 505 506 props.put("mail.smtp.host", hostName); 507 props.put("mail.smtp.timeout", strTimeout); 508 props.put("mail.smtp.connectiontimeout", strTimeout); 509 if(port>0){ 510 props.put("mail.smtp.port", Caster.toString(port)); 511 } 512 if(ssl) { 513 props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); 514 props.put("mail.smtp.socketFactory.port", Caster.toString(port)); 515 props.put("mail.smtp.socketFactory.fallback", "false"); 516 } 517 else { 518 props.put("mail.smtp.socketFactory.class", "javax.net.SocketFactory"); 519 props.remove("mail.smtp.socketFactory.port"); 520 props.remove("mail.smtp.socketFactory.fallback"); 521 } 522 if(!StringUtil.isEmpty(username)) { 523 props.put("mail.smtp.auth", "true"); 524 props.put("mail.smtp.starttls.enable",tls?"true":"false"); 525 526 props.put("mail.smtp.user", username); 527 props.put("mail.smtp.password", password); 528 props.put("password", password); 529 } 530 else { 531 props.put("mail.smtp.auth", "false"); 532 props.remove("mail.smtp.starttls.enable"); 533 534 props.remove("mail.smtp.user"); 535 props.remove("mail.smtp.password"); 536 props.remove("password"); 537 } 538 return props; 539 } 540 541 542 private static String hash(Properties props) { 543 Enumeration<?> e = props.propertyNames(); 544 java.util.List<String> names=new ArrayList<String>(); 545 String str; 546 while(e.hasMoreElements()){ 547 str=Caster.toString(e.nextElement(),null); 548 if(!StringUtil.isEmpty(str) && str.startsWith("mail.smtp.")) 549 names.add(str); 550 551 } 552 Collections.sort(names); 553 StringBuilder sb=new StringBuilder(); 554 Iterator<String> it = names.iterator(); 555 while(it.hasNext()){ 556 str=it.next(); 557 sb.append(str).append(':').append(props.getProperty(str)).append(';'); 558 } 559 str=sb.toString(); 560 return MD5.getDigestAsString(str,str); 561 562 } 563 564 private static void setHeaders(SMTPMessage msg, Map<String,String> headers) throws MessagingException { 565 Iterator<Entry<String, String>> it = headers.entrySet().iterator(); 566 Entry<String, String> e; 567 while(it.hasNext()) { 568 e = it.next(); 569 msg.setHeader(e.getKey(),e.getValue()); 570 } 571 } 572 573 private void checkAddress(InternetAddress[] ias,String charset) { // DIFF 23 574 for(int i=0;i<ias.length;i++) { 575 checkAddress(ias[i], charset); 576 } 577 } 578 private void checkAddress(InternetAddress ia,String charset) { // DIFF 23 579 try { 580 if(!StringUtil.isEmpty(ia.getPersonal())) { 581 String personal = MailUtil.encode(ia.getPersonal(), charset); 582 if(!personal.equals(ia.getPersonal())) 583 ia.setPersonal(personal); 584 } 585 } catch (UnsupportedEncodingException e) {} 586 } 587 588 /** 589 * @param plainText 590 */ 591 public void setPlainText(String plainText) { 592 this.plainText=plainText; 593 this.plainTextCharset=charset; 594 } 595 596 /** 597 * @param plainText 598 * @param plainTextCharset 599 */ 600 public void setPlainText(String plainText, String plainTextCharset) { 601 this.plainText=plainText; 602 this.plainTextCharset=plainTextCharset; 603 } 604 605 /** 606 * @param htmlText 607 */ 608 public void setHTMLText(String htmlText) { 609 this.htmlText=htmlText; 610 this.htmlTextCharset=charset; 611 } 612 613 614 615 public boolean hasHTMLText() { 616 return htmlText!=null; 617 } 618 619 public boolean hasPlainText() { 620 return plainText!=null; 621 } 622 623 /** 624 * @param htmlText 625 * @param htmlTextCharset 626 */ 627 public void setHTMLText(String htmlText, String htmlTextCharset) { 628 this.htmlText=htmlText; 629 this.htmlTextCharset=htmlTextCharset; 630 } 631 632 public void addAttachment(URL url) { 633 Attachment mbp = new Attachment(url); 634 attachmentz=add(attachmentz, mbp); 635 } 636 637 public void addAttachment(Resource resource, String type, String disposition, String contentID,boolean removeAfterSend) { 638 Attachment att = new Attachment(resource, type, disposition, contentID,removeAfterSend); 639 attachmentz=add(attachmentz, att); 640 } 641 642 public MimeBodyPart toMimeBodyPart(Multipart mp, lucee.runtime.config.Config config,Attachment att) throws MessagingException { 643 644 MimeBodyPart mbp = new MimeBodyPart(); 645 646 // set Data Source 647 String strRes = att.getAbsolutePath(); 648 if(!StringUtil.isEmpty(strRes)){ 649 650 mbp.setDataHandler(new DataHandler(new ResourceDataSource(config.getResource(strRes)))); 651 } 652 else mbp.setDataHandler(new DataHandler(new URLDataSource2(att.getURL()))); 653 654 mbp.setFileName(att.getFileName()); 655 if(!StringUtil.isEmpty(att.getType())) mbp.setHeader("Content-Type", att.getType()); 656 if(!StringUtil.isEmpty(att.getDisposition())){ 657 mbp.setDisposition(att.getDisposition()); 658 /*if(mp instanceof MimeMultipart) { 659 ((MimeMultipart)mp).setSubType("related"); 660 }*/ 661 662 } 663 if(!StringUtil.isEmpty(att.getContentID()))mbp.setContentID(att.getContentID()); 664 665 return mbp; 666 } 667 668 /** 669 * @param file 670 * @throws MessagingException 671 * @throws FileNotFoundException 672 */ 673 public void addAttachment(Resource file) throws MessagingException { 674 addAttachment(file,null,null,null,false); 675 } 676 677 678 679 680 public void send(ConfigWeb config, long sendTime) throws MailException { 681 if(ArrayUtil.isEmpty(config.getMailServers()) && ArrayUtil.isEmpty(host)) 682 throw new MailException("no SMTP Server defined"); 683 684 if(plainText==null && htmlText==null) 685 throw new MailException("you must define plaintext or htmltext"); 686 687 ///if(timeout<1)timeout=config.getMailTimeout()*1000; 688 if(spool==SPOOL_YES || (spool==SPOOL_UNDEFINED && config.isMailSpoolEnable())) { 689 config.getSpoolerEngine().add(new MailSpoolerTask(this, sendTime)); 690 } 691 else 692 _send(config); 693 } 694 695 696 public void _send(lucee.runtime.config.ConfigWeb config) throws MailException { 697 698 long start=System.nanoTime(); 699 long _timeout = getTimeout(config,timeout); 700 try { 701 702 Proxy.start(proxyData); 703 Log log = ((ConfigImpl)config).getLog("mail"); 704 // Server 705 Server[] servers = config.getMailServers(); 706 if(host!=null) { 707 int prt; 708 String usr,pwd; 709 ServerImpl[] nServers = new ServerImpl[host.length]; 710 for(int i=0;i<host.length;i++) { 711 usr=null;pwd=null; 712 prt=ServerImpl.DEFAULT_PORT; 713 714 if(port>0)prt=port; 715 if(!StringUtil.isEmpty(username)) { 716 usr=username; 717 pwd=password; 718 } 719 nServers[i]=toServerImpl(host[i],prt,usr,pwd,lifeTimespan,idleTimespan); 720 if(ssl==SSL_YES) nServers[i].setSSL(true); 721 if(tls==TLS_YES) nServers[i].setTLS(true); 722 723 } 724 servers=nServers; 725 } 726 if(servers.length==0) { 727 //return; 728 throw new MailException("no SMTP Server defined"); 729 } 730 731 boolean _ssl,_tls; 732 for(int i=0;i<servers.length;i++) { 733 734 Server server = servers[i]; 735 String _username=null,_password=""; 736 //int _port; 737 738 // username/password 739 740 if(server.hasAuthentication()) { 741 _username=server.getUsername(); 742 _password=server.getPassword(); 743 } 744 745 // tls 746 if(tls!=TLS_NONE)_tls=tls==TLS_YES; 747 else _tls=((ServerImpl)server).isTLS(); 748 749 // ssl 750 if(ssl!=SSL_NONE)_ssl=ssl==SSL_YES; 751 else _ssl=((ServerImpl)server).isSSL(); 752 753 754 MimeMessageAndSession msgSess; 755 boolean recyleConnection=((ServerImpl)server).reuseConnections(); 756 {//synchronized(LOCK) { no longer necessary we have a proxy lock now 757 try { 758 759 760 761 msgSess = createMimeMessage(config,server.getHostName(),server.getPort(),_username,_password, 762 ((ServerImpl)server) .getLifeTimeSpan(),((ServerImpl)server).getIdleTimeSpan(), 763 _tls,_ssl,!recyleConnection); 764 765 } catch (MessagingException e) { 766 // listener 767 listener(config,server,log,e,System.nanoTime()-start); 768 MailException me = new MailException(e.getMessage()); 769 me.setStackTrace(e.getStackTrace()); 770 throw me; 771 } 772 773 774 try { 775 SerializableObject lock = new SerializableObject(); 776 SMTPSender sender=new SMTPSender(lock,msgSess,server.getHostName(),server.getPort(),_username,_password,recyleConnection); 777 sender.start(); 778 SystemUtil.wait(lock, _timeout); 779 780 if(!sender.isSent()) { 781 Throwable t = sender.getThrowable(); 782 if(t!=null) throw Caster.toPageException(t); 783 784 // stop when still running 785 try{ 786 if(sender.isAlive())sender.stop(); 787 } 788 catch(Throwable t2){ 789 ExceptionUtil.rethrowIfNecessary(t2); 790 } 791 792 // after thread is stopped check sent flag again 793 if(!sender.isSent()){ 794 throw new MessagingException("timeout occurred after "+(_timeout/1000)+" seconds while sending mail message"); 795 } 796 } 797 clean(config,attachmentz); 798 799 listener(config,server,log,null,System.nanoTime()-start); 800 break; 801 } 802 catch (Exception e) {e.printStackTrace(); 803 if(i+1==servers.length) { 804 805 listener(config,server,log,e,System.nanoTime()-start); 806 MailException me = new MailException(server.getHostName()+" "+ExceptionUtil.getStacktrace(e, true)+":"+i); 807 me.setStackTrace(e.getStackTrace()); 808 809 throw me; 810 } 811 } 812 } 813 } 814 } 815 finally { 816 Proxy.end(); 817 } 818 } 819 820 private void listener(ConfigWeb config,Server server, Log log, Exception e, long exe) { 821 if(e==null) log.info("mail","mail sent (subject:"+subject+"from:"+toString(from)+"; to:"+toString(tos)+"; cc:"+toString(ccs)+"; bcc:"+toString(bccs)+"; ft:"+toString(fts)+"; rt:"+toString(rts)+")"); 822 else LogUtil.log(log,Log.LEVEL_ERROR,"mail",e); 823 824 // listener 825 826 Map<String,Object> props=new HashMap<String,Object>(); 827 props.put("attachments", this.attachmentz); 828 props.put("bccs", this.bccs); 829 props.put("ccs", this.ccs); 830 props.put("charset", this.charset); 831 props.put("from", this.from); 832 props.put("fts", this.fts); 833 props.put("headers", this.headers); 834 props.put("host", server.getHostName()); 835 props.put("htmlText", this.htmlText); 836 props.put("htmlTextCharset", this.htmlTextCharset); 837 props.put("parts", this.parts); 838 props.put("password", this.password); 839 props.put("plainText", this.plainText); 840 props.put("plainTextCharset", this.plainTextCharset); 841 props.put("port", server.getPort()); 842 props.put("proxyData", this.proxyData); 843 props.put("rts", this.rts); 844 props.put("subject", this.subject); 845 props.put("timeout", getTimeout(config,timeout)); 846 props.put("timezone", this.timeZone); 847 props.put("tos", this.tos); 848 props.put("username", this.username); 849 props.put("xmailer", this.xmailer); 850 ((ConfigWebImpl)config).getActionMonitorCollector() 851 .log(config, "mail", "Mail", exe, props); 852 853 } 854 855 856 private static String toString(InternetAddress... ias) { 857 if(ArrayUtil.isEmpty(ias)) return ""; 858 859 StringBuilder sb=new StringBuilder(); 860 for(int i=0;i<ias.length;i++){ 861 if(sb.length()>0)sb.append(", "); 862 sb.append(ias[i].toString()); 863 } 864 return sb.toString(); 865 } 866 867 868 private static long getTimeout(Config config, int timeout) { 869 return timeout>0?timeout:config.getMailTimeout()*1000L; 870 } 871 872 873 // remove all atttachements that are marked to remove 874 private static void clean(Config config, Attachment[] attachmentz) { 875 if(attachmentz!=null)for(int i=0;i<attachmentz.length;i++){ 876 if(attachmentz[i].isRemoveAfterSend()){ 877 Resource res = config.getResource(attachmentz[i].getAbsolutePath()); 878 ResourceUtil.removeEL(res,true); 879 } 880 } 881 } 882 883 private MimeBodyPart getHTMLText() throws MessagingException { 884 MimeBodyPart html = new MimeBodyPart(); 885 fillHTMLText(html); 886 return html; 887 } 888 889 private void fillHTMLText(MimePart mp) throws MessagingException { 890 mp.setDataHandler(new DataHandler(new StringDataSource(htmlText,TEXT_HTML ,htmlTextCharset,76))); 891 //mp.setHeader("Content-Transfer-Encoding", "7bit"); 892 mp.setHeader("Content-Type", TEXT_HTML+"; charset="+htmlTextCharset); 893 } 894 895 private MimeBodyPart getPlainText() throws MessagingException { 896 MimeBodyPart plain = new MimeBodyPart(); 897 fillPlainText(plain); 898 return plain; 899 } 900 private void fillPlainText(MimePart mp) throws MessagingException { 901 mp.setDataHandler(new DataHandler(new StringDataSource(plainText,TEXT_PLAIN ,plainTextCharset,980))); 902 //mp.setHeader("Content-Transfer-Encoding", "7bit"); 903 mp.setHeader("Content-Type", TEXT_PLAIN+"; charset="+plainTextCharset); 904 } 905 private BodyPart toMimeBodyPart(MailPart part) throws MessagingException { 906 MimeBodyPart mbp = new MimeBodyPart(); 907 mbp.setDataHandler(new DataHandler(new StringDataSource(part.getBody(),part.getType() ,part.getCharset(),980))); 908 //mbp.setHeader("Content-Transfer-Encoding", "7bit"); 909 //mbp.setHeader("Content-Type", TEXT_PLAIN+"; charset="+plainTextCharset); 910 return mbp; 911 } 912 913 /** 914 * @return the proxyData 915 */ 916 public ProxyData getProxyData() { 917 return proxyData; 918 } 919 920 /** 921 * @param proxyData the proxyData to set 922 */ 923 public void setProxyData(ProxyData proxyData) { 924 this.proxyData = proxyData; 925 } 926 927 /** 928 * @param ssl the ssl to set 929 */ 930 public void setSSL(boolean ssl) { 931 this.ssl = ssl?SSL_YES:SSL_NO; 932 } 933 934 /** 935 * @param tls the tls to set 936 */ 937 public void setTLS(boolean tls) { 938 this.tls = tls?TLS_YES:TLS_NO; 939 } 940 941 /** 942 * @return the subject 943 */ 944 public String getSubject() { 945 return subject; 946 } 947 948 /** 949 * @return the from 950 */ 951 public InternetAddress getFrom() { 952 return from; 953 } 954 955 /** 956 * @return the tos 957 */ 958 public InternetAddress[] getTos() { 959 return tos; 960 } 961 962 /** 963 * @return the bccs 964 */ 965 public InternetAddress[] getBccs() { 966 return bccs; 967 } 968 969 /** 970 * @return the ccs 971 */ 972 public InternetAddress[] getCcs() { 973 return ccs; 974 } 975 976 public void setPart(MailPart part) { 977 if(parts==null) parts=new ArrayList<MailPart>(); 978 parts.add(part); 979 } 980 981 982 public void setTimeZone(TimeZone timeZone) { 983 this.timeZone=timeZone; 984 } 985}