KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > mail > smtp > SMTPTransport


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the "License"). You may not use this file except
5  * in compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * glassfish/bootstrap/legal/CDDLv1.0.txt or
9  * https://glassfish.dev.java.net/public/CDDLv1.0.html.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * HEADER in each file and include the License file at
15  * glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable,
16  * add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your
18  * own identifying information: Portions Copyright [yyyy]
19  * [name of copyright owner]
20  */

21
22 /*
23  * @(#)SMTPTransport.java 1.80 06/02/27
24  *
25  * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
26  */

27
28 package com.sun.mail.smtp;
29
30 import java.io.*;
31 import java.net.*;
32 import java.util.*;
33
34 import javax.mail.*;
35 import javax.mail.event.*;
36 import javax.mail.internet.*;
37
38 import com.sun.mail.util.*;
39
40 /**
41  * This class implements the Transport abstract class using SMTP for
42  * message submission and transport.
43  *
44  * See the <a HREF="package-summary.html">com.sun.mail.smtp</a> package
45  * documentation for further information on the SMTP protocol provider. <p>
46  *
47  * @author Max Spivak
48  * @author Bill Shannon
49  * @author Dean Gibson (DIGEST-MD5 authentication)
50  * @version 1.80, 06/02/27
51  *
52  * @see javax.mail.event.ConnectionEvent
53  * @see javax.mail.event.TransportEvent
54  */

