View Javadoc

1   /**
2    *
3    * Copyright 2003-2005 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.geronimo.javamail.transport.smtp;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.io.PrintStream;
24  import java.io.UnsupportedEncodingException;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.net.InetAddress;
28  import java.net.Socket;
29  import java.net.SocketException;
30  import java.net.UnknownHostException;
31  import java.util.ArrayList;
32  import java.util.HashMap;
33  import java.util.StringTokenizer;
34  
35  import javax.mail.Address;
36  import javax.mail.AuthenticationFailedException;
37  import javax.mail.Message;
38  import javax.mail.MessagingException;
39  import javax.mail.Session;
40  import javax.mail.Transport;
41  import javax.mail.URLName;
42  import javax.mail.event.TransportEvent;
43  import javax.mail.internet.InternetAddress;
44  import javax.mail.internet.MimeMessage;
45  import javax.net.ssl.SSLSocket;
46  
47  import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
48  import org.apache.geronimo.javamail.authentication.CramMD5Authenticator;
49  import org.apache.geronimo.javamail.authentication.DigestMD5Authenticator;
50  import org.apache.geronimo.javamail.authentication.LoginAuthenticator;
51  import org.apache.geronimo.javamail.authentication.PlainAuthenticator;
52  import org.apache.geronimo.javamail.util.MIMEOutputStream;
53  import org.apache.geronimo.javamail.util.TraceInputStream;
54  import org.apache.geronimo.javamail.util.TraceOutputStream;
55  import org.apache.geronimo.mail.util.Base64;
56  import org.apache.geronimo.mail.util.XText;
57  
58  /**
59   * Simple implementation of SMTP transport. Just does plain RFC821-ish delivery.
60   * <p/> Supported properties : <p/>
61   * <ul>
62   * <li> mail.host : to set the server to deliver to. Default = localhost</li>
63   * <li> mail.smtp.port : to set the port. Default = 25</li>
64   * <li> mail.smtp.locahost : name to use for HELO/EHLO - default getHostName()</li>
65   * </ul>
66   * <p/> There is no way to indicate failure for a given recipient (it's possible
67   * to have a recipient address rejected). The sun impl throws exceptions even if
68   * others successful), but maybe we do a different way... <p/> TODO : lots.
69   * ESMTP, user/pass, indicate failure, etc...
70   *
71   * @version $Rev: 432884 $ $Date: 2006-08-19 14:53:20 -0700 (Sat, 19 Aug 2006) $
72   */
73  public class SMTPTransport extends Transport {
74  
75      /**
76       * constants for EOL termination
77       */
78      protected static final char CR = '\r';
79  
80      protected static final char LF = '\n';
81  
82      /**
83       * property keys for top level session properties.
84       */
85      protected static final String MAIL_LOCALHOST = "mail.localhost";
86  
87      protected static final String MAIL_SSLFACTORY_CLASS = "mail.SSLSocketFactory.class";
88  
89      /**
90       * property keys for protocol properties. The actual property name will be
91       * appended with "mail." + protocol + ".", where the protocol is either
92       * "smtp" or "smtps".
93       */
94      protected static final String MAIL_SMTP_AUTH = "auth";
95  
96      protected static final String MAIL_SMTP_PORT = "port";
97  
98      protected static final String MAIL_SMTP_LOCALHOST = "localhost";
99  
100     protected static final String MAIL_SMTP_TIMEOUT = "timeout";
101 
102     protected static final String MAIL_SMTP_SASL_REALM = "sasl.realm";
103 
104     protected static final String MAIL_SMTP_TLS = "starttls.enable";
105 
106     protected static final String MAIL_SMTP_FACTORY_CLASS = "socketFactory.class";
107 
108     protected static final String MAIL_SMTP_FACTORY_FALLBACK = "socketFactory.fallback";
109 
110     protected static final String MAIL_SMTP_FACTORY_PORT = "socketFactory.port";
111 
112     protected static final String MAIL_SMTP_REPORT_SUCCESS = "reportsuccess";
113 
114     protected static final String MAIL_SMTP_STARTTLS_ENABLE = "starttls.enable";
115 
116     protected static final String MAIL_SMTP_DSN_NOTIFY = "dsn.notify";
117 
118     protected static final String MAIL_SMTP_SENDPARTIAL = "sendpartial";
119 
120     protected static final String MAIL_SMTP_LOCALADDRESS = "localaddress";
121 
122     protected static final String MAIL_SMTP_LOCALPORT = "localport";
123 
124     protected static final String MAIL_SMTP_QUITWAIT = "quitwait";
125 
126     protected static final String MAIL_SMTP_FROM = "from";
127 
128     protected static final String MAIL_SMTP_DSN_RET = "dsn.ret";
129 
130     protected static final String MAIL_SMTP_SUBMITTER = "submitter";
131 
132     protected static final String MAIL_SMTP_EXTENSION = "mailextension";
133 
134     protected static final String MAIL_SMTP_EHLO = "ehlo";
135 
136     protected static final String MAIL_SMTP_ENCODE_TRACE = "encodetrace";
137 
138     protected static final int MIN_MILLIS = 1000 * 60;
139 
140     protected static final int TIMEOUT = MIN_MILLIS * 5;
141 
142     protected static final String DEFAULT_MAIL_HOST = "localhost";
143 
144     protected static final int DEFAULT_MAIL_SMTP_PORT = 25;
145 
146     protected static final int DEFAULT_MAIL_SMTPS_PORT = 465;
147 
148     // SMTP reply codes
149     protected static final int SERVICE_READY = 220;
150 
151     protected static final int SERVICE_CLOSING = 221;
152 
153     protected static final int AUTHENTICATION_COMPLETE = 235;
154 
155     protected static final int COMMAND_ACCEPTED = 250;
156 
157     protected static final int ADDRESS_NOT_LOCAL = 251;
158 
159     protected static final int AUTHENTICATION_CHALLENGE = 334;
160 
161     protected static final int START_MAIL_INPUT = 354;
162 
163     protected static final int SERVICE_NOT_AVAILABLE = 421;
164 
165     protected static final int MAILBOX_BUSY = 450;
166 
167     protected static final int PROCESSING_ERROR = 451;
168 
169     protected static final int INSUFFICIENT_STORAGE = 452;
170 
171     protected static final int COMMAND_SYNTAX_ERROR = 500;
172 
173     protected static final int PARAMETER_SYNTAX_ERROR = 501;
174 
175     protected static final int COMMAND_NOT_IMPLEMENTED = 502;
176 
177     protected static final int INVALID_COMMAND_SEQUENCE = 503;
178 
179     protected static final int COMMAND_PARAMETER_NOT_IMPLEMENTED = 504;
180 
181     protected static final int MAILBOX_NOT_FOUND = 550;
182 
183     protected static final int USER_NOT_LOCAL = 551;
184 
185     protected static final int MAILBOX_FULL = 552;
186 
187     protected static final int INVALID_MAILBOX = 553;
188 
189     protected static final int TRANSACTION_FAILED = 553;
190 
191     protected static final String AUTHENTICATION_PLAIN = "PLAIN";
192 
193     protected static final String AUTHENTICATION_LOGIN = "LOGIN";
194 
195     protected static final String AUTHENTICATION_CRAMMD5 = "CRAM-MD5";
196 
197     protected static final String AUTHENTICATION_DIGESTMD5 = "DIGEST-MD5";
198 
199     // the protocol we're working with. This will be either "smtp" or "smtps".
200     protected String protocol;
201 
202     // the target host
203     protected String host;
204 
205     // the default port to use for this protocol (differs between "smtp" and
206     // "smtps").
207     protected int defaultPort;
208 
209     // the target server port.
210     protected int port;
211 
212     // the connection socket...can be a plain socket or SSLSocket, if TLS is
213     // being used.
214     protected Socket socket;
215 
216     // our local host name
217     protected String localHost;
218 
219     // input stream used to read data. If Sasl is in use, this might be other
220     // than the
221     // direct access to the socket input stream.
222     protected InputStream inputStream;
223 
224     // the other end of the connection pipeline.
225     protected OutputStream outputStream;
226 
227     // list of authentication mechanisms supported by the server
228     protected HashMap serverAuthenticationMechanisms;
229 
230     // map of server extension arguments
231     protected HashMap serverExtensionArgs;
232 
233     // do we report success after completion of each mail send.
234     protected boolean reportSuccess;
235 
236     // does the server support transport level security?
237     protected boolean serverTLS = false;
238 
239     // is TLS enabled on our part?
240     protected boolean useTLS = false;
241 
242     // do we use SSL for our initial connection?
243     protected boolean sslConnection = false;
244 
245     // the username we connect with
246     protected String username;
247 
248     // the authentication password.
249     protected String password;
250 
251     // the target SASL realm (normally null unless explicitly set or we have an
252     // authentication mechanism that
253     // requires it.
254     protected String realm;
255 
256     // the last response line received from the server.
257     protected SMTPReply lastServerResponse = null;
258 
259     // our session provided debug output stream.
260     protected PrintStream debugStream;
261 
262     /**
263      * Normal constructor for an SMTPTransport() object. This constructor is
264      * used to build a transport instance for the "smtp" protocol.
265      *
266      * @param session
267      *            The attached session.
268      * @param name
269      *            An optional URLName object containing target information.
270      */
271     public SMTPTransport(Session session, URLName name) {
272         this(session, name, "smtp", DEFAULT_MAIL_SMTP_PORT, false);
273     }
274 
275     /**
276      * Common constructor used by the SMTPTransport and SMTPSTransport classes
277      * to do common initialization of defaults.
278      *
279      * @param session
280      *            The host session instance.
281      * @param name
282      *            The URLName of the target.
283      * @param protocol
284      *            The protocol type (either "smtp" or "smtps". This helps us in
285      *            retrieving protocol-specific session properties.
286      * @param defaultPort
287      *            The default port used by this protocol. For "smtp", this will
288      *            be 25. The default for "smtps" is 465.
289      * @param sslConnection
290      *            Indicates whether an SSL connection should be used to initial
291      *            contact the server. This is different from the STARTTLS
292      *            support, which switches the connection to SSL after the
293      *            initial startup.
294      */
295     protected SMTPTransport(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
296         super(session, name);
297         this.protocol = protocol;
298 
299         // these are defaults based on what the superclass specifies.
300         this.defaultPort = defaultPort;
301         this.sslConnection = sslConnection;
302         // check to see if we need to throw an exception after a send operation.
303         reportSuccess = isProtocolPropertyTrue(MAIL_SMTP_REPORT_SUCCESS);
304         // and also check for TLS enablement.
305         useTLS = isProtocolPropertyTrue(MAIL_SMTP_STARTTLS_ENABLE);
306 
307         // get our debug output.
308         debugStream = session.getDebugOut();
309     }
310 
311     /**
312      * Connect to a server using an already created socket. This connection is
313      * just like any other connection, except we will not create a new socket.
314      *
315      * @param socket
316      *            The socket connection to use.
317      */
318     public void connect(Socket socket) throws MessagingException {
319         this.socket = socket;
320         super.connect();
321     }
322 
323     /**
324      * Do the protocol connection for an SMTP transport. This handles server
325      * authentication, if possible. Returns false if unable to connect to the
326      * server.
327      *
328      * @param host
329      *            The target host name.
330      * @param port
331      *            The server port number.
332      * @param user
333      *            The authentication user (if any).
334      * @param password
335      *            The server password. Might not be sent directly if more
336      *            sophisticated authentication is used.
337      *
338      * @return true if we were able to connect to the server properly, false for
339      *         any failures.
340      * @exception MessagingException
341      */
342     protected boolean protocolConnect(String host, int port, String username, String password)
343             throws MessagingException {
344         if (debug) {
345             debugOut("Connecting to server " + host + ":" + port + " for user " + username);
346         }
347 
348         // first check to see if we need to authenticate. If we need this, then
349         // we must have a username and
350         // password specified. Failing this may result in a user prompt to
351         // collect the information.
352         boolean mustAuthenticate = isProtocolPropertyTrue(MAIL_SMTP_AUTH);
353 
354         // if we need to authenticate, and we don't have both a userid and
355         // password, then we fail this
356         // immediately. The Service.connect() method will try to obtain the user
357         // information and retry the
358         // connection one time.
359         if (mustAuthenticate && (username == null || password == null)) {
360             return false;
361         }
362 
363         // if the port is defaulted, then see if we have something configured in
364         // the session.
365         // if not configured, we just use the default default.
366         if (port == -1) {
367             // take the default first.
368             port = defaultPort;
369             String configuredPort = getProtocolProperty(MAIL_SMTP_PORT);
370             if (configuredPort != null) {
371                 port = Integer.parseInt(configuredPort);
372             }
373         }
374 
375         try {
376 
377             // create socket and connect to server.
378             getConnection(host, port, username, password);
379 
380             // receive welcoming message
381             if (!getWelcome()) {
382                 throw new MessagingException("Error in getting welcome msg");
383             }
384 
385             // say hello
386             if (!sendHandshake()) {
387                 throw new MessagingException("Error in saying EHLO to server");
388             }
389 
390             // authenticate with the server, if necessary
391             if (!processAuthentication()) {
392                 if (debug) {
393                     debugOut("User authentication failure");
394                 }
395                 throw new AuthenticationFailedException("Error authenticating with server");
396             }
397         } catch (IOException e) {
398             if (debug) {
399                 debugOut("I/O exception establishing connection", e);
400             }
401             throw new MessagingException("Connection error", e);
402         }
403         return true;
404     }
405 
406     /**
407      * Send a message to multiple addressees.
408      *
409      * @param message
410      *            The message we're sending.
411      * @param addresses
412      *            An array of addresses to send to.
413      *
414      * @exception MessagingException
415      */
416     public void sendMessage(Message message, Address[] addresses) throws MessagingException {
417         if (!isConnected()) {
418             throw new IllegalStateException("Not connected");
419         }
420         // don't bother me w/ null messages or no addreses
421         if (message == null) {
422             throw new MessagingException("Null message");
423         }
424 
425         // SMTP only handles instances of MimeMessage, not the more general
426         // message case.
427         if (!(message instanceof MimeMessage)) {
428             throw new MessagingException("SMTP can only send MimeMessages");
429         }
430 
431         // we must have a message list.
432         if (addresses == null || addresses.length == 0) {
433             throw new MessagingException("Null or empty address array");
434         }
435 
436         boolean haveGroup = false;
437 
438         // enforce the requirement that all of the targets are InternetAddress
439         // instances.
440         for (int i = 0; i < addresses.length; i++) {
441             if (addresses[i] instanceof InternetAddress) {
442                 // and while we're here, see if we have a groups in the address
443                 // list. If we do, then
444                 // we're going to need to expand these before sending.
445                 if (((InternetAddress) addresses[i]).isGroup()) {
446                     haveGroup = true;
447                 }
448             } else {
449                 throw new MessagingException("Illegal InternetAddress " + addresses[i]);
450             }
451         }
452 
453         // did we find a group? Time to expand this into our full target list.
454         if (haveGroup) {
455             addresses = expandGroups(addresses);
456         }
457 
458         SendStatus[] stats = new SendStatus[addresses.length];
459 
460         // create our lists for notification and exception reporting.
461         Address[] sent = null;
462         Address[] unsent = null;
463         Address[] invalid = null;
464 
465         try {
466             // send sender first. If this failed, send a failure notice of the
467             // event, using the full list of
468             // addresses as the unsent, and nothing for the rest.
469             if (!sendMailFrom(message)) {
470                 unsent = addresses;
471                 sent = new Address[0];
472                 invalid = new Address[0];
473                 // notify of the error.
474                 notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
475 
476                 // include the reponse information here.
477                 SMTPReply last = lastServerResponse;
478                 // now send an "uber-exception" to indicate the failure.
479                 throw new SMTPSendFailedException("MAIL FROM", last.getCode(), last.getMessage(), null, sent, unsent,
480                         invalid);
481             }
482 
483             String dsn = null;
484 
485             // there's an optional notification argument that can be added to
486             // MAIL TO. See if we've been
487             // provided with one.
488 
489             // an SMTPMessage object is the first source
490             if (message instanceof SMTPMessage) {
491                 // get the notification options
492                 int options = ((SMTPMessage) message).getNotifyOptions();
493 
494                 switch (options) {
495                 // a zero value indicates nothing is set.
496                 case 0:
497                     break;
498 
499                 case SMTPMessage.NOTIFY_NEVER:
500                     dsn = "NEVER";
501                     break;
502 
503                 case SMTPMessage.NOTIFY_SUCCESS:
504                     dsn = "SUCCESS";
505                     break;
506 
507                 case SMTPMessage.NOTIFY_FAILURE:
508                     dsn = "FAILURE";
509                     break;
510 
511                 case SMTPMessage.NOTIFY_DELAY:
512                     dsn = "DELAY";
513                     break;
514 
515                 // now for combinations...there are few enough combinations here
516                 // that we can just handle this in the switch statement rather
517                 // than have to
518                 // concatentate everything together.
519                 case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_FAILURE):
520                     dsn = "SUCCESS,FAILURE";
521                     break;
522 
523                 case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_DELAY):
524                     dsn = "SUCCESS,DELAY";
525                     break;
526 
527                 case (SMTPMessage.NOTIFY_FAILURE + SMTPMessage.NOTIFY_DELAY):
528                     dsn = "FAILURE,DELAY";
529                     break;
530 
531                 case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_FAILURE + SMTPMessage.NOTIFY_DELAY):
532                     dsn = "SUCCESS,FAILURE,DELAY";
533                     break;
534                 }
535             }
536 
537             // if still null, grab a property value (yada, yada, yada...)
538             if (dsn == null) {
539                 dsn = getProtocolProperty(MAIL_SMTP_DSN_NOTIFY);
540             }
541 
542             // we need to know about any failures once we've gone through the
543             // complete list, so keep a
544             // failure flag.
545             boolean sendFailure = false;
546 
547             // event notifcation requires we send lists of successes and
548             // failures broken down by category.
549             // The categories are:
550             //
551             // 1) addresses successfully processed.
552             // 2) addresses deemed valid, but had a processing failure that
553             // prevented sending.
554             // 3) addressed deemed invalid (basically all other processing
555             // failures).
556             ArrayList sentAddresses = new ArrayList();
557             ArrayList unsentAddresses = new ArrayList();
558             ArrayList invalidAddresses = new ArrayList();
559 
560             // Now we add a MAIL TO record for each recipient. At this point, we
561             // just collect
562             for (int i = 0; i < addresses.length; i++) {
563                 InternetAddress target = (InternetAddress) addresses[i];
564 
565                 // write out the record now.
566                 SendStatus status = sendRcptTo(target, dsn);
567                 stats[i] = status;
568 
569                 switch (status.getStatus()) {
570                 // successfully sent
571                 case SendStatus.SUCCESS:
572                     sentAddresses.add(target);
573                     break;
574 
575                 // we have an invalid address of some sort, or a general sending
576                 // error (which we'll
577                 // interpret as due to an invalid address.
578                 case SendStatus.INVALID_ADDRESS:
579                 case SendStatus.GENERAL_ERROR:
580                     sendFailure = true;
581                     invalidAddresses.add(target);
582                     break;
583 
584                 // good address, but this was a send failure.
585                 case SendStatus.SEND_FAILURE:
586                     sendFailure = true;
587                     unsentAddresses.add(target);
588                     break;
589                 }
590             }
591 
592             // if we had a send failure, then we need to check if we allow
593             // partial sends. If not allowed,
594             // we abort the send operation now.
595             if (sendFailure) {
596                 // now see how we're configured for this send operation.
597                 boolean partialSends = false;
598 
599                 // this can be attached directly to the message.
600                 if (message instanceof SMTPMessage) {
601                     partialSends = ((SMTPMessage) message).getSendPartial();
602                 }
603 
604                 // if still false on the message object, check for a property
605                 // version also
606                 if (!partialSends) {
607                     partialSends = isProtocolPropertyTrue(MAIL_SMTP_SENDPARTIAL);
608                 }
609 
610                 // if we're not allowing partial successes or we've failed on
611                 // all of the addresses, it's
612                 // time to abort.
613                 if (!partialSends || sentAddresses.isEmpty()) {
614                     // we send along the valid and invalid address lists on the
615                     // notifications and
616                     // exceptions.
617                     // however, since we're aborting the entire send, the
618                     // successes need to become
619                     // members of the failure list.
620                     unsentAddresses.addAll(sentAddresses);
621 
622                     // this one is empty.
623                     sent = new Address[0];
624                     unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
625                     invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
626 
627                     // go reset our connection so we can process additional
628                     // sends.
629                     resetConnection();
630 
631                     // get a list of chained exceptions for all of the failures.
632                     MessagingException failures = generateExceptionChain(stats, false);
633 
634                     // now send an "uber-exception" to indicate the failure.
635                     throw new SMTPSendFailedException("MAIL TO", 0, "Invalid Address", failures, sent, unsent, invalid);
636                 }
637             }
638 
639             try {
640                 // try to send the data
641                 sendData(message);
642             } catch (MessagingException e) {
643                 // If there's an error at this point, this is a complete
644                 // delivery failure.
645                 // we send along the valid and invalid address lists on the
646                 // notifications and
647                 // exceptions.
648                 // however, since we're aborting the entire send, the successes
649                 // need to become
650                 // members of the failure list.
651                 unsentAddresses.addAll(sentAddresses);
652 
653                 // this one is empty.
654                 sent = new Address[0];
655                 unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
656                 invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
657                 // notify of the error.
658                 notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
659                 // send a send failure exception.
660                 throw new SMTPSendFailedException("DATA", 0, "Send failure", e, sent, unsent, invalid);
661             }
662 
663             // create our lists for notification and exception reporting from
664             // this point on.
665             sent = (Address[]) sentAddresses.toArray(new Address[0]);
666             unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
667             invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
668 
669             // if sendFailure is true, we had an error during the address phase,
670             // but we had permission to
671             // process this as a partial send operation. Now that the data has
672             // been sent ok, it's time to
673             // report the partial failure.
674             if (sendFailure) {
675                 // notify our listeners of the partial delivery.
676                 notifyTransportListeners(TransportEvent.MESSAGE_PARTIALLY_DELIVERED, sent, unsent, invalid, message);
677 
678                 // get a list of chained exceptions for all of the failures (and
679                 // the successes, if reportSuccess has been
680                 // turned on).
681                 MessagingException failures = generateExceptionChain(stats, getReportSuccess());
682 
683                 // now send an "uber-exception" to indicate the failure.
684                 throw new SMTPSendFailedException("MAIL TO", 0, "Invalid Address", failures, sent, unsent, invalid);
685             }
686 
687             // notify our listeners of successful delivery.
688             notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED, sent, unsent, invalid, message);
689 
690             // we've not had any failures, but we've been asked to report
691             // success as an exception. Do
692             // this now.
693             if (reportSuccess) {
694                 // generate the chain of success exceptions (we already know
695                 // there are no failure ones to report).
696                 MessagingException successes = generateExceptionChain(stats, reportSuccess);
697                 if (successes != null) {
698                     throw successes;
699                 }
700             }
701         } catch (SMTPSendFailedException e) {
702             // if this is a send failure, we've already handled
703             // notifications....just rethrow it.
704             throw e;
705         } catch (MessagingException e) {
706             // notify of the error.
707             notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
708             throw e;
709         }
710     }
711 
712     /**
713      * Close the connection. On completion, we'll be disconnected from the
714      * server and unable to send more data.
715      *
716      * @exception MessagingException
717      */
718     public void close() throws MessagingException {
719         // if we're already closed, get outta here.
720         if (socket == null) {
721             return;
722         }
723         try {
724             // say goodbye
725             sendQuit();
726         } finally {
727             // and close up the connection. We do this in a finally block to
728             // make sure the connection
729             // is shut down even if quit gets an error.
730             closeServerConnection();
731         }
732     }
733 
734     /**
735      * Turn a series of send status items into a chain of exceptions indicating
736      * the state of each send operation.
737      *
738      * @param stats
739      *            The list of SendStatus items.
740      * @param reportSuccess
741      *            Indicates whether we should include the report success items.
742      *
743      * @return The head of a chained list of MessagingExceptions.
744      */
745     protected MessagingException generateExceptionChain(SendStatus[] stats, boolean reportSuccess) {
746         MessagingException current = null;
747 
748         for (int i = 0; i < stats.length; i++) {
749             SendStatus status = stats[i];
750 
751             if (status != null) {
752                 MessagingException nextException = stats[i].getException(reportSuccess);
753                 // if there's an exception associated with this status, chain it
754                 // up with the rest.
755                 if (nextException != null) {
756                     if (current == null) {
757                         current = nextException;
758                     } else {
759                         current.setNextException(nextException);
760                         current = nextException;
761                     }
762                 }
763             }
764         }
765         return current;
766     }
767 
768     /**
769      * Reset the server connection after an error.
770      *
771      * @exception MessagingException
772      */
773     protected void resetConnection() throws MessagingException {
774         // we want the caller to retrieve the last response responsbile for
775         // requiring the reset, so save and
776         // restore that info around the reset.
777         SMTPReply last = lastServerResponse;
778 
779         // send a reset command.
780         SMTPReply line = sendCommand("RSET");
781 
782         // if this did not reset ok, just close the connection
783         if (line.getCode() != COMMAND_ACCEPTED) {
784             close();
785         }
786         // restore this.
787         lastServerResponse = last;
788     }
789 
790     /**
791      * Expand the address list by converting any group addresses into single
792      * address targets.
793      *
794      * @param addresses
795      *            The input array of addresses.
796      *
797      * @return The expanded array of addresses.
798      * @exception MessagingException
799      */
800     protected Address[] expandGroups(Address[] addresses) throws MessagingException {
801         ArrayList expandedAddresses = new ArrayList();
802 
803         // run the list looking for group addresses, and add the full group list
804         // to our targets.
805         for (int i = 0; i < addresses.length; i++) {
806             InternetAddress address = (InternetAddress) addresses[i];
807             // not a group? Just copy over to the other list.
808             if (!address.isGroup()) {
809                 expandedAddresses.add(address);
810             } else {
811                 // get the group address and copy each member of the group into
812                 // the expanded list.
813                 InternetAddress[] groupAddresses = address.getGroup(true);
814                 for (int j = 1; j < groupAddresses.length; j++) {
815                     expandedAddresses.add(groupAddresses[j]);
816                 }
817             }
818         }
819 
820         // convert back into an array.
821         return (Address[]) expandedAddresses.toArray(new Address[0]);
822     }
823 
824     /**
825      * Create a transport connection object and connect it to the target server.
826      *
827      * @param host
828      *            The target server host.
829      * @param port
830      *            The connection port.
831      *
832      * @exception MessagingException
833      */
834     protected void getConnection(String host, int port, String username, String password) throws IOException {
835         this.host = host;
836         this.port = port;
837         this.username = username;
838         this.password = password;
839         // and see if STARTTLS is enabled.
840         useTLS = isProtocolPropertyTrue(MAIL_SMTP_TLS);
841         serverAuthenticationMechanisms = new HashMap();
842         // We might have been passed a socket to connect with...if not, we need
843         // to create one of the correct type.
844         if (socket == null) {
845             // if this is the "smtps" protocol, we start with an SSLSocket
846             if (sslConnection) {
847                 getConnectedSSLSocket();
848             } else {
849                 getConnectedSocket();
850             }
851         }
852         // if we already have a socket, get some information from it and
853         // override what we've been passed.
854         else {
855             port = socket.getPort();
856             host = socket.getInetAddress().getHostName();
857         }
858         // now set up the input/output streams.
859         inputStream = new TraceInputStream(socket.getInputStream(), debugStream, debug,
860                 isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
861         ;
862         outputStream = new TraceOutputStream(socket.getOutputStream(), debugStream, debug,
863                 isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
864     }
865 
866     /**
867      * Get a property associated with this mail protocol.
868      *
869      * @param name
870      *            The name of the property.
871      *
872      * @return The property value (returns null if the property has not been
873      *         set).
874      */
875     protected String getProtocolProperty(String name) {
876         // the name we're given is the least qualified part of the name. We
877         // construct the full property name
878         // using the protocol (either "smtp" or "smtps").
879         String fullName = "mail." + protocol + "." + name;
880         return getSessionProperty(fullName);
881     }
882 
883     /**
884      * Get a property associated with this mail session.
885      *
886      * @param name
887      *            The name of the property.
888      *
889      * @return The property value (returns null if the property has not been
890      *         set).
891      */
892     protected String getSessionProperty(String name) {
893         return session.getProperty(name);
894     }
895 
896     /**
897      * Get a property associated with this mail session. Returns the provided
898      * default if it doesn't exist.
899      *
900      * @param name
901      *            The name of the property.
902      * @param defaultValue
903      *            The default value to return if the property doesn't exist.
904      *
905      * @return The property value (returns defaultValue if the property has not
906      *         been set).
907      */
908     protected String getSessionProperty(String name, String defaultValue) {
909         String result = session.getProperty(name);
910         if (result == null) {
911             return defaultValue;
912         }
913         return result;
914     }
915 
916     /**
917      * Get a property associated with this mail session. Returns the provided
918      * default if it doesn't exist.
919      *
920      * @param name
921      *            The name of the property.
922      * @param defaultValue
923      *            The default value to return if the property doesn't exist.
924      *
925      * @return The property value (returns defaultValue if the property has not
926      *         been set).
927      */
928     protected String getProtocolProperty(String name, String defaultValue) {
929         // the name we're given is the least qualified part of the name. We
930         // construct the full property name
931         // using the protocol (either "smtp" or "smtps").
932         String fullName = "mail." + protocol + "." + name;
933         return getSessionProperty(fullName, defaultValue);
934     }
935 
936     /**
937      * Get a property associated with this mail session as an integer value.
938      * Returns the default value if the property doesn't exist or it doesn't
939      * have a valid int value.
940      *
941      * @param name
942      *            The name of the property.
943      * @param defaultValue
944      *            The default value to return if the property doesn't exist.
945      *
946      * @return The property value converted to an int.
947      */
948     protected int getIntSessionProperty(String name, int defaultValue) {
949         String result = getSessionProperty(name);
950         if (result != null) {
951             try {
952                 // convert into an int value.
953                 return Integer.parseInt(result);
954             } catch (NumberFormatException e) {
955             }
956         }
957         // return default value if it doesn't exist is isn't convertable.
958         return defaultValue;
959     }
960 
961     /**
962      * Get a property associated with this mail session as an integer value.
963      * Returns the default value if the property doesn't exist or it doesn't
964      * have a valid int value.
965      *
966      * @param name
967      *            The name of the property.
968      * @param defaultValue
969      *            The default value to return if the property doesn't exist.
970      *
971      * @return The property value converted to an int.
972      */
973     protected int getIntProtocolProperty(String name, int defaultValue) {
974         // the name we're given is the least qualified part of the name. We
975         // construct the full property name
976         // using the protocol (either "smtp" or "smtps").
977         String fullName = "mail." + protocol + "." + name;
978         return getIntSessionProperty(fullName, defaultValue);
979     }
980 
981     /**
982      * Process a session property as a boolean value, returning either true or
983      * false.
984      *
985      * @return True if the property value is "true". Returns false for any other
986      *         value (including null).
987      */
988     protected boolean isProtocolPropertyTrue(String name) {
989         // the name we're given is the least qualified part of the name. We
990         // construct the full property name
991         // using the protocol (either "smtp" or "smtps").
992         String fullName = "mail." + protocol + "." + name;
993         return isSessionPropertyTrue(fullName);
994     }
995 
996     /**
997      * Process a session property as a boolean value, returning either true or
998      * false.
999      *
1000      * @return True if the property value is "true". Returns false for any other
1001      *         value (including null).
1002      */
1003     protected boolean isSessionPropertyTrue(String name) {
1004         String property = session.getProperty(name);
1005         if (property != null) {
1006             return property.equals("true");
1007         }
1008         return false;
1009     }
1010 
1011     /**
1012      * Process a session property as a boolean value, returning either true or
1013      * false.
1014      *
1015      * @return True if the property value is "false". Returns false for other
1016      *         value (including null).
1017      */
1018     protected boolean isSessionPropertyFalse(String name) {
1019         String property = session.getProperty(name);
1020         if (property != null) {
1021             return property.equals("false");
1022         }
1023         return false;
1024     }
1025 
1026     /**
1027      * Process a session property as a boolean value, returning either true or
1028      * false.
1029      *
1030      * @return True if the property value is "false". Returns false for other
1031      *         value (including null).
1032      */
1033     protected boolean isProtocolPropertyFalse(String name) {
1034         // the name we're given is the least qualified part of the name. We
1035         // construct the full property name
1036         // using the protocol (either "smtp" or "smtps").
1037         String fullName = "mail." + protocol + "." + name;
1038         return isSessionPropertyTrue(fullName);
1039     }
1040 
1041     /**
1042      * Close the server connection at termination.
1043      */
1044     protected void closeServerConnection() {
1045         try {
1046             socket.close();
1047         } catch (IOException ignored) {
1048         }
1049 
1050         socket = null;
1051         inputStream = null;
1052         outputStream = null;
1053     }
1054 
1055     /**
1056      * Creates a connected socket
1057      *
1058      * @exception MessagingException
1059      */
1060     protected void getConnectedSocket() throws IOException {
1061         if (debug) {
1062             debugOut("Attempting plain socket connection to server " + host + ":" + port);
1063         }
1064 
1065         // the socket factory can be specified via a session property. By
1066         // default, we just directly
1067         // instantiate a socket without using a factor.
1068         String socketFactory = getProtocolProperty(MAIL_SMTP_FACTORY_CLASS);
1069 
1070         // there are several protocol properties that can be set to tune the
1071         // created socket. We need to
1072         // retrieve those bits before creating the socket.
1073         int timeout = getIntProtocolProperty(MAIL_SMTP_TIMEOUT, -1);
1074         InetAddress localAddress = null;
1075         // see if we have a local address override.
1076         String localAddrProp = getProtocolProperty(MAIL_SMTP_LOCALADDRESS);
1077         if (localAddrProp != null) {
1078             localAddress = InetAddress.getByName(localAddrProp);
1079         }
1080 
1081         // check for a local port...default is to allow socket to choose.
1082         int localPort = getIntProtocolProperty(MAIL_SMTP_LOCALPORT, 0);
1083 
1084         socket = null;
1085 
1086         // if there is no socket factory defined (normal), we just create a
1087         // socket directly.
1088         if (socketFactory == null) {
1089             socket = new Socket(host, port, localAddress, localPort);
1090         }
1091 
1092         else {
1093             try {
1094                 int socketFactoryPort = getIntProtocolProperty(MAIL_SMTP_FACTORY_PORT, -1);
1095 
1096                 // we choose the port used by the socket based on overrides.
1097                 Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
1098 
1099                 // use the current context loader to resolve this.
1100                 ClassLoader loader = Thread.currentThread().getContextClassLoader();
1101                 Class factoryClass = loader.loadClass(socketFactory);
1102 
1103                 // done indirectly, we need to invoke the method using
1104                 // reflection.
1105                 // This retrieves a factory instance.
1106                 Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
1107                 Object defFactory = getDefault.invoke(new Object(), new Object[0]);
1108 
1109                 // now that we have the factory, there are two different
1110                 // createSocket() calls we use,
1111                 // depending on whether we have a localAddress override.
1112 
1113                 if (localAddress != null) {
1114                     // retrieve the createSocket(String, int, InetAddress, int)
1115                     // method.
1116                     Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE };
1117                     Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
1118 
1119                     Object[] createSocketArgs = new Object[] { host, portArg, localAddress, new Integer(localPort) };
1120                     socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
1121                 } else {
1122                     // retrieve the createSocket(String, int) method.
1123                     Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
1124                     Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
1125 
1126                     Object[] createSocketArgs = new Object[] { host, portArg };
1127                     socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
1128                 }
1129             } catch (Throwable e) {
1130                 // if a socket factor is specified, then we may need to fall
1131                 // back to a default. This behavior
1132                 // is controlled by (surprise) more session properties.
1133                 if (isProtocolPropertyTrue(MAIL_SMTP_FACTORY_FALLBACK)) {
1134                     if (debug) {
1135                         debugOut("First plain socket attempt faile, falling back to default factory", e);
1136                     }
1137                     socket = new Socket(host, port, localAddress, localPort);
1138                 }
1139                 // we have an exception. We're going to throw an IOException,
1140                 // which may require unwrapping
1141                 // or rewrapping the exception.
1142                 else {
1143                     // we have an exception from the reflection, so unwrap the
1144                     // base exception
1145                     if (e instanceof InvocationTargetException) {
1146                         e = ((InvocationTargetException) e).getTargetException();
1147                     }
1148 
1149                     if (debug) {
1150                         debugOut("Plain socket creation failure", e);
1151                     }
1152 
1153                     // throw this as an IOException, with the original exception
1154                     // attached.
1155                     IOException ioe = new IOException("Error connecting to " + host + ", " + port);
1156                     ioe.initCause(e);
1157                     throw ioe;
1158                 }
1159             }
1160         }
1161 
1162         if (timeout >= 0) {
1163             socket.setSoTimeout(timeout);
1164         }
1165     }
1166 
1167     /**
1168      * Creates a connected SSL socket for an initial SSL connection.
1169      *
1170      * @exception MessagingException
1171      */
1172     protected void getConnectedSSLSocket() throws IOException {
1173         if (debug) {
1174             debugOut("Attempting SSL socket connection to server " + host + ":" + port);
1175         }
1176         // the socket factory can be specified via a protocol property, a
1177         // session property, and if all else
1178         // fails (which it usually does), we fall back to the standard factory
1179         // class.
1180         String socketFactory = getProtocolProperty(MAIL_SMTP_FACTORY_CLASS, getSessionProperty(MAIL_SSLFACTORY_CLASS,
1181                 "javax.net.ssl.SSLSocketFactory"));
1182 
1183         // there are several protocol properties that can be set to tune the
1184         // created socket. We need to
1185         // retrieve those bits before creating the socket.
1186         int timeout = getIntProtocolProperty(MAIL_SMTP_TIMEOUT, -1);
1187         InetAddress localAddress = null;
1188         // see if we have a local address override.
1189         String localAddrProp = getProtocolProperty(MAIL_SMTP_LOCALADDRESS);
1190         if (localAddrProp != null) {
1191             localAddress = InetAddress.getByName(localAddrProp);
1192         }
1193 
1194         // check for a local port...default is to allow socket to choose.
1195         int localPort = getIntProtocolProperty(MAIL_SMTP_LOCALPORT, 0);
1196 
1197         socket = null;
1198 
1199         // if there is no socket factory defined (normal), we just create a
1200         // socket directly.
1201         if (socketFactory == null) {
1202             socket = new Socket(host, port, localAddress, localPort);
1203         }
1204 
1205         else {
1206             // we'll try this with potentially two different factories if we're
1207             // allowed to fall back.
1208             boolean fallback = isProtocolPropertyTrue(MAIL_SMTP_FACTORY_FALLBACK);
1209 
1210             while (true) {
1211                 try {
1212                     if (debug) {
1213                         debugOut("Creating SSL socket using factory " + socketFactory);
1214                     }
1215 
1216                     int socketFactoryPort = getIntProtocolProperty(MAIL_SMTP_FACTORY_PORT, -1);
1217 
1218                     // we choose the port used by the socket based on overrides.
1219                     Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
1220 
1221                     // use the current context loader to resolve this.
1222                     ClassLoader loader = Thread.currentThread().getContextClassLoader();
1223                     Class factoryClass = loader.loadClass(socketFactory);
1224 
1225                     // done indirectly, we need to invoke the method using
1226                     // reflection.
1227                     // This retrieves a factory instance.
1228                     Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
1229                     Object defFactory = getDefault.invoke(new Object(), new Object[0]);
1230 
1231                     // now that we have the factory, there are two different
1232                     // createSocket() calls we use,
1233                     // depending on whether we have a localAddress override.
1234 
1235                     if (localAddress != null) {
1236                         // retrieve the createSocket(String, int, InetAddress,
1237                         // int) method.
1238                         Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class,
1239                                 Integer.TYPE };
1240                         Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
1241 
1242                         Object[] createSocketArgs = new Object[] { host, portArg, localAddress, new Integer(localPort) };
1243                         socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
1244                     } else {
1245                         // retrieve the createSocket(String, int) method.
1246                         Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
1247                         Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
1248 
1249                         Object[] createSocketArgs = new Object[] { host, portArg };
1250                         socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
1251                     }
1252                 } catch (Throwable e) {
1253                     // if we're allowed to fallback, then use the default
1254                     // factory and try this again. We only
1255                     // allow this to happen once.
1256                     if (fallback) {
1257                         if (debug) {
1258                             debugOut("First attempt at creating SSL socket failed, falling back to default factory");
1259                         }
1260                         socketFactory = "javax.net.ssl.SSLSocketFactory";
1261                         fallback = false;
1262                         continue;
1263                     }
1264                     // we have an exception. We're going to throw an
1265                     // IOException, which may require unwrapping
1266                     // or rewrapping the exception.
1267                     else {
1268                         // we have an exception from the reflection, so unwrap
1269                         // the base exception
1270                         if (e instanceof InvocationTargetException) {
1271                             e = ((InvocationTargetException) e).getTargetException();
1272                         }
1273 
1274                         if (debug) {
1275                             debugOut("Failure creating SSL socket", e);
1276                         }
1277 
1278                         // throw this as an IOException, with the original
1279                         // exception attached.
1280                         IOException ioe = new IOException("Error connecting to " + host + ", " + port);
1281                         ioe.initCause(e);
1282                         throw ioe;
1283                     }
1284                 }
1285             }
1286         }
1287 
1288         if (timeout >= 0) {
1289             socket.setSoTimeout(timeout);
1290         }
1291     }
1292 
1293     /**
1294      * Switch the connection to using TLS level security, switching to an SSL
1295      * socket.
1296      */
1297     protected void getConnectedTLSSocket() throws MessagingException {
1298         if (debug) {
1299             debugOut("Attempting to negotiate STARTTLS with server " + host);
1300         }
1301         // tell the server of our intention to start a TLS session
1302         SMTPReply line = sendCommand("STARTTLS");
1303 
1304         if (line.getCode() != SERVICE_READY) {
1305             if (debug) {
1306                 debugOut("STARTTLS command rejected by SMTP server " + host);
1307             }
1308             throw new MessagingException("Unable to make TLS server connection");
1309         }
1310         // it worked, now switch the socket into TLS mode
1311         try {
1312 
1313             // we use the same target and port as the current connection.
1314             String host = socket.getInetAddress().getHostName();
1315             int port = socket.getPort();
1316 
1317             // the socket factory can be specified via a session property. By
1318             // default, we use
1319             // the native SSL factory.
1320             String socketFactory = getProtocolProperty(MAIL_SMTP_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
1321 
1322             // use the current context loader to resolve this.
1323             ClassLoader loader = Thread.currentThread().getContextClassLoader();
1324             Class factoryClass = loader.loadClass(socketFactory);
1325 
1326             // done indirectly, we need to invoke the method using reflection.
1327             // This retrieves a factory instance.
1328             Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
1329             Object defFactory = getDefault.invoke(new Object(), new Object[0]);
1330 
1331             // now we need to invoke createSocket()
1332             Class[] createSocketSig = new Class[] { Socket.class, String.class, Integer.TYPE, Boolean.TYPE };
1333             Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
1334 
1335             Object[] createSocketArgs = new Object[] { socket, host, new Integer(port), Boolean.TRUE };
1336 
1337             // and finally create the socket
1338             Socket sslSocket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
1339 
1340             // if this is an instance of SSLSocket (very common), try setting
1341             // the protocol to be
1342             // "TLSv1". If this is some other class because of a factory
1343             // override, we'll just have to
1344             // accept that things will work.
1345             if (sslSocket instanceof SSLSocket) {
1346                 ((SSLSocket) sslSocket).setEnabledProtocols(new String[] { "TLSv1" });
1347                 ((SSLSocket) sslSocket).setUseClientMode(true);
1348                 ((SSLSocket) sslSocket).startHandshake();
1349             }
1350 
1351             // and finally, as a last step, replace our input streams with the
1352             // secure ones.
1353             // now set up the input/output streams.
1354             inputStream = new TraceInputStream(sslSocket.getInputStream(), debugStream, debug,
1355                     isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
1356             ;
1357             outputStream = new TraceOutputStream(sslSocket.getOutputStream(), debugStream, debug,
1358                     isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
1359             // this is our active socket now
1360             socket = sslSocket;
1361 
1362         } catch (Exception e) {
1363             if (debug) {
1364                 debugOut("Failure attempting to convert connection to TLS", e);
1365             }
1366             throw new MessagingException("Unable to convert connection to SSL", e);
1367         }
1368     }
1369 
1370     /**
1371      * Get the servers welcome blob from the wire....
1372      */
1373     protected boolean getWelcome() throws MessagingException {
1374         SMTPReply line = getReply();
1375         return !line.isError();
1376     }
1377 
1378     /**
1379      * Sends the data in the message down the socket. This presumes the server
1380      * is in the right place and ready for getting the DATA message and the data
1381      * right place in the sequence
1382      */
1383     protected void sendData(Message msg) throws MessagingException {
1384 
1385         // send the DATA command
1386         SMTPReply line = sendCommand("DATA");
1387 
1388         if (line.isError()) {
1389             throw new MessagingException("Error issuing SMTP 'DATA' command: " + line);
1390         }
1391 
1392         // now the data... I could look at the type, but
1393         try {
1394             // the data content has two requirements we need to meet by
1395             // filtering the
1396             // output stream. Requirement 1 is to conicalize any line breaks.
1397             // All line
1398             // breaks will be transformed into properly formed CRLF sequences.
1399             //
1400             // Requirement 2 is to perform byte-stuff for any line that begins
1401             // with a "."
1402             // so that data is not confused with the end-of-data marker (a
1403             // "\r\n.\r\n" sequence.
1404             //
1405             // The MIME output stream performs those two functions on behalf of
1406             // the content
1407             // writer.
1408             OutputStream mimeOut = new MIMEOutputStream(outputStream);
1409 
1410             msg.writeTo(mimeOut);
1411             mimeOut.flush();
1412         } catch (IOException e) {
1413             throw new MessagingException(e.toString());
1414         } catch (MessagingException e) {
1415             throw new MessagingException(e.toString());
1416         }
1417 
1418         // now to finish, we send a CRLF sequence, followed by a ".".
1419         sendLine("");
1420         sendLine(".");
1421 
1422         // use a longer time out here to give the server time to process the
1423         // data.
1424         try {
1425             line = new SMTPReply(receiveLine(TIMEOUT * 2));
1426         } catch (MalformedSMTPReplyException e) {
1427             throw new MessagingException(e.toString());
1428         } catch (MessagingException e) {
1429             throw new MessagingException(e.toString());
1430         }
1431 
1432         if (line.isError()) {
1433             throw new MessagingException("Error issuing SMTP 'DATA' command: " + line);
1434         }
1435     }
1436 
1437     /**
1438      * Sends the QUIT message and receieves the response
1439      */
1440     protected void sendQuit() throws MessagingException {
1441         // there's yet another property that controls whether we should wait for
1442         // a
1443         // reply for a QUIT command. If on, just send the command and get outta
1444         // here.
1445         if (isProtocolPropertyTrue(MAIL_SMTP_QUITWAIT)) {
1446             sendLine("QUIT");
1447         } else {
1448             // handle as a real command...we're going to ignore the response.
1449             sendCommand("QUIT");
1450         }
1451     }
1452 
1453     /**
1454      * Sets a receiver address for the current message
1455      *
1456      * @param addr
1457      *            The target address.
1458      * @param dsn
1459      *            An optional notification address appended to the MAIL command.
1460      *
1461      * @return The status for this particular send operation.
1462      * @exception MessagingException
1463      */
1464     protected SendStatus sendRcptTo(InternetAddress addr, String dsn) throws MessagingException {
1465         // compose the command using the fixed up email address. Normally, this
1466         // involves adding
1467         // "<" and ">" around the address.
1468 
1469         StringBuffer command = new StringBuffer();
1470 
1471         // compose the first part of the command
1472         command.append("RCPT TO: ");
1473         command.append(fixEmailAddress(addr.getAddress()));
1474 
1475         // if we have DSN information, append it to the command.
1476         if (dsn != null) {
1477             command.append(" NOTIFY=");
1478             command.append(dsn);
1479         }
1480 
1481         // get a string version of this command.
1482         String commandString = command.toString();
1483 
1484         SMTPReply line = sendCommand(commandString);
1485 
1486         switch (line.getCode()) {
1487         // these two are both successful transmissions
1488         case COMMAND_ACCEPTED:
1489         case ADDRESS_NOT_LOCAL:
1490             // we get out of here with the status information.
1491             return new SendStatus(SendStatus.SUCCESS, addr, commandString, line);
1492 
1493         // these are considered invalid address errors
1494         case PARAMETER_SYNTAX_ERROR:
1495         case INVALID_COMMAND_SEQUENCE:
1496         case MAILBOX_NOT_FOUND:
1497         case INVALID_MAILBOX:
1498         case USER_NOT_LOCAL:
1499             // we get out of here with the status information.
1500             return new SendStatus(SendStatus.INVALID_ADDRESS, addr, commandString, line);
1501 
1502         // the command was valid, but something went wrong in the server.
1503         case SERVICE_NOT_AVAILABLE:
1504         case MAILBOX_BUSY:
1505         case PROCESSING_ERROR:
1506         case INSUFFICIENT_STORAGE:
1507         case MAILBOX_FULL:
1508             // we get out of here with the status information.
1509             return new SendStatus(SendStatus.SEND_FAILURE, addr, commandString, line);
1510 
1511         // everything else is considered really bad...
1512         default:
1513             // we get out of here with the status information.
1514             return new SendStatus(SendStatus.GENERAL_ERROR, addr, commandString, line);
1515         }
1516     }
1517 
1518     /**
1519      * Set the sender for this mail.
1520      *
1521      * @param message
1522      *            The message we're sending.
1523      *
1524      * @exception MessagingException
1525      */
1526     protected boolean sendMailFrom(Message message) throws MessagingException {
1527 
1528         // need to sort the from value out from a variety of sources.
1529         String from = null;
1530 
1531         // first potential source is from the message itself, if it's an
1532         // instance of SMTPMessage.
1533         if (message instanceof SMTPMessage) {
1534             from = ((SMTPMessage) message).getEnvelopeFrom();
1535         }
1536 
1537         // if not available from the message, check the protocol property next
1538         if (from == null || from.length() == 0) {
1539             // the from value can be set explicitly as a property
1540             from = getProtocolProperty(MAIL_SMTP_FROM);
1541         }
1542 
1543         // if not there, see if we have something in the message header.
1544         if (from == null || from.length() == 0) {
1545             Address[] fromAddresses = message.getFrom();
1546 
1547             // if we have some addresses in the header, then take the first one
1548             // as our From: address
1549             if (fromAddresses != null && fromAddresses.length > 0) {
1550                 from = ((InternetAddress) fromAddresses[0]).getAddress();
1551             }
1552             // get what the InternetAddress class believes to be the local
1553             // address.
1554             else {
1555                 from = InternetAddress.getLocalAddress(session).getAddress();
1556             }
1557         }
1558 
1559         if (from == null || from.length() == 0) {
1560             throw new MessagingException("no FROM address");
1561         }
1562 
1563         StringBuffer command = new StringBuffer();
1564 
1565         // start building up the command
1566         command.append("MAIL FROM: ");
1567         command.append(fixEmailAddress(from));
1568 
1569         // does this server support Delivery Status Notification? Then we may
1570         // need to add some extra to the command.
1571         if (supportsExtension("DSN")) {
1572             String returnNotification = null;
1573 
1574             // the return notification stuff might be set as value on the
1575             // message object itself.
1576             if (message instanceof SMTPMessage) {
1577                 // we need to convert the option into a string value.
1578                 switch (((SMTPMessage) message).getReturnOption()) {
1579                 case SMTPMessage.RETURN_FULL:
1580                     returnNotification = "FULL";
1581                     break;
1582 
1583                 case SMTPMessage.RETURN_HDRS:
1584                     returnNotification = "HDRS";
1585                     break;
1586                 }
1587             }
1588 
1589             // if not obtained from the message object, it can also be set as a
1590             // property.
1591             if (returnNotification == null) {
1592                 // the DSN value is set by yet another property.
1593                 returnNotification = getProtocolProperty(MAIL_SMTP_DSN_RET);
1594             }
1595 
1596             // if we have a target, add the notification stuff to our FROM
1597             // command.
1598             if (returnNotification != null) {
1599                 command.append(" RET=");
1600                 command.append(returnNotification);
1601             }
1602         }
1603 
1604         // if this server supports AUTH and we have submitter information, then
1605         // we also add the
1606         // "AUTH=" keyword to the MAIL FROM command (see RFC 2554).
1607 
1608         if (supportsExtension("AUTH")) {
1609             String submitter = null;
1610 
1611             // another option that can be specified on the message object.
1612             if (message instanceof SMTPMessage) {
1613                 submitter = ((SMTPMessage) message).getSubmitter();
1614             }
1615             // if not part of the object, try for a propery version.
1616             if (submitter == null) {
1617                 // we only send the extra keyword is a submitter is specified.
1618                 submitter = getProtocolProperty(MAIL_SMTP_SUBMITTER);
1619             }
1620             // we have one...add the keyword, plus the submitter info in xtext
1621             // format (defined by RFC 1891).
1622             if (submitter != null) {
1623                 command.append(" AUTH=");
1624                 try {
1625                     // add this encoded
1626                     command.append(new String(XText.encode(submitter.getBytes("US-ASCII"))));
1627                 } catch (UnsupportedEncodingException e) {
1628                     throw new MessagingException("Invalid submitter value " + submitter);
1629                 }
1630             }
1631         }
1632 
1633         String extension = null;
1634 
1635         // now see if we need to add any additional extension info to this
1636         // command. The extension is not
1637         // checked for validity. That's the reponsibility of the caller.
1638         if (message instanceof SMTPMessage) {
1639             extension = ((SMTPMessage) message).getMailExtension();
1640         }
1641         // this can come either from the object or from a set property.
1642         if (extension == null) {
1643             extension = getProtocolProperty(MAIL_SMTP_EXTENSION);
1644         }
1645 
1646         // have something real to add?
1647         if (extension != null && extension.length() != 0) {
1648             // tack this on the end with a blank delimiter.
1649             command.append(' ');
1650             command.append(extension);
1651         }
1652 
1653         // and finally send the command
1654         SMTPReply line = sendCommand(command.toString());
1655 
1656         // 250 response indicates success.
1657         return line.getCode() == COMMAND_ACCEPTED;
1658     }
1659 
1660     /**
1661      * Send a command to the server, returning the first response line back as a
1662      * reply.
1663      *
1664      * @param data
1665      *            The data to send.
1666      *
1667      * @return A reply object with the reply line.
1668      * @exception MessagingException
1669      */
1670     protected SMTPReply sendCommand(String data) throws MessagingException {
1671         sendLine(data);
1672         return getReply();
1673     }
1674 
1675     /**
1676      * Sends a message down the socket and terminates with the appropriate CRLF
1677      */
1678     protected void sendLine(String data) throws MessagingException {
1679         if (socket == null || !socket.isConnected()) {
1680             throw new MessagingException("no connection");
1681         }
1682         try {
1683             System.out.println(">>>>>Sending data " + data + "<<<<<<");
1684             outputStream.write(data.getBytes());
1685             outputStream.write(CR);
1686             outputStream.write(LF);
1687             outputStream.flush();
1688         } catch (IOException e) {
1689             throw new MessagingException(e.toString());
1690         }
1691     }
1692 
1693     /**
1694      * Receives one line from the server. A line is a sequence of bytes
1695      * terminated by a CRLF
1696      *
1697      * @return the line from the server as String
1698      */
1699     protected String receiveLine() throws MessagingException {
1700         return receiveLine(TIMEOUT);
1701     }
1702 
1703     /**
1704      * Get a reply line for an SMTP command.
1705      *
1706      * @return An SMTP reply object from the stream.
1707      */
1708     protected SMTPReply getReply() throws MessagingException {
1709         try {
1710             lastServerResponse = new SMTPReply(receiveLine());
1711         } catch (MalformedSMTPReplyException e) {
1712             throw new MessagingException(e.toString());
1713         } catch (MessagingException e) {
1714             throw e;
1715         }
1716         return lastServerResponse;
1717     }
1718 
1719     /**
1720      * Retrieve the last response received from the SMTP server.
1721      *
1722      * @return The raw response string (including the error code) returned from
1723      *         the SMTP server.
1724      */
1725     public String getLastServerResponse() {
1726         if (lastServerResponse == null) {
1727             return "";
1728         }
1729         return lastServerResponse.getReply();
1730     }
1731 
1732     /**
1733      * Receives one line from the server. A line is a sequence of bytes
1734      * terminated by a CRLF
1735      *
1736      * @return the line from the server as String
1737      */
1738     protected String receiveLine(int delayMillis) throws MessagingException {
1739         if (socket == null || !socket.isConnected()) {
1740             throw new MessagingException("no connection");
1741         }
1742 
1743         int timeout = 0;
1744 
1745         try {
1746             // for now, read byte for byte, looking for a CRLF
1747             timeout = socket.getSoTimeout();
1748 
1749             socket.setSoTimeout(delayMillis);
1750 
1751             StringBuffer buff = new StringBuffer();
1752 
1753             int c;
1754             boolean crFound = false, lfFound = false;
1755 
1756             while ((c = inputStream.read()) != -1 && crFound == false && lfFound == false) {
1757                 // we're looking for a CRLF sequence, so mark each one as seen.
1758                 // Any other
1759                 // character gets appended to the end of the buffer.
1760                 if (c == CR) {
1761                     crFound = true;
1762                 } else if (c == LF) {
1763                     lfFound = true;
1764                 } else {
1765                     buff.append((char) c);
1766                 }
1767             }
1768 
1769             String line = buff.toString();
1770             return line;
1771 
1772         } catch (SocketException e) {
1773             throw new MessagingException(e.toString());
1774         } catch (IOException e) {
1775             throw new MessagingException(e.toString());
1776         } finally {
1777             try {
1778                 socket.setSoTimeout(timeout);
1779             } catch (SocketException e) {
1780                 // ignore - was just trying to do the decent thing...
1781             }
1782         }
1783     }
1784 
1785     /**
1786      * Convert an InternetAddress into a form sendable on an SMTP mail command.
1787      * InternetAddress.getAddress() generally returns just the address portion
1788      * of the full address, minus route address markers. We need to ensure we
1789      * have an address with '<' and '>' delimiters.
1790      *
1791      * @param mail
1792      *            The mail address returned from InternetAddress.getAddress().
1793      *
1794      * @return A string formatted for sending.
1795      */
1796     protected String fixEmailAddress(String mail) {
1797         if (mail.charAt(0) == '<') {
1798             return mail;
1799         }
1800         return "<" + mail + ">";
1801     }
1802 
1803     /**
1804      * Start the handshake process with the server, including setting up and
1805      * TLS-level work. At the completion of this task, we should be ready to
1806      * authenticate with the server, if needed.
1807      */
1808     protected boolean sendHandshake() throws MessagingException {
1809         // check to see what sort of initial handshake we need to make.
1810         boolean useEhlo = !isProtocolPropertyFalse(MAIL_SMTP_EHLO);
1811 
1812         // if we're to use Ehlo, send it and then fall back to just a HELO
1813         // message if it fails.
1814         if (useEhlo) {
1815             if (!sendEhlo()) {
1816                 sendHelo();
1817             }
1818         } else {
1819             // send the initial hello response.
1820             sendHelo();
1821         }
1822 
1823         if (useTLS) {
1824             // if we've been told to use TLS, and this server doesn't support
1825             // it, then this is a failure
1826             if (!serverTLS) {
1827                 throw new MessagingException("Server doesn't support required transport level security");
1828             }
1829             // if the server supports TLS, then use it for the connection.
1830             // on our connection.
1831             getConnectedTLSSocket();
1832 
1833             // some servers (gmail is one that I know of) only send a STARTTLS
1834             // extension message on the
1835             // first EHLO command. Now that we have the TLS handshaking
1836             // established, we need to send a
1837             // second EHLO message to retrieve the AUTH records from the server.
1838             serverAuthenticationMechanisms.clear();
1839             if (!sendEhlo()) {
1840                 throw new MessagingException("Failure sending EHLO command to SMTP server");
1841             }
1842         }
1843 
1844         // this worked.
1845         return true;
1846     }
1847 
1848     /**
1849      * Send the EHLO command to the SMTP server.
1850      *
1851      * @return True if the command was accepted ok, false for any errors.
1852      * @exception SMTPTransportException
1853      * @exception MalformedSMTPReplyException
1854      * @exception MessagingException
1855      */
1856     protected boolean sendEhlo() throws MessagingException {
1857         sendLine("EHLO " + getLocalHost());
1858 
1859         SMTPReply line = getReply();
1860 
1861         // we get a 250 code back. The first line is just a greeting, and
1862         // extensions are identifed on
1863         // continuations. If this fails, then we'll try once more with HELO to
1864         // establish bona fides.
1865         if (line.getCode() != COMMAND_ACCEPTED) {
1866             return false;
1867         }
1868 
1869         // get a fresh extension mapping table.
1870         serverExtensionArgs = new HashMap();
1871 
1872         // process all of the continuation lines
1873         while (line.isContinued()) {
1874             // get the next line
1875             line = getReply();
1876             if (line.getCode() != COMMAND_ACCEPTED) {
1877                 // all EHLO failures go back to the HELO failback step.
1878                 return false;
1879             }
1880             // go process the extention
1881             processExtension(line.getMessage());
1882         }
1883         return true;
1884     }
1885 
1886     /**
1887      * Send the HELO command to the SMTP server.
1888      *
1889      * @exception MessagingException
1890      */
1891     protected void sendHelo() throws MessagingException {
1892         sendLine("HELO " + getLocalHost());
1893 
1894         SMTPReply line = getReply();
1895 
1896         // we get a 250 code back. The first line is just a greeting, and
1897         // extensions are identifed on
1898         // continuations. If this fails, then we'll try once more with HELO to
1899         // establish bona fides.
1900         if (line.getCode() != COMMAND_ACCEPTED) {
1901             throw new MessagingException("Failure sending HELO command to SMTP server");
1902         }
1903     }
1904 
1905     /**
1906      * Retrieve the local client host name.
1907      *
1908      * @return The string version of the local host name.
1909      * @exception SMTPTransportException
1910      */
1911     public String getLocalHost() throws MessagingException {
1912         if (localHost == null) {
1913 
1914             try {
1915                 localHost = InetAddress.getLocalHost().getHostName();
1916             } catch (UnknownHostException e) {
1917                 // fine, we're misconfigured - ignore
1918             }
1919 
1920             if (localHost == null) {
1921                 localHost = getProtocolProperty(MAIL_SMTP_LOCALHOST);
1922             }
1923 
1924             if (localHost == null) {
1925                 localHost = getSessionProperty(MAIL_LOCALHOST);
1926             }
1927 
1928             if (localHost == null) {
1929                 throw new MessagingException("Can't get local hostname. "
1930                         + " Please correctly configure JDK/DNS or set mail.smtp.localhost");
1931             }
1932         }
1933 
1934         return localHost;
1935     }
1936 
1937     /**
1938      * Return the current reportSuccess property.
1939      *
1940      * @return The current reportSuccess property.
1941      */
1942     public boolean getReportSuccess() {
1943         return reportSuccess;
1944     }
1945 
1946     /**
1947      * Set a new value for the reportSuccess property.
1948      *
1949      * @param report
1950      *            The new setting.
1951      */
1952     public void setReportSuccess(boolean report) {
1953         reportSuccess = report;
1954     }
1955 
1956     /**
1957      * Return the current startTLS property.
1958      *
1959      * @return The current startTLS property.
1960      */
1961     public boolean getStartTLS() {
1962         return reportSuccess;
1963     }
1964 
1965     /**
1966      * Set a new value for the startTLS property.
1967      *
1968      * @param start
1969      *            The new setting.
1970      */
1971     public void setStartTLS(boolean start) {
1972         useTLS = start;
1973     }
1974 
1975     /**
1976      * Retrieve the SASL realm used for DIGEST-MD5 authentication. This will
1977      * either be explicitly set, or retrieved using the mail.smtp.sasl.realm
1978      * session property.
1979      *
1980      * @return The current realm information (which can be null).
1981      */
1982     public String getSASLRealm() {
1983         // if the realm is null, retrieve it using the realm session property.
1984         if (realm == null) {
1985             realm = getProtocolProperty(MAIL_SMTP_SASL_REALM);
1986         }
1987         return realm;
1988     }
1989 
1990     /**
1991      * Explicitly set the SASL realm used for DIGEST-MD5 authenticaiton.
1992      *
1993      * @param name
1994      *            The new realm name.
1995      */
1996     public void setSASLRealm(String name) {
1997         realm = name;
1998     }
1999 
2000     /**
2001      * Explicitly set the local host information.
2002      *
2003      * @param localHost
2004      *            The new localHost name.
2005      */
2006     public void setLocalHost(String localHost) {
2007         this.localHost = localHost;
2008     }
2009 
2010     /**
2011      * Process an extension string passed back as the EHLP response.
2012      *
2013      * @param extension
2014      *            The string value of the extension (which will be of the form
2015      *            "NAME arguments").
2016      */
2017     protected void processExtension(String extension) {
2018         String extensionName = extension.toUpperCase();
2019         String argument = "";
2020 
2021         int delimiter = extension.indexOf(' ');
2022         // if we have a keyword with arguments, parse them out and add to the
2023         // argument map.
2024         if (delimiter != -1) {
2025             extensionName = extension.substring(0, delimiter).toUpperCase();
2026             argument = extension.substring(delimiter + 1);
2027         }
2028 
2029         // add this to the map so it can be tested later.
2030         serverExtensionArgs.put(extensionName, argument);
2031 
2032         // process a few special ones that don't require extra parsing.
2033         // AUTH and AUTH=LOGIN are handled the same
2034         if (extensionName.equals("AUTH")) {
2035             // if we don't have an argument on AUTH, this means LOGIN.
2036             if (argument == null) {
2037                 serverAuthenticationMechanisms.put("LOGIN", "LOGIN");
2038             } else {
2039                 // The security mechanisms are blank delimited tokens.
2040                 StringTokenizer tokenizer = new StringTokenizer(argument);
2041 
2042                 while (tokenizer.hasMoreTokens()) {
2043                     String mechanism = tokenizer.nextToken().toUpperCase();
2044                     serverAuthenticationMechanisms.put(mechanism, mechanism);
2045                 }
2046             }
2047         }
2048         // special case for some older servers.
2049         else if (extensionName.equals("AUTH=LOGIN")) {
2050             serverAuthenticationMechanisms.put("LOGIN", "LOGIN");
2051         }
2052         // does this support transport level security?
2053         else if (extensionName.equals("STARTTLS")) {
2054             // flag this for later
2055             serverTLS = true;
2056         }
2057     }
2058 
2059     /**
2060      * Retrieve any argument information associated with a extension reported
2061      * back by the server on the EHLO command.
2062      *
2063      * @param name
2064      *            The name of the target server extension.
2065      *
2066      * @return Any argument passed on a server extension. Returns null if the
2067      *         extension did not include an argument or the extension was not
2068      *         supported.
2069      */
2070     public String extensionParameter(String name) {
2071         if (serverExtensionArgs != null) {
2072             return (String) serverExtensionArgs.get(name);
2073         }
2074         return null;
2075     }
2076 
2077     /**
2078      * Tests whether the target server supports a named extension.
2079      *
2080      * @param name
2081      *            The target extension name.
2082      *
2083      * @return true if the target server reported on the EHLO command that is
2084      *         supports the targer server, false if the extension was not
2085      *         supported.
2086      */
2087     public boolean supportsExtension(String name) {
2088         // this only returns null if we don't have this extension
2089         return extensionParameter(name) != null;
2090     }
2091 
2092     /**
2093      * Determine if the target server supports a given authentication mechanism.
2094      *
2095      * @param mechanism
2096      *            The mechanism name.
2097      *
2098      * @return true if the server EHLO response indicates it supports the
2099      *         mechanism, false otherwise.
2100      */
2101     protected boolean supportsAuthentication(String mechanism) {
2102         return serverAuthenticationMechanisms.get(mechanism) != null;
2103     }
2104 
2105     /**
2106      * Authenticate with the server, if necessary (or possible).
2107      *
2108      * @return true if we are ok to proceed, false for an authentication
2109      *         failures.
2110      */
2111     protected boolean processAuthentication() throws MessagingException {
2112         // no authentication defined?
2113         if (!isProtocolPropertyTrue(MAIL_SMTP_AUTH)) {
2114             return true;
2115         }
2116 
2117         // we need to authenticate, but we don't have userid/password
2118         // information...fail this
2119         // immediately.
2120         if (username == null || password == null) {
2121             return false;
2122         }
2123 
2124         ClientAuthenticator authenticator = null;
2125 
2126         // now go through the progression of mechanisms we support, from the
2127         // most secure to the
2128         // least secure.
2129 
2130         if (supportsAuthentication(AUTHENTICATION_DIGESTMD5)) {
2131             authenticator = new DigestMD5Authenticator(host, username, password, getSASLRealm());
2132         } else if (supportsAuthentication(AUTHENTICATION_CRAMMD5)) {
2133             authenticator = new CramMD5Authenticator(username, password);
2134         } else if (supportsAuthentication(AUTHENTICATION_LOGIN)) {
2135             authenticator = new LoginAuthenticator(username, password);
2136         } else if (supportsAuthentication(AUTHENTICATION_PLAIN)) {
2137             authenticator = new PlainAuthenticator(username, password);
2138         } else {
2139             // can't find a mechanism we support in common
2140             return false;
2141         }
2142 
2143         if (debug) {
2144             debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
2145         }
2146 
2147         // if the authenticator has some initial data, we compose a command
2148         // containing the initial data.
2149         if (authenticator.hasInitialResponse()) {
2150             StringBuffer command = new StringBuffer();
2151             // the auth command initiates the handshaking.
2152             command.append("AUTH ");
2153             // and tell the server which mechanism we're using.
2154             command.append(authenticator.getMechanismName());
2155             command.append(" ");
2156             // and append the response data
2157             command.append(new String(Base64.encode(authenticator.evaluateChallenge(null))));
2158             // send the command now
2159             sendLine(command.toString());
2160         }
2161         // we just send an auth command with the command type.
2162         else {
2163             StringBuffer command = new StringBuffer();
2164             // the auth command initiates the handshaking.
2165             command.append("AUTH ");
2166             // and tell the server which mechanism we're using.
2167             command.append(authenticator.getMechanismName());
2168             // send the command now
2169             sendLine(command.toString());
2170         }
2171 
2172         // now process the challenge sequence. We get a 235 response back when
2173         // the server accepts the
2174         // authentication, and a 334 indicates we have an additional challenge.
2175         while (true) {
2176             // get the next line, and if it is an error response, return now.
2177             SMTPReply line;
2178             try {
2179                 line = new SMTPReply(receiveLine());
2180             } catch (MalformedSMTPReplyException e) {
2181                 throw new MessagingException(e.toString());
2182             } catch (MessagingException e) {
2183                 throw e;
2184             }
2185 
2186             // if we get a completion return, we've passed muster, so give an
2187             // authentication response.
2188             if (line.getCode() == AUTHENTICATION_COMPLETE) {
2189                 if (debug) {
2190                     debugOut("Successful SMTP authentication");
2191                 }
2192                 return true;
2193             }
2194             // we have an additional challenge to process.
2195             else if (line.getCode() == AUTHENTICATION_CHALLENGE) {
2196                 // Does the authenticator think it is finished? We can't answer
2197                 // an additional challenge,
2198                 // so fail this.
2199                 if (authenticator.isComplete()) {
2200                     return false;
2201                 }
2202 
2203                 // we're passed back a challenge value, Base64 encoded.
2204                 byte[] challenge = Base64.decode(line.getMessage().getBytes());
2205 
2206                 // have the authenticator evaluate and send back the encoded
2207                 // response.
2208                 sendLine(new String(Base64.encode(authenticator.evaluateChallenge(challenge))));
2209             }
2210             // completion or challenge are the only responses we know how to
2211             // handle. Anything else must
2212             // be a failure.
2213             else {
2214                 if (debug) {
2215                     debugOut("Authentication failure " + line);
2216                 }
2217                 return false;
2218             }
2219         }
2220     }
2221 
2222     /**
2223      * Simple holder class for the address/send status duple, as we can have
2224      * mixed success for a set of addresses and a message
2225      */
2226     public class SendStatus {
2227         public final static int SUCCESS = 0;
2228 
2229         public final static int INVALID_ADDRESS = 1;
2230 
2231         public final static int SEND_FAILURE = 2;
2232 
2233         public final static int GENERAL_ERROR = 3;
2234 
2235         // the status type of the send operation.
2236         int status;
2237 
2238         // the address associated with this status
2239         InternetAddress address;
2240 
2241         // the command string send to the server.
2242         String cmd;
2243 
2244         // the reply from the server.
2245         SMTPReply reply;
2246 
2247         /**
2248          * Constructor for a SendStatus item.
2249          *
2250          * @param s
2251          *            The status type.
2252          * @param a
2253          *            The address this is the status for.
2254          * @param c
2255          *            The command string associated with this status.
2256          * @param r
2257          *            The reply information from the server.
2258          */
2259         public SendStatus(int s, InternetAddress a, String c, SMTPReply r) {
2260             this.cmd = c;
2261             this.status = s;
2262             this.address = a;
2263             this.reply = r;
2264         }
2265 
2266         /**
2267          * Get the status information for this item.
2268          *
2269          * @return The current status code.
2270          */
2271         public int getStatus() {
2272             return this.status;
2273         }
2274 
2275         /**
2276          * Retrieve the InternetAddress object associated with this send
2277          * operation.
2278          *
2279          * @return The associated address object.
2280          */
2281         public InternetAddress getAddress() {
2282             return this.address;
2283         }
2284 
2285         /**
2286          * Retrieve the reply information associated with this send operati
2287          *
2288          * @return The SMTPReply object received for the operation.
2289          */
2290         public SMTPReply getReply() {
2291             return reply;
2292         }
2293 
2294         /**
2295          * Get the command string sent for this send operation.
2296          *
2297          * @return The command string for the MAIL TO command sent to the
2298          *         server.
2299          */
2300         public String getCommand() {
2301             return cmd;
2302         }
2303 
2304         /**
2305          * Get an exception object associated with this send operation. There is
2306          * a mechanism for reporting send success via a send operation, so this
2307          * will be either a success or failure exception.
2308          *
2309          * @param reportSuccess
2310          *            Indicates if we want success operations too.
2311          *
2312          * @return A newly constructed exception object.
2313          */
2314         public MessagingException getException(boolean reportSuccess) {
2315             if (status != SUCCESS) {
2316                 return new SMTPAddressFailedException(address, cmd, reply.getCode(), reply.getMessage());
2317             } else {
2318                 if (reportSuccess) {
2319                     return new SMTPAddressSucceededException(address, cmd, reply.getCode(), reply.getMessage());
2320                 }
2321             }
2322             return null;
2323         }
2324     }
2325 
2326     /**
2327      * Internal debug output routine.
2328      *
2329      * @param value
2330      *            The string value to output.
2331      */
2332     protected void debugOut(String message) {
2333         debugStream.println("SMTPTransport DEBUG: " + message);
2334     }
2335 
2336     /**
2337      * Internal debugging routine for reporting exceptions.
2338      *
2339      * @param message
2340      *            A message associated with the exception context.
2341      * @param e
2342      *            The received exception.
2343      */
2344     protected void debugOut(String message, Throwable e) {
2345         debugOut("Received exception -> " + message);
2346         debugOut("Exception message -> " + e.getMessage());
2347         e.printStackTrace(debugStream);
2348     }
2349 }