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}