55
56 public class SMTPTransport extends Transport {
57
58     private String JavaDoc name = "smtp"; // Name of this protocol
59
private int defaultPort = 25; // default SMTP port
60
private boolean isSSL = false; // use SSL?
61

62     // Following fields valid only during the sendMessage method.
63
private MimeMessage message; // Message to be sent
64
private Address[] addresses; // Addresses to which to send the msg
65
// Valid sent, valid unsent and invalid addresses
66
private Address[] validSentAddr, validUnsentAddr, invalidAddr;
67     // Did we send the message even though some addresses were invalid?
68
boolean sendPartiallyFailed = false;
69     // If so, here's an exception we need to throw
70
MessagingException exception;
71
72     // Map of SMTP service extensions supported by server, if EHLO used.
73
private Hashtable extMap;
74
75     private boolean quitWait = false; // true if we should wait
76
private String JavaDoc saslRealm = UNKNOWN;
77
78     private boolean reportSuccess; // throw an exception even on success
79
private boolean useStartTLS; // use STARTTLS command
80
private boolean useRset; // use RSET instead of NOOP
81

82     private PrintStream out; // debug output stream
83
private String JavaDoc localHostName; // our own host name
84
private String JavaDoc lastServerResponse; // last SMTP response
85
private int lastReturnCode; // last SMTP return code
86

87     /** Headers that should not be included when sending */
88     private static final String JavaDoc[] ignoreList = { "Bcc", "Content-Length" };
89     private static final byte[] CRLF = { (byte)'\r', (byte)'\n' };
90     private static final String JavaDoc UNKNOWN = "UNKNOWN"; // place holder
91

92     /**
93      * Constructor that takes a Session object and a URLName
94      * that represents a specific SMTP server.
95      */

96     public SMTPTransport(Session session, URLName urlname) {
97     this(session, urlname, "smtp", 25, false);
98     }
99
100     /**
101      * Constructor used by this class and by SMTPSSLTransport subclass.
102      */

103     protected SMTPTransport(Session session, URLName urlname,
104                 String JavaDoc name, int defaultPort, boolean isSSL) {
105     super(session, urlname);
106     if (urlname != null)
107         name = urlname.getProtocol();
108     this.name = name;
109     this.defaultPort = defaultPort;
110     this.isSSL = isSSL;
111
112     out = session.getDebugOut();
113
114     // setting mail.smtp.quitwait to false causes us to not wait for the
115
// response from the QUIT command
116
String JavaDoc s = session.getProperty("mail." + name + ".quitwait");
117     quitWait = s == null || s.equalsIgnoreCase("true");
118
119     // mail.smtp.reportsuccess causes us to throw an exception on success
120
s = session.getProperty("mail." + name + ".reportsuccess");
121     reportSuccess = s != null && s.equalsIgnoreCase("true");
122
123     // mail.smtp.starttls.enable enables use of STARTTLS command
124
s = session.getProperty("mail." + name + ".starttls.enable");
125     useStartTLS = s != null && s.equalsIgnoreCase("true");
126
127     // mail.smtp.userset causes us to use RSET instead of NOOP
128
// for isConnected
129
s = session.getProperty("mail." + name + ".userset");
130     useRset = s != null && s.equalsIgnoreCase("true");
131     }
132
133     /**
134      * Get the name of the local host, for use in the EHLO and HELO commands.
135      * The property mail.smtp.localhost overrides mail.smtp.localaddress,
136      * which overrides what InetAddress would tell us.
137      */

138     public String JavaDoc getLocalHost() {
139     try {
140         // get our hostname and cache it for future use
141
if (localHostName == null || localHostName.length() <= 0)
142         localHostName =
143             session.getProperty("mail." + name + ".localhost");
144         if (localHostName == null || localHostName.length() <= 0)
145         localHostName =
146             session.getProperty("mail." + name + ".localaddress");
147         if (localHostName == null || localHostName.length() <= 0) {
148         InetAddress localHost = InetAddress.getLocalHost();
149         localHostName = localHost.getHostName();
150         // if we can't get our name, use local address literal
151
if (localHostName == null)
152             // XXX - not correct for IPv6
153
localHostName = "[" + localHost.getHostAddress() + "]";
154         }
155     } catch (UnknownHostException uhex) {
156     }
157     return localHostName;
158     }
159
160     /**
161      * Set the name of the local host, for use in the EHLO and HELO commands.
162      *
163      * @since JavaMail 1.3.1
164      */

165     public void setLocalHost(String JavaDoc localhost) {
166     localHostName = localhost;
167     }
168
169     /**
170      * Start the SMTP protocol on the given socket, which was already
171      * connected by the caller. Useful for implementing the SMTP ATRN
172      * command (RFC 2645) where an existing connection is used when
173      * the server reverses roles and becomes the client.
174      *
175      * @since JavaMail 1.3.3
176      */

177     public synchronized void connect(Socket socket) throws MessagingException {
178     serverSocket = socket;
179     super.connect();
180     }
181
182     /**
183      * Gets the SASL realm to be used for DIGEST-MD5 authentication.
184      *
185      * @return the name of the realm to use for SASL authentication.
186      *
187      * @since JavaMail 1.3.1
188      */

189     public String JavaDoc getSASLRealm() {
190     if (saslRealm == UNKNOWN) {
191         saslRealm = session.getProperty("mail." + name + ".sasl.realm");
192         if (saslRealm == null) // try old name
193
saslRealm = session.getProperty("mail." + name + ".saslrealm");
194     }
195     return saslRealm;
196     }
197
198     /**
199      * Sets the SASL realm to be used for DIGEST-MD5 authentication.
200      *
201      * @param saslRealm the name of the realm to use for
202      * SASL authentication.
203      *
204      * @since JavaMail 1.3.1
205      */

206     public void setSASLRealm(String JavaDoc saslRealm) {
207     this.saslRealm = saslRealm;
208     }
209
210     /**
211      * Should we report even successful sends by throwing an exception?
212      * If so, a <code>SendFailedException</code> will always be thrown and
213      * an {@link com.sun.mail.smtp.SMTPAddressSucceededException
214      * SMTPAddressSucceededException} will be included in the exception
215      * chain for each successful address, along with the usual
216      * {@link com.sun.mail.smtp.SMTPAddressFailedException
217      * SMTPAddressFailedException} for each unsuccessful address.
218      *
219      * @return true if an exception will be thrown on successful sends.
220      *
221      * @since JavaMail 1.3.2
222      */

223     public boolean getReportSuccess() {
224     return reportSuccess;
225     }
226
227     /**
228      * Set whether successful sends should be reported by throwing
229      * an exception.
230      *
231      * @param reportSuccess should we throw an exception on success?
232      *
233      * @since JavaMail 1.3.2
234      */

235     public void setReportSuccess(boolean reportSuccess) {
236     this.reportSuccess = reportSuccess;
237     }
238
239     /**
240      * Should we use the STARTTLS command to secure the connection
241      * if the server supports it?
242      *
243      * @return true if the STARTTLS command will be used
244      *
245      * @since JavaMail 1.3.2
246      */

247     public boolean getStartTLS() {
248     return useStartTLS;
249     }
250
251     /**
252      * Set whether the STARTTLS command should be used.
253      *
254      * @param useStartTLS should we use the STARTTLS command?
255      *
256      * @since JavaMail 1.3.2
257      */

258     public void setStartTLS(boolean useStartTLS) {
259     this.useStartTLS = useStartTLS;
260     }
261
262     /**
263      * Should we use the RSET command instead of the NOOP command
264      * in the @{link #isConnected isConnected} method?
265      *
266      * @return true if RSET will be used
267      *
268      * @since JavaMail 1.4
269      */

270     public boolean getUseRset() {
271     return useRset;
272     }
273
274     /**
275      * Set whether the RSET command should be used instead of the
276      * NOOP command in the @{link #isConnected isConnected} method.
277      *
278      * @param useRset should we use the RSET command?
279      *
280      * @since JavaMail 1.4
281      */

282     public void setUseRset(boolean useRset) {
283     this.useRset = useRset;
284     }
285
286     /**
287      * Return the last response we got from the server.
288      * A failed send is often followed by an RSET command,
289      * but the response from the RSET command is not saved.
290      * Instead, this returns the response from the command
291      * before the RSET command.
292      *
293      * @return last response from server
294      *
295      * @since JavaMail 1.3.2
296      */

297     public String JavaDoc getLastServerResponse() {
298     return lastServerResponse;
299     }
300
301     private DigestMD5 md5support;
302
303     private synchronized DigestMD5 getMD5() {
304     if (md5support == null)
305         md5support = new DigestMD5(debug ? out : null);
306     return md5support;
307     }
308
309     /**
310      * Performs the actual protocol-specific connection attempt.
311      * Will attempt to connect to "localhost" if the host was null. <p>
312      *
313      * Unless mail.smtp.ehlo is set to false, we'll try to identify
314      * ourselves using the ESMTP command EHLO.
315      *
316      * If mail.smtp.auth is set to true, we insist on having a username
317      * and password, and will try to authenticate ourselves if the server
318      * supports the AUTH extension (RFC 2554).
319      *
320      * @param host the name of the host to connect to
321      * @param port the port to use (-1 means use default port)
322      * @param user the name of the user to login as
323      * @param passwd the user's password
324      * @return true if connection successful, false if authentication failed
325      * @exception MessagingException for non-authentication failures
326      */

327     protected boolean protocolConnect(String JavaDoc host, int port, String JavaDoc user,
328                   String JavaDoc passwd) throws MessagingException {
329     // setting mail.smtp.ehlo to false disables attempts to use EHLO
330
String JavaDoc ehloStr = session.getProperty("mail." + name + ".ehlo");
331     boolean useEhlo = ehloStr == null || !ehloStr.equalsIgnoreCase("false");
332     // setting mail.smtp.auth to true enables attempts to use AUTH
333
String JavaDoc authStr = session.getProperty("mail." + name + ".auth");
334     boolean useAuth = authStr != null && authStr.equalsIgnoreCase("true");
335     DigestMD5 md5;
336     if (debug)
337         out.println("DEBUG SMTP: useEhlo " + useEhlo +
338                 ", useAuth " + useAuth);
339
340     /*
341      * If mail.smtp.auth is set, make sure we have a valid username
342      * and password, even if we might not end up using it (e.g.,
343      * because the server doesn't support ESMTP or doesn't support
344      * the AUTH extension).
345      */

346     if (useAuth && (user == null || passwd == null))
347         return false;
348
349     /*
350      * If port is not specified, set it to value of mail.smtp.port
351          * property if it exists, otherwise default to 25.
352      */

353         if (port == -1) {
354         String JavaDoc portstring = session.getProperty("mail." + name + ".port");
355         if (portstring != null) {
356         port = Integer.parseInt(portstring);
357         } else {
358         port = defaultPort;
359         }
360     }
361
362     if (host == null || host.length() == 0)
363         host = "localhost";
364
365     boolean succeed = false;
366
367     if (serverSocket != null)
368         openServer(); // only happens from connect(socket)
369
else
370         openServer(host, port);
371
372     if (useEhlo)
373         succeed = ehlo(getLocalHost());
374     if (!succeed)
375         helo(getLocalHost());
376
377     if (useStartTLS && supportsExtension("STARTTLS")) {
378         startTLS();
379         /*
380          * Have to issue another EHLO to update list of extensions
381          * supported, especially authentication mechanisms.
382          * Don't know if this could ever fail, but we ignore failure.
383          */

384         ehlo(getLocalHost());
385     }
386
387     if (useAuth &&
388           (supportsExtension("AUTH") || supportsExtension("AUTH=LOGIN"))) {
389         if (debug) {
390         out.println("DEBUG SMTP: Attempt to authenticate");
391         if (!supportsAuthentication("LOGIN") &&
392             supportsExtension("AUTH=LOGIN"))
393             out.println("DEBUG SMTP: use AUTH=LOGIN hack");
394         }
395         // if authentication fails, close connection and return false
396
if (supportsAuthentication("LOGIN") ||
397             supportsExtension("AUTH=LOGIN")) {
398         // XXX - could use "initial response" capability
399
int resp = simpleCommand("AUTH LOGIN");
400
401         /*
402          * A 530 response indicates that the server wants us to
403          * issue a STARTTLS command first. Do that and try again.
404          */

405         if (resp == 530) {
406             startTLS();
407             resp = simpleCommand("AUTH LOGIN");
408         }
409
410         /*
411          * Wrap a BASE64Encoder around a ByteArrayOutputstream
412          * to craft b64 encoded username and password strings.
413          *
414          * Also note that unlike the B64 definition in MIME, CRLFs
415          * should *not* be inserted during the encoding process.
416          * So I use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the
417          * bytesPerLine, which should be sufficiently large!
418          */

419         try {
420             ByteArrayOutputStream bos = new ByteArrayOutputStream();
421             OutputStream b64os =
422                 new BASE64EncoderStream(bos, Integer.MAX_VALUE);
423
424             if (resp == 334) {
425             // obtain b64 encoded bytes
426
b64os.write(ASCIIUtility.getBytes(user));
427             b64os.flush(); // complete the encoding
428

429             // send username
430
resp = simpleCommand(bos.toByteArray());
431             bos.reset(); // reset buffer
432
}
433             if (resp == 334) {
434             // obtain b64 encoded bytes
435
b64os.write(ASCIIUtility.getBytes(passwd));
436             b64os.flush(); // complete the encoding
437

438             // send passwd
439
resp = simpleCommand(bos.toByteArray());
440             bos.reset(); // reset buffer
441
}
442         } catch (IOException ex) { // should never happen, ignore
443
} finally {
444             if (resp != 235) {
445             closeConnection();
446             return false;
447             }
448         }
449         } else if (supportsAuthentication("PLAIN")) {
450         // XXX - could use "initial response" capability
451
int resp = simpleCommand("AUTH PLAIN");
452         try {
453             ByteArrayOutputStream bos = new ByteArrayOutputStream();
454             OutputStream b64os =
455                 new BASE64EncoderStream(bos, Integer.MAX_VALUE);
456             if (resp == 334) {
457             // send "<NUL>user<NUL>passwd"
458
// XXX - we don't send an authorization identity
459
b64os.write(0);
460             b64os.write(ASCIIUtility.getBytes(user));
461             b64os.write(0);
462             b64os.write(ASCIIUtility.getBytes(passwd));
463             b64os.flush(); // complete the encoding
464

465             // send username
466
resp = simpleCommand(bos.toByteArray());
467             }
468         } catch (IOException ex) { // should never happen, ignore
469
} finally {
470             if (resp != 235) {
471             closeConnection();
472             return false;
473             }
474         }
475         } else if (supportsAuthentication("DIGEST-MD5") &&
476             (md5 = getMD5()) != null) {
477         int resp = simpleCommand("AUTH DIGEST-MD5");
478         try {
479             if (resp == 334) {
480             byte[] b = md5.authClient(host, user, passwd,
481                         getSASLRealm(), lastServerResponse);
482             resp = simpleCommand(b);
483             if (resp == 334) { // client authenticated by server
484
if (!md5.authServer(lastServerResponse)) {
485                 // server NOT authenticated by client !!!
486
resp = -1;
487                 } else {
488                 // send null response
489
resp = simpleCommand(new byte[0]);
490                 }
491             }
492             }
493         } catch (Exception JavaDoc ex) { // should never happen, ignore
494
if (debug)
495             out.println("DEBUG SMTP: DIGEST-MD5: " + ex);
496         } finally {
497             if (resp != 235) {
498             closeConnection();
499             return false;
500             }
501         }
502         }
503     }
504
505     // we connected correctly
506
return true;
507     }
508
509
510     /**
511      * Send the Message to the specified list of addresses.<p>
512      *
513      * If all the <code>addresses</code> succeed the SMTP check
514      * using the <code>RCPT TO:</code> command, we attempt to send the message.
515      * A TransportEvent of type MESSAGE_DELIVERED is fired indicating the
516      * successful submission of a message to the SMTP host.<p>
517      *
518      * If some of the <code>addresses</code> fail the SMTP check,
519      * and the <code>mail.stmp.sendpartial</code> property is not set,
520      * sending is aborted. The TransportEvent of type MESSAGE_NOT_DELIVERED
521      * is fired containing the valid and invalid addresses. The
522      * SendFailedException is also thrown. <p>
523      *
524      * If some of the <code>addresses</code> fail the SMTP check,
525      * and the <code>mail.stmp.sendpartial</code> property is set to true,
526      * the message is sent. The TransportEvent of type
527      * MESSAGE_PARTIALLY_DELIVERED
528      * is fired containing the valid and invalid addresses. The
529      * SMTPSendFailedException is also thrown. <p>
530      *
531      * MessagingException is thrown if the message can't write out
532      * an RFC822-compliant stream using its <code>writeTo</code> method. <p>
533      *
534      * @param message The MimeMessage to be sent
535      * @param addresses List of addresses to send this message to
536      * @see javax.mail.event.TransportEvent
537      * @exception SMTPSendFailedException if the send failed because of
538      * an SMTP command error
539      * @exception SendFailedException if the send failed because of
540      * invalid addresses.
541      * @exception MessagingException if the connection is dead
542      * or not in the connected state or if the message is
543      * not a MimeMessage.
544      */

545     public synchronized void sendMessage(Message message, Address[] addresses)
546             throws MessagingException, SendFailedException {
547
548     checkConnected();
549
550     // check if the message is a valid MIME/RFC822 message and that
551
// it has all valid InternetAddresses; fail if not
552
if (!(message instanceof MimeMessage)) {
553         if (debug)
554         out.println("DEBUG SMTP: Can only send RFC822 msgs");
555         throw new MessagingException("SMTP can only send RFC822 messages");
556     }
557     for (int i = 0; i < addresses.length; i++) {
558         if (!(addresses[i] instanceof InternetAddress)) {
559         throw new MessagingException(addresses[i] +
560                          " is not an InternetAddress");
561         }
562     }
563
564     this.message = (MimeMessage)message;
565     this.addresses = addresses;
566     validUnsentAddr = addresses; // until we know better
567
expandGroups();
568
569     boolean use8bit = false;
570     if (message instanceof SMTPMessage)
571         use8bit = ((SMTPMessage)message).getAllow8bitMIME();
572     if (!use8bit) {
573         String JavaDoc ebStr =
574             session.getProperty("mail." + name + ".allow8bitmime");
575         use8bit = ebStr != null && ebStr.equalsIgnoreCase("true");
576     }
577     if (debug)
578         out.println("DEBUG SMTP: use8bit " + use8bit);
579     if (use8bit && supportsExtension("8BITMIME"))
580         convertTo8Bit(this.message);
581
582     try {
583         mailFrom();
584         rcptTo();
585         this.message.writeTo(data(), ignoreList);
586         finishData();
587         if (sendPartiallyFailed) {
588         // throw the exception,
589
// fire TransportEvent.MESSAGE_PARTIALLY_DELIVERED event
590
if (debug)
591             out.println("DEBUG SMTP: Sending partially failed " +
592             "because of invalid destination addresses");
593         notifyTransportListeners(
594             TransportEvent.MESSAGE_PARTIALLY_DELIVERED,
595             validSentAddr, validUnsentAddr, invalidAddr,
596             this.message);
597
598         throw new SMTPSendFailedException(".", lastReturnCode,
599                 lastServerResponse, exception,
600                 validSentAddr, validUnsentAddr, invalidAddr);
601         }
602         notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED,
603                      validSentAddr, validUnsentAddr,
604                      invalidAddr, this.message);
605     } catch (MessagingException mex) {
606         if (debug)
607         mex.printStackTrace(out);
608         notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
609                      validSentAddr, validUnsentAddr,
610                      invalidAddr, this.message);
611
612         throw mex;
613     } catch (IOException ex) {
614         if (debug)
615         ex.printStackTrace(out);
616         // if we catch an IOException, it means that we want
617
// to drop the connection so that the message isn't sent
618
try {
619         closeConnection();
620         } catch (MessagingException mex) { /* ignore it */ }
621         notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
622                      validSentAddr, validUnsentAddr,
623                      invalidAddr, this.message);
624
625         throw new MessagingException("IOException while sending message",
626                      ex);
627     } finally {
628         // no reason to keep this data around
629
validSentAddr = validUnsentAddr = invalidAddr = null;
630         this.addresses = null;
631         this.message = null;
632         this.exception = null;
633         sendPartiallyFailed = false;
634     }
635     }
636
637     /** Close the server and terminate its connection */
638     public synchronized void close() throws MessagingException {
639     if (!super.isConnected()) // Already closed.
640
return;
641     try {
642         if (serverSocket != null) {
643         sendCommand("QUIT");
644         if (quitWait) {
645             int resp = readServerResponse();
646             if (resp != 221 && resp != -1)
647             out.println("DEBUG SMTP: QUIT failed with " + resp);
648         }
649         }
650     } finally {
651         closeConnection();
652     }
653     }
654
655     private void closeConnection() throws MessagingException {
656     try {
657         if (serverSocket != null)
658         serverSocket.close();
659     } catch (IOException ioex) { // shouldn't happen
660
throw new MessagingException("Server Close Failed", ioex);
661     } finally {
662         serverSocket = null;
663         serverOutput = null;
664         serverInput = null;
665         lineInputStream = null;
666         if (super.isConnected()) // only notify if already connected
667
super.close();
668     }
669     }
670
671     /**
672      * Check whether the transport is connected. Override superclass
673      * method, to actually ping our server connection.
674      */

675     public synchronized boolean isConnected() {
676     if (!super.isConnected())
677         // if we haven't been connected at all, don't bother with NOOP
678
return false;
679
680     try {
681         // sendmail may respond slowly to NOOP after many requests
682
// so if mail.smtp.userset is set we use RSET instead of NOOP.
683
if (useRset)
684         sendCommand("RSET");
685         else
686         sendCommand("NOOP");
687         int resp = readServerResponse();
688
689         // NOOP should return 250 on success, however, SIMS 3.2 returns
690
// 200, so we work around it.
691
//
692
// Hotmail doesn't implement the NOOP command at all so assume
693
// any kind of response means we're still connected.
694
// That is, any response except 421, which means the server
695
// is shutting down the connection.
696
//
697
if (resp >= 0 && resp != 421) {
698         return true;
699         } else {
700         try {
701             closeConnection();
702         } catch (MessagingException mex) { } // ignore it
703
return false;
704         }
705     } catch (Exception JavaDoc ex) {
706         try {
707         closeConnection();
708         } catch (MessagingException mex) { } // ignore it
709
return false;
710     }
711     }
712
713     /**
714      * Expand any group addresses.
715      */

716     private void expandGroups() {
717     Vector groups = null;
718     for (int i = 0; i < addresses.length; i++) {
719         InternetAddress a = (InternetAddress)addresses[i];
720         if (a.isGroup()) {
721         if (groups == null) {
722             // first group, catch up with where we are
723
groups = new Vector();
724             for (int k = 0; k < i; k++)
725             groups.addElement(addresses[k]);
726         }
727         // parse it and add each individual address
728
try {
729             InternetAddress[] ia = a.getGroup(true);
730             if (ia != null) {
731             for (int j = 0; j < ia.length; j++)
732                 groups.addElement(ia[j]);
733             } else
734             groups.addElement(a);
735         } catch (ParseException pex) {
736             // parse failed, add the whole thing
737
groups.addElement(a);
738         }
739         } else {
740         // if we've started accumulating a list, add this to it
741
if (groups != null)
742             groups.addElement(a);
743         }
744     }
745
746     // if we have a new list, convert it back to an array
747
if (groups != null) {
748         InternetAddress[] newa = new InternetAddress[groups.size()];
749         groups.copyInto(newa);
750         addresses = newa;
751     }
752     }
753
754     /**
755      * If the Part is a text part and has a Content-Transfer-Encoding
756      * of "quoted-printable" or "base64", and it obeys the rules for
757      * "8bit" encoding, change the encoding to "8bit". If the part is
758      * a multipart, recursively process all its parts.
759      *
760      * XXX - This is really quite a hack.
761      */

762     private void convertTo8Bit(MimePart part) {
763     try {
764         if (part.isMimeType("text/*")) {
765         String JavaDoc enc = part.getEncoding();
766         if (enc.equalsIgnoreCase("quoted-printable") ||
767             enc.equalsIgnoreCase("base64")) {
768             InputStream is = part.getInputStream();
769             if (is8Bit(is))
770             part.setHeader("Content-Transfer-Encoding", "8bit");
771         }
772         } else if (part.isMimeType("multipart/*")) {
773         MimeMultipart mp = (MimeMultipart)part.getContent();
774         int count = mp.getCount();
775         for (int i = 0; i < count; i++)
776             convertTo8Bit((MimePart)mp.getBodyPart(i));
777         }
778     } catch (IOException ioex) {
779         // any exception causes us to give up
780
} catch (MessagingException mex) {
781         // any exception causes us to give up
782
}
783     }
784
785     /**
786      * Check whether the data in the given InputStream follows the
787      * rules for 8bit text. Lines have to be 998 characters or less
788      * and no NULs are allowed. CR and LF must occur in pairs but we
789      * don't check that because we assume this is text and we convert
790      * all CR/LF combinations into canonical CRLF later.
791      */

792     private boolean is8Bit(InputStream is) {
793     int b;
794     int linelen = 0;
795     boolean need8bit = false;
796     try {
797         while ((b = is.read()) >= 0) {
798         b &= 0xff;
799         if (b == '\r' || b == '\n')
800             linelen = 0;
801         else if (b == 0)
802             return false;
803         else {
804             linelen++;
805             if (linelen > 998) // 1000 - CRLF
806
return false;
807         }
808         if (b > 0x7f)
809             need8bit = true;
810         }
811     } catch (IOException ex) {
812         return false;
813     }
814     if (debug && need8bit)
815         out.println("DEBUG SMTP: found an 8bit part");
816     return need8bit;
817     }
818
819     protected void finalize() throws Throwable JavaDoc {
820     super.finalize();
821     try {
822         closeConnection();
823     } catch (MessagingException mex) { } // ignore it
824
}
825
826     ///////////////////// smtp stuff ///////////////////////
827
private BufferedInputStream serverInput;
828     private LineInputStream lineInputStream;
829     private OutputStream serverOutput;
830     private Socket serverSocket;
831
832     /////// smtp protocol //////
833

834     private void helo(String JavaDoc domain) throws MessagingException {
835     if (domain != null)
836         issueCommand("HELO " + domain, 250);
837     else
838         issueCommand("HELO", 250);
839     }
840
841     private boolean ehlo(String JavaDoc domain) throws MessagingException {
842     String JavaDoc cmd;
843     if (domain != null)
844         cmd = "EHLO " + domain;
845     else
846         cmd = "EHLO";
847     sendCommand(cmd);
848     int resp = readServerResponse();
849     if (resp == 250) {
850         // extract the supported service extensions
851
BufferedReader rd =
852         new BufferedReader(new StringReader(lastServerResponse));
853         String JavaDoc line;
854         extMap = new Hashtable();
855         try {
856         boolean first = true;
857         while ((line = rd.readLine()) != null) {
858             if (first) { // skip first line which is the greeting
859
first = false;
860             continue;
861             }
862             if (line.length() < 5)
863             continue; // shouldn't happen
864
line = line.substring(4); // skip response code
865
int i = line.indexOf(' ');
866             String JavaDoc arg = "";
867             if (i > 0) {
868             arg = line.substring(i + 1);
869             line = line.substring(0, i);
870             }
871             if (debug)
872             out.println("DEBUG SMTP: Found extension \"" +
873                         line + "\", arg \"" + arg + "\"");
874             extMap.put(line.toUpperCase(), arg);
875         }
876         } catch (IOException ex) { } // can't happen
877
}
878     return resp == 250;
879     }
880
881     /*
882      * Gets the sender's address in the following order:
883      * 0. SMTPMessage.getEnvelopeFrom()
884      * 1. mail.smtp.from property
885      * 2. From: header in the message
886      * 3. system username using the InternetAddress.getLocalAddress() method
887      */

888     private void mailFrom() throws MessagingException {
889     String JavaDoc from = null;
890     if (message instanceof SMTPMessage)
891         from = ((SMTPMessage)message).getEnvelopeFrom();
892     if (from == null || from.length() <= 0)
893         from = session.getProperty("mail." + name + ".from");
894     if (from == null || from.length() <= 0) {
895         Address[] fa;
896         Address me;
897         if (message != null && (fa = message.getFrom()) != null &&
898             fa.length > 0)
899         me = fa[0];
900         else
901         me = InternetAddress.getLocalAddress(session);
902
903         if (me != null)
904         from = ((InternetAddress)me).getAddress();
905         else
906         throw new MessagingException(
907                     "can't determine local email address");
908     }
909
910     String JavaDoc cmd = "MAIL FROM:" + normalizeAddress(from);
911
912     // request delivery status notification?
913
if (supportsExtension("DSN")) {
914         String JavaDoc ret = null;
915         if (message instanceof SMTPMessage)
916         ret = ((SMTPMessage)message).getDSNRet();
917         if (ret == null)
918         ret = session.getProperty("mail." + name + ".dsn.ret");
919         // XXX - check for legal syntax?
920
if (ret != null)
921         cmd += " RET=" + ret;
922     }
923
924     /*
925      * If an RFC 2554 submitter has been specified, and the server
926      * supports the AUTH extension, include the AUTH= element on
927      * the MAIL FROM command.
928      */

929     if (supportsExtension("AUTH")) {
930         String JavaDoc submitter = null;
931         if (message instanceof SMTPMessage)
932         submitter = ((SMTPMessage)message).getSubmitter();
933         if (submitter == null)
934         submitter = session.getProperty("mail." + name + ".submitter");
935         // XXX - check for legal syntax?
936
if (submitter != null) {
937         try {
938             String JavaDoc s = xtext(submitter);
939             cmd += " AUTH=" + s;
940         } catch (IllegalArgumentException JavaDoc ex) {
941             if (debug)
942             out.println("DEBUG SMTP: ignoring invalid submitter: " +
943                 submitter + ", Exception: " + ex);
944         }
945         }
946     }
947
948     /*
949      * Have any extensions to the MAIL command been specified?
950      */

951     String JavaDoc ext = null;
952     if (message instanceof SMTPMessage)
953         ext = ((SMTPMessage)message).getMailExtension();
954     if (ext == null)
955         ext = session.getProperty("mail." + name + ".mailextension");
956     if (ext != null && ext.length() > 0)
957         cmd += " " + ext;
958
959     issueSendCommand(cmd, 250);
960     }
961
962     /**
963      * Sends each address to the SMTP host and copies it either into
964      * the validSentAddr or invalidAddr arrays.
965      * Sets the <code>sendFailed</code>
966      * flag to true if any addresses failed.
967      */

968     /*
969      * success/failure/error possibilities from the RCPT command
970      * from rfc821, section 4.3
971      * S: 250, 251
972      * F: 550, 551, 552, 553, 450, 451, 452
973      * E: 500, 501, 503, 421
974      *
975      * and how we map the above error/failure conditions to valid/invalid
976      * address vectors that are reported in the thrown exception:
977      * invalid addr: 550, 501, 503, 551, 553
978      * valid addr: 552 (quota), 450, 451, 452 (quota), 421 (srvr abort)
979      */

980     private void rcptTo() throws MessagingException {
981     Vector valid = new Vector();
982     Vector validUnsent = new Vector();
983     Vector invalid = new Vector();
984     int retCode = -1;
985     MessagingException mex = null;
986     boolean sendFailed = false;
987     MessagingException sfex = null;
988     validSentAddr = validUnsentAddr = invalidAddr = null;
989     boolean sendPartial = false;
990     if (message instanceof SMTPMessage)
991         sendPartial = ((SMTPMessage)message).getSendPartial();
992     if (!sendPartial) {
993         String JavaDoc sp = session.getProperty("mail." + name + ".sendpartial");
994         sendPartial = sp != null && sp.equalsIgnoreCase("true");
995     }
996     if (debug && sendPartial)
997         out.println("DEBUG SMTP: sendPartial set");
998
999     boolean dsn = false;
1000    String JavaDoc notify = null;
1001    if (supportsExtension("DSN")) {
1002        if (message instanceof SMTPMessage)
1003        notify = ((SMTPMessage)message).getDSNNotify();
1004        if (notify == null)
1005        notify = session.getProperty("mail." + name + ".dsn.notify");
1006        // XXX - check for legal syntax?
1007
if (notify != null)
1008        dsn = true;
1009    }
1010
1011    // try the addresses one at a time
1012
for (int i = 0; i < addresses.length; i++) {
1013
1014        sfex = null;
1015        InternetAddress ia = (InternetAddress)addresses[i];
1016        String JavaDoc cmd = "RCPT TO:" + normalizeAddress(ia.getAddress());
1017        if (dsn)
1018        cmd += " NOTIFY=" + notify;
1019        // send the addresses to the SMTP server
1020
sendCommand(cmd);
1021        // check the server's response for address validity
1022
retCode = readServerResponse();
1023        switch (retCode) {
1024        case 250: case 251:
1025        valid.addElement(ia);
1026        if (!reportSuccess)
1027            break;
1028
1029        // user wants exception even when successful, including
1030
// details of the return code
1031

1032        // create and chain the exception
1033
sfex = new SMTPAddressSucceededException(ia, cmd, retCode,
1034                            lastServerResponse);
1035        if (mex == null)
1036            mex = sfex;
1037        else
1038            mex.setNextException(sfex);
1039        break;
1040
1041        case 550: case 553: case 503: case 551: case 501:
1042        // given address is invalid
1043
if (!sendPartial)
1044            sendFailed = true;
1045        invalid.addElement(ia);
1046        // create and chain the exception
1047
sfex = new SMTPAddressFailedException(ia, cmd, retCode,
1048                            lastServerResponse);
1049        if (mex == null)
1050            mex = sfex;
1051        else
1052            mex.setNextException(sfex);
1053        break;
1054
1055        case 552: case 450: case 451: case 452:
1056        // given address is valid
1057
if (!sendPartial)
1058            sendFailed = true;
1059        validUnsent.addElement(ia);
1060        // create and chain the exception
1061
sfex = new SMTPAddressFailedException(ia, cmd, retCode,
1062                            lastServerResponse);
1063        if (mex == null)
1064            mex = sfex;
1065        else
1066            mex.setNextException(sfex);
1067        break;
1068
1069        default:
1070        // handle remaining 4xy & 5xy codes
1071
if (retCode >= 400 && retCode <= 499) {
1072            // assume address is valid, although we don't really know
1073
validUnsent.addElement(ia);
1074        } else if (retCode >= 500 && retCode <= 599) {
1075            // assume address is invalid, although we don't really know
1076
invalid.addElement(ia);
1077        } else {
1078            // completely unexpected response, just give up
1079
if (debug)
1080            out.println("DEBUG SMTP: got response code " + retCode +
1081                ", with response: " + lastServerResponse);
1082            String JavaDoc _lsr = lastServerResponse; // else rset will nuke it
1083
int _lrc = lastReturnCode;
1084            if (serverSocket != null) // hasn't already been closed
1085
issueCommand("RSET", 250);
1086            lastServerResponse = _lsr; // restore, for get
1087
lastReturnCode = _lrc;
1088            throw new SMTPAddressFailedException(ia, cmd, retCode,
1089                                _lsr);
1090        }
1091        if (!sendPartial)
1092            sendFailed = true;
1093        // create and chain the exception
1094
sfex = new SMTPAddressFailedException(ia, cmd, retCode,
1095                            lastServerResponse);
1096        if (mex == null)
1097            mex = sfex;
1098        else
1099            mex.setNextException(sfex);
1100        break;
1101        }
1102    }
1103
1104    // if we're willing to send to a partial list, and we found no
1105
// valid addresses, that's complete failure
1106
if (sendPartial && valid.size() == 0)
1107        sendFailed = true;
1108
1109    // copy the vectors into appropriate arrays
1110
if (sendFailed) {
1111        // copy invalid addrs
1112
invalidAddr = new Address[invalid.size()];
1113        invalid.copyInto(invalidAddr);
1114
1115        // copy all valid addresses to validUnsent, since something failed
1116
validUnsentAddr = new Address[valid.size() + validUnsent.size()];
1117        int i = 0;
1118        for (int j = 0; j < valid.size(); j++)
1119        validUnsentAddr[i++] = (Address)valid.elementAt(j);
1120        for (int j = 0; j < validUnsent.size(); j++)
1121        validUnsentAddr[i++] = (Address)validUnsent.elementAt(j);
1122    } else if (reportSuccess || (sendPartial &&
1123            (invalid.size() > 0 || validUnsent.size() > 0))) {
1124        // we'll go on to send the message, but after sending we'll
1125
// throw an exception with this exception nested
1126
sendPartiallyFailed = true;
1127        exception = mex;
1128
1129        // copy invalid addrs
1130
invalidAddr = new Address[invalid.size()];
1131        invalid.copyInto(invalidAddr);
1132
1133        // copy valid unsent addresses to validUnsent
1134
validUnsentAddr = new Address[validUnsent.size()];
1135        validUnsent.copyInto(validUnsentAddr);
1136
1137        // copy valid addresses to validSent
1138
validSentAddr = new Address[valid.size()];
1139        valid.copyInto(validSentAddr);
1140    } else { // all addresses pass
1141
validSentAddr = addresses;
1142    }
1143
1144
1145    // print out the debug info
1146
if (debug) {
1147        if (validSentAddr != null && validSentAddr.length > 0) {
1148        out.println("DEBUG SMTP: Verified Addresses");
1149        for (int l = 0; l < validSentAddr.length; l++) {
1150            out.println("DEBUG SMTP: " + validSentAddr[l]);
1151        }
1152        }
1153        if (validUnsentAddr != null && validUnsentAddr.length > 0) {
1154        out.println("DEBUG SMTP: Valid Unsent Addresses");
1155        for (int j = 0; j < validUnsentAddr.length; j++) {
1156            out.println("DEBUG SMTP: " + validUnsentAddr[j]);
1157        }
1158        }
1159        if (invalidAddr != null && invalidAddr.length > 0) {
1160        out.println("DEBUG SMTP: Invalid Addresses");
1161        for (int k = 0; k < invalidAddr.length; k++) {
1162            out.println("DEBUG SMTP: " + invalidAddr[k]);
1163        }
1164        }
1165    }
1166
1167    // throw the exception, fire TransportEvent.MESSAGE_NOT_DELIVERED event
1168
if (sendFailed) {
1169        if (debug)
1170        out.println("DEBUG SMTP: Sending failed " +
1171                   "because of invalid destination addresses");
1172        notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
1173                     validSentAddr, validUnsentAddr,
1174                     invalidAddr, this.message);
1175
1176        // reset the connection so more sends are allowed
1177
String JavaDoc lsr = lastServerResponse; // save, for get
1178
int lrc = lastReturnCode;
1179        try {
1180        if (serverSocket != null)
1181            issueCommand("RSET", 250);
1182        } catch (MessagingException ex) {
1183        // if can't reset, best to close the connection
1184
try {
1185            close();
1186        } catch (MessagingException ex2) {
1187            // thrown by close()--ignore, will close() later anyway
1188
if (debug)
1189            ex2.printStackTrace(out);
1190        }
1191        } finally {
1192        lastServerResponse = lsr; // restore
1193
lastReturnCode = lrc;
1194        }
1195
1196        throw new SendFailedException("Invalid Addresses", mex,
1197                      validSentAddr,
1198                      validUnsentAddr, invalidAddr);
1199    }
1200    }
1201
1202    /**
1203     * Send the "data" command to smtp host and return an OutputStream
1204     * to which the data is to be written.
1205     */

1206    private OutputStream data() throws MessagingException {
1207    issueSendCommand("DATA", 354);
1208    return new SMTPOutputStream(serverOutput);
1209    }
1210
1211    /**
1212     * Terminate the sent data.
1213     */

1214    private void finishData() throws MessagingException {
1215    issueSendCommand("\r\n.", 250); // XXX - adds blank line to message?
1216
}
1217
1218    /**
1219     * Issue the STARTTLS command and switch the socket to
1220     * TLS mode if it succeeds.
1221     */

1222    private void startTLS() throws MessagingException {
1223    issueCommand("STARTTLS", 220);
1224    // it worked, now switch the socket into TLS mode
1225
try {
1226        serverSocket = SocketFetcher.startTLS(serverSocket);
1227        initStreams();
1228    } catch (IOException ioex) {
1229        closeConnection();
1230        throw new MessagingException("Could not convert socket to TLS",
1231                                ioex);
1232    }
1233    }
1234
1235    /////// primitives ///////
1236

1237    /**
1238     * Connect to server on port and start the SMTP protocol.
1239     */

1240    private void openServer(String JavaDoc server, int port)
1241                throws MessagingException {
1242
1243        if (debug)
1244        out.println("DEBUG SMTP: trying to connect to host \"" + server +
1245                "\", port " + port + ", isSSL " + isSSL);
1246
1247    try {
1248        Properties props = session.getProperties();
1249
1250        serverSocket = SocketFetcher.getSocket(server, port,
1251        props, "mail." + name, isSSL);
1252
1253        // socket factory may've chosen a different port,
1254
// update it for the debug messages that follow
1255
port = serverSocket.getPort();
1256
1257        initStreams();
1258
1259        int r = -1;
1260        if ((r = readServerResponse()) != 220) {
1261        serverSocket.close();
1262        serverSocket = null;
1263        serverOutput = null;
1264        serverInput = null;
1265        lineInputStream = null;
1266        if (debug)
1267            out.println("DEBUG SMTP: could not connect to host \"" +
1268                    server + "\", port: " + port +
1269                    ", response: " + r + "\n");
1270        throw new MessagingException(
1271            "Could not connect to SMTP host: " + server +
1272                    ", port: " + port +
1273                    ", response: " + r);
1274        } else {
1275        if (debug)
1276            out.println("DEBUG SMTP: connected to host \"" +
1277                       server + "\", port: " + port + "\n");
1278        }
1279    } catch (UnknownHostException uhex) {
1280        throw new MessagingException("Unknown SMTP host: " + server, uhex);
1281    } catch (IOException ioe) {
1282        throw new MessagingException("Could not connect to SMTP host: " +
1283                    server + ", port: " + port, ioe);
1284    }
1285    }
1286
1287    /**
1288     * Start the protocol to the server on serverSocket,
1289     * assumed to be provided and connected by the caller.
1290     */

1291    private void openServer() throws MessagingException {
1292    int port = -1;
1293    String JavaDoc server = "UNKNOWN";
1294    try {
1295        port = serverSocket.getPort();
1296        server = serverSocket.getInetAddress().getHostName();
1297        if (debug)
1298        out.println("DEBUG SMTP: starting protocol to host \"" +
1299                    server + "\", port " + port);
1300
1301        initStreams();
1302
1303        int r = -1;
1304        if ((r = readServerResponse()) != 220) {
1305        serverSocket.close();
1306        serverSocket = null;
1307        serverOutput = null;
1308        serverInput = null;
1309        lineInputStream = null;
1310        if (debug)
1311            out.println("DEBUG SMTP: got bad greeting from host \"" +
1312                    server + "\", port: " + port +
1313                    ", response: " + r + "\n");
1314        throw new MessagingException(
1315            "Got bad greeting from SMTP host: " + server +
1316                    ", port: " + port +
1317                    ", response: " + r);
1318        } else {
1319        if (debug)
1320            out.println("DEBUG SMTP: protocol started to host \"" +
1321                       server + "\", port: " + port + "\n");
1322        }
1323    } catch (IOException ioe) {
1324        throw new MessagingException(
1325                    "Could not start protocol to SMTP host: " +
1326                    server + ", port: " + port, ioe);
1327    }
1328    }
1329
1330
1331    private void initStreams() throws IOException {
1332    Properties props = session.getProperties();
1333    PrintStream out = session.getDebugOut();
1334    boolean debug = session.getDebug();
1335
1336    String JavaDoc s = props.getProperty("mail.debug.quote");
1337    boolean quote = s != null && s.equalsIgnoreCase("true");
1338
1339    TraceInputStream traceInput =
1340        new TraceInputStream(serverSocket.getInputStream(), out);
1341    traceInput.setTrace(debug);
1342    traceInput.setQuote(quote);
1343
1344    TraceOutputStream traceOutput =
1345        new TraceOutputStream(serverSocket.getOutputStream(), out);
1346    traceOutput.setTrace(debug);
1347    traceOutput.setQuote(quote);
1348
1349    serverOutput =
1350        new BufferedOutputStream(traceOutput);
1351    serverInput =
1352        new BufferedInputStream(traceInput);
1353    lineInputStream = new LineInputStream(serverInput);
1354    }
1355
1356    private void issueCommand(String JavaDoc cmd, int expect)
1357                throws MessagingException {
1358    sendCommand(cmd);
1359
1360    // if server responded with an unexpected return code,
1361
// throw the exception, notifying the client of the response
1362
if (readServerResponse() != expect)
1363        throw new MessagingException(lastServerResponse);
1364    }
1365
1366    /**
1367     * Issue a command that's part of sending a message.
1368     */

1369    private void issueSendCommand(String JavaDoc cmd, int expect)
1370                throws MessagingException {
1371    sendCommand(cmd);
1372
1373    // if server responded with an unexpected return code,
1374
// throw the exception, notifying the client of the response
1375
int ret;
1376    if ((ret = readServerResponse()) != expect) {
1377        // assume message was not sent to anyone,
1378
// combine valid sent & unsent addresses
1379
int vsl = validSentAddr == null ? 0 : validSentAddr.length;
1380        int vul = validUnsentAddr == null ? 0 : validUnsentAddr.length;
1381        Address[] valid = new Address[vsl + vul];
1382        if (vsl > 0)
1383        System.arraycopy(validSentAddr, 0, valid, 0, vsl);
1384        if (vul > 0)
1385        System.arraycopy(validUnsentAddr, 0, valid, vsl, vul);
1386        validSentAddr = null;
1387        validUnsentAddr = valid;
1388        throw new SMTPSendFailedException(cmd, ret, lastServerResponse,
1389            exception, validSentAddr, validUnsentAddr, invalidAddr);
1390    }
1391    }
1392
1393    private int simpleCommand(String JavaDoc cmd) throws MessagingException {
1394    sendCommand(cmd);
1395    return readServerResponse();
1396    }
1397
1398    private int simpleCommand(byte[] cmd) throws MessagingException {
1399    sendCommand(cmd);
1400    return readServerResponse();
1401    }
1402
1403    /**
1404     * Sends command <code>cmd</code> to the server terminating
1405     * it with <code>CRLF</code>.
1406     */

1407    private void sendCommand(String JavaDoc cmd) throws MessagingException {
1408    sendCommand(ASCIIUtility.getBytes(cmd));
1409    }
1410
1411    private void sendCommand(byte[] cmdBytes) throws MessagingException {
1412    //if (debug)
1413
//out.println("DEBUG SMTP SENT: " + new String(cmdBytes, 0));
1414

1415        try {
1416        serverOutput.write(cmdBytes);
1417        serverOutput.write(CRLF);
1418        serverOutput.flush();
1419    } catch (IOException ex) {
1420        throw new MessagingException("Can't send command to SMTP host", ex);
1421    }
1422    }
1423
1424    /**
1425     * Reads server reponse returning the <code>returnCode</code>
1426     * as the number. Returns -1 on failure. Sets
1427     * <code>lastServerResponse</code> and <code>lastReturnCode</code>.
1428     */

1429    private int readServerResponse() throws MessagingException {
1430        String JavaDoc serverResponse = "";
1431        int returnCode = 0;
1432    StringBuffer JavaDoc buf = new StringBuffer JavaDoc(100);
1433
1434    // read the server response line(s) and add them to the buffer
1435
// that stores the response
1436
try {
1437        String JavaDoc line = null;
1438
1439        do {
1440        line = lineInputStream.readLine();
1441        if (line == null) {
1442            serverResponse = buf.toString();
1443            if (serverResponse.length() == 0)
1444            serverResponse = "[EOF]";
1445            lastServerResponse = serverResponse;
1446            lastReturnCode = -1;
1447            if (debug)
1448            out.println("DEBUG SMTP: EOF: " + serverResponse);
1449            return -1;
1450        }
1451        buf.append(line);
1452        buf.append("\n");
1453        } while (isNotLastLine(line));
1454
1455            serverResponse = buf.toString();
1456        } catch (IOException ioex) {
1457        if (debug)
1458        out.println("DEBUG SMTP: exception reading response: " + ioex);
1459            //ioex.printStackTrace(out);
1460
lastServerResponse = "";
1461        lastReturnCode = 0;
1462        throw new MessagingException("Exception reading response", ioex);
1463            //returnCode = -1;
1464
}
1465
1466    // print debug info
1467
//if (debug)
1468
//out.println("DEBUG SMTP RCVD: " + serverResponse);
1469

1470    // parse out the return code
1471
if (serverResponse != null && serverResponse.length() >= 3) {
1472            try {
1473                returnCode = Integer.parseInt(serverResponse.substring(0, 3));
1474            } catch (NumberFormatException JavaDoc nfe) {
1475        try {
1476            close();
1477        } catch (MessagingException mex) {
1478            // thrown by close()--ignore, will close() later anyway
1479
if (debug)
1480            mex.printStackTrace(out);
1481        }
1482        returnCode = -1;
1483            } catch (StringIndexOutOfBoundsException JavaDoc ex) {
1484        //if (debug) ex.printStackTrace(out);
1485
try {
1486            close();
1487        } catch (MessagingException mex) {
1488            // thrown by close()--ignore, will close() later anyway
1489
if (debug)
1490            mex.printStackTrace(out);
1491        }
1492                returnCode = -1;
1493        }
1494    } else {
1495        returnCode = -1;
1496    }
1497    if (returnCode == -1 && debug)
1498        out.println("DEBUG SMTP: bad server response: " + serverResponse);
1499
1500        lastServerResponse = serverResponse;
1501    lastReturnCode = returnCode;
1502        return returnCode;
1503    }
1504
1505    /**
1506     * Check if we're in the connected state. Don't bother checking
1507     * whether the server is still alive, that will be detected later.
1508     */

1509    private void checkConnected() {
1510    if (!super.isConnected())
1511        throw new IllegalStateException JavaDoc("Not connected");
1512    }
1513
1514    // tests if the <code>line</code> is an intermediate line according to SMTP
1515
private boolean isNotLastLine(String JavaDoc line) {
1516        return line != null && line.length() >= 4 && line.charAt(3) == '-';
1517    }
1518
1519    // wraps an address in "<>"'s if necessary
1520
private String JavaDoc normalizeAddress(String JavaDoc addr) {
1521    if ((!addr.startsWith("<")) && (!addr.endsWith(">")))
1522        return "<" + addr + ">";
1523    else
1524        return addr;
1525    }
1526
1527    /**
1528     * Return true if the SMTP server supports the specified service
1529     * extension. Extensions are reported as results of the EHLO
1530     * command when connecting to the server. See
1531     * <A HREF="http://www.ietf.org/rfc/rfc1869.txt">RFC 1869</A>
1532     * and other RFCs that define specific extensions.
1533     *
1534     * @param ext the service extension name
1535     * @return true if the extension is supported
1536     *
1537     * @since JavaMail 1.3.2
1538     */

1539    public boolean supportsExtension(String JavaDoc ext) {
1540    return extMap != null && extMap.get(ext.toUpperCase()) != null;
1541    }
1542
1543    /**
1544     * Return the parameter the server provided for the specified
1545     * service extension, or null if the extension isn't supported.
1546     *
1547     * @param ext the service extension name
1548     * @return the extension parameter
1549     *
1550     * @since JavaMail 1.3.2
1551     */

1552    public String JavaDoc getExtensionParameter(String JavaDoc ext) {
1553    return extMap == null ? null : (String JavaDoc)extMap.get(ext.toUpperCase());
1554    }
1555
1556    private boolean supportsAuthentication(String JavaDoc auth) {
1557    if (extMap == null)
1558        return false;
1559    String JavaDoc a = (String JavaDoc)extMap.get("AUTH");
1560    if (a == null)
1561        return false;
1562    StringTokenizer st = new StringTokenizer(a);
1563    while (st.hasMoreTokens()) {
1564        String JavaDoc tok = st.nextToken();
1565        if (tok.equalsIgnoreCase(auth))
1566        return true;
1567    }
1568    return false;
1569    }
1570
1571    private static char[] hexchar = {
1572    '0', '1', '2', '3', '4', '5', '6', '7',
1573    '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
1574    };
1575
1576    /**
1577     * Convert a string to RFC 1891 xtext format.
1578     *
1579     * <p><pre>
1580     * xtext = *( xchar / hexchar )
1581     *
1582     * xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive,
1583     * except for "+" and "=".
1584     *
1585     * ; "hexchar"s are intended to encode octets that cannot appear
1586     * ; as ASCII characters within an esmtp-value.
1587     *
1588     * hexchar = ASCII "+" immediately followed by two upper case
1589     * hexadecimal digits
1590     * </pre></p>
1591     */

1592    private String JavaDoc xtext(String JavaDoc s) {
1593    StringBuffer JavaDoc sb = null;
1594    for (int i = 0; i < s.length(); i++) {
1595        char c = s.charAt(i);
1596        if (c >= 128) // not ASCII
1597
throw new IllegalArgumentException JavaDoc(
1598            "Non-ASCII character in SMTP submitter: " + s);
1599        if (c < '!' || c > '~' || c == '+' || c == '=') {
1600        if (sb == null) {
1601            sb = new StringBuffer JavaDoc(s.length() + 4);
1602            sb.append(s.substring(0, i));
1603        }
1604        sb.append('+');
1605        sb.append(hexchar[(((int)c)& 0xf0) >> 4]);
1606        sb.append(hexchar[((int)c)& 0x0f]);
1607        } else {
1608        if (sb != null)
1609            sb.append(c);
1610        }
1611    }
1612    return sb != null ? sb.toString() : s;
1613    }
1614}
1615
Popular Tags